I know this sounds like a weird question, let me try explaining it further with examples:
First of all, I'm trying to add some functionality to JSDoc in a simple library. Let's call it jsdoc-extra.
When a project includes my library, it should also have jsdoc installed. I have listed jsdoc as a dependency on my own library as well.
jsdoc-extra > package.json
{
[...]
"dependencies": {
[...]
"jsdoc": "^3.6.6",
[...]
}
}
And let's suppose a "sample" project is trying to use my library (this is what I actually have running for now, installed from the file)
{
[...]
"dependencies": {
"jsdoc": "^3.6.6",
"jsdoc-cov": "file:../jsdoc-cov",
"jsdoc-ts-utils": "^1.1.2"
}
}
From my jsdoc-extra code I can search and find the sample/node_modules/jsdoc/jsdoc.js that is installed on the "client" application (sample), or use my own jsdoc-extra/node_modules/jsdoc/jsdoc.js instead when the first one is not available. I can then execute it with spawn
So far so good. However:
The "client" (sample project in this case) might be using some plugins on their jsdoc setup, like you can see in the previous code snippet, I have ts-utils installed as an example.
So when I'm inside the sample project, and try running:
node_modules/jsdoc/jsdoc-extra.js -c jsdoc.json
(jsdoc.json is the standard jsdoc config file that I just pass through to it)
I get this kind of errors:
(node:3961) UnhandledPromiseRejectionWarning: Error: ERROR: Unable to find the plugin "jsdoc-ts-utils"
It seems my app (jsdoc-extra) cannot use jsdoc-ts-utils that is installed on the client (sample) project, even when I run sample's own installed jsdoc.
I want to be able to execute it like this so the "client" project can execute jsdoc-extra without extra jsdoc configuration, it will use whatever it's already using for regular jsdoc operations.
I'm beginning to think that my best options is to actually write a jsdoc plugin...
I know this is a lot, and probably confusing, I'll gladly provide more info if you think it's necessary. Thanks!
I decided to close this and instead just write a jsdoc plugin. It's the easier way to hack into the data it generates to do what I want. My extra functionality will be tied in with the doc generation, which is not ideal, but I'll deal with it...
This seems to be a trivial problem, but it is not very obvious what settings/configurations need to be used to solve this issue.
Here are the Hello World program directory structure and the source code:
Directory Structure:
| -- HelloWorldProgram
| -- HelloWorld.ts
| -- index.ts
| -- package.json
| -- tsconfig.json
index.ts:
import {HelloWorld} from "./HelloWorld";
let world = new HelloWorld();
HelloWorld.ts:
export class HelloWorld {
constructor(){
console.log("Hello World!");
}
}
package.json:
{
"type": "module",
"scripts": {
"start": "tsc && node index.js"
}
}
Now, execution of the command tsc && node index.js results in the following error:
internal/modules/run_main.js:54
internalBinding('errors').triggerUncaughtException(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'HelloWorld' imported from HelloWorld\index.js
Did you mean to import ../HelloWorld.js?
at finalizeResolution (internal/modules/esm/resolve.js:284:11)
at moduleResolve (internal/modules/esm/resolve.js:662:10)
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:752:11)
at Loader.resolve (internal/modules/esm/loader.js:97:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:242:28)
at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:50:40)
at link (internal/modules/esm/module_job.js:49:36) {
code: 'ERR_MODULE_NOT_FOUND'
}
It is obvious that the problem seems to have been originated from the fact that in index.ts Typescript file there is no .js extension in the import statement (import {HelloWorld} from "./HelloWorld";). Typescript didn't throw any error during compilation. However, during runtime Node (v14.4.0) wants the .js extension.
Hope the context is clear.
Now, how to change the compiler output setting (tsconfig.json or any flags) so that local relative path imports such as import {HelloWorld} from ./Helloworld; will get replaced by import {HelloWorld} from ./Helloworld.js; during Typescript to Javascript compilation in the index.js file?
Note:
It is possible to directly use the .js extension while importing inside typescript file. However, it doesn't help much while working with hundreds of old typescript modules, because then we have to go back and manually add .js extension. Rather than that for us better solution is to batch rename and remove all the .js extension from all the generated .js filenames at last.
To fellow developers who are looking for a solution to this issue, the possible work-arounds we have come across are as follows:
Use .js extension in the import:
For new files, it is possible to simply add ".js" extension in the import statement in Typescript file while editing.
Example: import {HelloWorld} from "./HelloWorld.js";
Extensionless filename
If working with old projects, rather than going through each and every file and updating the import statements, we found it easier to simply batch rename and remove the ".js" extension from the generated Javascript via a simple automated script. Please note however that this might require a minor change in the server side code to serve these extension-less ".js" files with the proper MIME type to the clients.
Use regex to batch replace import statements
Another option is to use regular expression to batch find and replace in all files the import statements to add the .js extension. An example: https://stackoverflow.com/a/73075563/3330840 or similar other answers.
Updated side note:
Initially, some answers and comments here created unnecessary distractions and tried to evade the original purpose of the question instead of providing possible solutions and dragged me into having to defend the validity of the problem. 16k+ views on this question indicates many developers were faced with this issue as well which itself proves the importance of the question. Hence, the original side note now has been moved to the comments to avoid further distraction.
If you are using VS code, you can use regex to replace the paths.
Find: (\bfrom\s+["']\..*)(["'])
Replace: $1.js$2
This solution is inspired on a previous solution, but this one works better because of the reasons outlined below. Note that this solution is not perfect since it uses regex instead of syntactically analyzing file imports.
Ignores npm imports. Example:
import fs from 'fs'
Supports multi-line imports. Example:
import {
foo,
bar
} from './file'
Supports as imports. Example:
import * as foo from './file'
Supports single and double quotes. Example:
import foo from './file'
import foo from "./file"
Supports exports. See export docs. Example:
export { foo } from './file'
you also can add nodejs CLI flags for enable node module resolution:
for importing json --experimental-json-modules
for importing without extensions --experimental-specifier-resolution=node
node --experimental-specifier-resolution=node dist/some-file.js
It's worth mentioning --experimental-specifier-resolution=node has a bug (or not) then you cannot run bin scripts without extensions (for example in package.json bin section, "tsc" won't work, but "tsc":"tsc.js" will work).
Too many packages have bin scripts without any extensions so there is some trouble with adding NODE_OPTIONS="--experimental-specifier-resolution=node" env variable
As many have pointed out. The reason why TypeScript doesn't and will never add file extension to import statements is their premise that transpiling pure JavaScript code should output the same JavaScript code.
I think having a flag to make TypeScript enforce file extensions in import statements would be the best they could do. Then linters like ESLint could maybe offer an auto fixer based on that rule.
In case you have trouble with TypeScript and ESM, there is this tiny library that actual works perfectly:
npm install #digitak/tsc-esm --save-dev
Replace the tsc call with tsc-esm in your scripts:
{
"scripts": {
"build": "tsc-esm"
}
}
Finally you can run:
npm run build
Had the same issue on a big monorepo, can't edit each file manually,
so I wrote a script to fix all esm import in my project and append .js or /index.js in a safe way:
fix-esm-import-paths
Test before using in your project.
(Probably) Better Answer
See here for a potentially better answer based on this idea & proposed in the comments.
I haven't tested this yet, but seems like my original answer below is lacking & seems like the linked answer is better. I can't say for sure but I'd recommend people check that out first.
My Original Answer
If you know that all your import statements should really have the .js extension, and all imports either have no extension or already have the .js extension, you could use a regex find/replace to "normalise" everything. I would advise you just check your git (or other VCS) logs before committing the change. Here are the regexes I use in VSCode:
Find: (import .* from ".*(?!\.js)(.){3})".
Replace: $1.js".
The find expression will match imports without the .js extension. The first group will capture the part up to the closing quote. The replace expression then takes the first group of the match, which always doesn't have the .js extension, and then appends the extension.
Failing getting a linter set up, you could run this periodically & check the git logs to ensure no imports without the extension slip into the codebase.
npm, anyone?
npm i fix-esm-import-path
check it on npmjs or github.
Only has 8 stars (one is from me), but I'm using it on multiple projects and it does what I need:
npx fix-esm-import-path dist/your-compiled-entrypoint.js
I usually just use the .js extension in import statements in typescript files as well and it works.
Not using a file extension in import paths is a nodejs only thing. Since you are not using commonjs but module you are not using nodejs. Therefore you have to use the .is extension in import paths.
TypeScript cannot possibly know what URI you are going to use to serve your files, therefore it simply must trust that the module path you gave it is correct. In this case, you gave it a path to a URI that doesn't exist, but TypeScript cannot know that, so there is nothing it can do about it.
If you are serving the module with a URI that ends in .js, then your module path needs to end in .js. If your module path doesn't end in .js, then you need to serve it up at a URI that does not end in .js.
Note that the W3C strongly advises against using file extensions in URIs, because it makes it harder to evolve your system, and advocates to instead rely on Content Negotiation.
Rewriting paths would break a couple of fundamental design principles of TypeScript. One design principle is that TypeScript is a proper superset of ECMAScript and every valid ECMAScript program and module is a semantically equivalent TypeScript program and module. Rewriting paths would break that principle, because a piece of ECMAScript would behave differently depending on whether it is executed as ECMAScript or TypeScript. Imagine, you have the following code:
./hello
export default "ECMAScript";
./hello.js
export default "TypeScript";
./main
import Hello from "./hello";
console.log(Hello);
If TypeScript did what you suggest, this would print two different things depending on whether you execute it as ECMAScript or as TypeScript, but the TypeScript design principles say that TypeScript does never change the meaning of ECMAScript. When I execute a piece of ECMAScript as TypeScript, it should behave exactly as it does when I execute it as ECMAScript.
You can use the same solution as me
File: tsconfig.json
"compilerOptions": {
"module": "commonjs", ==> not required extension when import
"target": "ES6",
},
Because use commonjs, you must remove "type": "module" in package.json
Done :D
In my typescript project I am using "whatwg-fetch": "2.0.3" this is latest version of this poly fill ans as types I am using this version "#types/whatwg-fetch": "0.0.33" and everything works well for me when i used typescript version "2.2.0".
But now I would like to update typescript to latest version "2.5.3" and that is my problem wen I try co compile this then I got bambilon types errors first what i got is this error
in
D:\Projects\AppStoreSource\appstore\source\AppStore\node_modules\#types\whatwg-fetch\index.d.ts
(11,13): error TS2451: Cannot redeclare block-scoped variable 'fetch'.
Yes true is that on line 11 is this declare
declare let fetch: typeof window.fetch;
Or
D:\Projects\AppStoreSource\appstore\source\AppStore\node_modules\#types\whatwg-fetch\index.d.ts
(13,14): error TS2300: Duplicate identifier 'HeadersInit'.
So did some one try use "whatwg-fetch" and latest typescript or I potential problem is in something like loaders etc, for me it looks like I have two definition or i do not know ... i am lost :-) ???
Or is there alternative for other poly fill that is compatible with typescript "2.5.3"??? I would like avoid to fork and rewrite typings.
Thanks very much for your help
The simple answer on this one is that you don't need to add types for fetch, as TypeScript has them in the lib.d.ts file now.
So if you remove the #types/whatwg-fetch development dependency, your program should compile.
This is very often the exact reason you get errors in a definition file, especially with anything based on emerging standards.
I have gone through several solutions including the ones listed here:
Environment Variables in an isomorphic JS app: Webpack find & replace?
Passing environment-dependent variables in webpack
I am used to using something like gulp-replace-task to find and update a config file for the app to replace things like ##SERVER_URL with something set from the environment.
That way I can do export SERVER_URL=something or run the script with SERVER_URL=something gulp build to set the configuration.
I've tried all of the following:
Using the transform-loader plus envify
This is a suggestion from the first question, but it does not work for me because of:
Module build failed: Error: Parse Error: Line 1: Illegal import declaration
at throwError (ngapp/node_modules/esprima-fb/esprima.js:2823:21)
Seems like esprima-fb is using an import declaration that Webpack can't use for some reason or another. The project is no longer maintained either, so this may be the wrong road to go down.
Using DefinePlugin
I've added:
module: {plugins: [new webpack.DefinePlugin({"process.env.SERVER_URL": "something"})]}
This seems to be ignored, or at least process.env.SERVER_URL does not get interpolated in my typescript files. When I console.log(process.env), it emits an empty object.
Setting using --define for webpack
I updated my npm script:
"start": "webpack-dev-server --define process.env.SERVER_URL=${SERVER_URL}"
However this just ends up replacing process.env.SERVER_URL in my code with a literal "${SERVER_URL}" rather than being interpolated in the npm script.
Is there a simple / convenient (or at this point really any) way to use environment variables in TypeScript apps built with Webpack?
My Webpack setup is essentially what is listed in the Angular docs.
I started learning from https://egghead.io/technologies/es6 the ECMAScript 6 way, knowing there is still a lot that might change, but wanted to get a early start.
However, when I follow the tutorial instructions on (https://egghead.io/lessons/ecmascript-6-es6-modules-es2015-import-and-export) exactly the same, I get an error which I have no idea what I might have done wrong.
Uncaught ReferenceError: require is not defined
Code on that line after Babel converted to ES5
"use strict";
var _distExporter = require("dist/exporter");
console.log("2 + 3=", (0, _distExporter.sumTwo)(2, 3));
//# sourceMappingURL=importer.js.map
Developers are mentioning CommonJS and WebPack as a solution some even mentioned RequireJS, but nowhere in the tutorial did it state I should should code or use alternative libraries.
My HTML is this
<html>
<head>
<script src="dist/importer.js"></script>
</head>
<body>
</body>
</html>
My importer.js is
import { sumTwo } from "dist/exporter";
console.log( "2 + 3=", sumTwo(2,3))
And my exporter.js is
function sumTwo(a, b){
return a + b;
}
export { sumTwo };
I have no idea where I'm going wrong. I'm using BabelJS (https://babeljs.io/)
If you run this code under Node, rather than in a browser, you should see the results you are expecting. Node understands CommonJS require calls and will go away and grab that other file for you.
The browser has no idea that require is anything special. But we can use a tool to make the browser understand. Here's an example with Browserify, as some other people have mentioned you can also use WebPack, but I think the learning curve for WebPack is a lot steeper.
First you'll need a couple of modules.
npm install -g browserify
npm install --save-dev babelify
Then we can use these modules together like this.
browserify main-file.js -o output-file.js -t babelify
This will walk your source files checking calls to require in each one and adding the other files that it requires to the bundle. Then it runs the Babel transform over it, to convert ES6 to ES5. Finally it wraps it all up in some code that lets require work in a browser.