My VS Code says that it can't find an import even though my WebPack build still works.
Here is the import...
import * as tf from '#tensorflow/tfjs';
and the message from VS Code:
Cannot find module '#tensorflow/tfjs'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?
I have read something about path aliases which can be set up in the tsconfig.json to shorten long paths to modules. But if this is a path alias and I don't have it configured in my tsconfig.json, how does WebPack know where the module is located?
I also read that the convention for path aliases is to start with an "#" but the folder in the "node_modules" itself is called "#tensorflow", so i don't know if it really is a path alias and if not, maybe WebPack magically knows that it has to search in "node_modules" for this module?
As you can see i'm really confused about this and i would be greatfull if somebody could clear this up for me and explain what i must do to stop VS Code from complaining about the import.
Found the solution on my own.
I only found stuff about defining the aliases in the tsconfig.json expclicitly in the "path" option, but this couldn't be the answer to my problem because in my other Angular projects there is nothing like this defined even though I'm using #Angular imports there a lot without this problem.
But then I found this in my Angular project "moduleResolution": "node".
As stated in othe typescript documentation:
However, resolution for a non-relative module name is performed differently. Node will look for your modules in special folders named node_modules.
And behold, it works. Yes I could have probably tried this earlier, since its written in the message from VS Code from my question, but i though this was something for only node.js specific projects and I didn't read about this anywhere.
Related
I am building a PixiJS game with native ES modules, without using a bundler such as Parcel or Rollup.
I am also loading PixiJS as a native module, I found the module in node_modules/pixi.js/dist/esm/pixi.js. I can now use:
import { Sprite } from './pixi.mjs'
The only problem is that the accompanying index.d.ts file is not recognised by VS Code (I found this file in node_modules/pixi.js/index.d.ts), so type checking for PixiJS doesn't work correctly, even though the code DOES run!
My folder structure is super simple. There is not even a package.json.
index.html
js/app.js
js/pixi.mjs
js/pixi.mjs.map
js/index.d.ts
How can I force VS Code to read the index.d.ts file?
There are a couple of issues with this approach.
Declaration files are missing
First of all, if you look at the types installed by pixi.js in node_modules/pixi.js/index.d.ts you will find that it is mostly empty, and
it references external packages starting with #pixi. This is due to the mono-repo approach in the pixi project.
You will only fulfill those references if these packages are installed as npm modules (which happens automatically after installing pixi.js) and for which you need a package.json so that TypeScript finds them by their package name.
Link between imported classes and types
Secondly, VS Code (or better said, the TypeScript engine) is not capable of relating the files pixi.mjs and index.d.ts once they are moved out of the node_modules folder. Although the classes are named the same (Sprite, Container, etc.), TypeScript cannot safely conclude that they refer to the same thing.
This workflow is automatic when using npm thanks to the types property in node_modules/pixi.js/package.json. As an alternative, you can rename the file index.d.ts to pixi.mjs.d.ts and TypeScript will relate them by file name.
This is the folder structure that TypeScript can understand:
index.html
js/app.js
js/pixi.mjs
js/pixi.mjs.d.ts
js/pixi.mjs.map
TypeScript error detection in JS files
Finally, the previous fixes will only provide type inference but no error detection in js files. If you want VS Code to report errors, you need to create a tsconfig.json file with the following settings:
{
"compilerOptions": {
"noEmit": true,
"checkJs": true,
},
"include": ["js/*.js"],
}
I wrote a few code that use plain browser Javascript APIs only and can be run well within browser HTML (served by IIS Server or Chrome Extensions). Now I want to contribute to the community by writing a library I have not seen on the market yet. However looking at current solutions, I am at loss at how a project is even built (WebPack/Browserify etc). A side note: I never actually work with NodeJS/NPM before.
For example I have this TypeScript project with the main file AwesomeClass.ts like this:
import { Helper1 } from "./Helper1.js";
import { Helper2 } from "./Helper2.js";
export class AwesomeClass {
doSomething() {
new Helper1().doSomething();
new Helper2().doSomething();
}
}
When built with tsc (I use VS Code as IDE), I can perfectly put this inside an Javascript module and browser can run it.
import { AwesomeClass } from "./AwesomeClass.js";
// Do something with AwesomeClass
So my question is, how do I build and distribute AwesomeClass? Maybe no NPM needed, but from a CDN? Ideally, I think somehow I should have the following output in a dist folder and developer can refer them either by hosting the files by themselves or use a CDN:
awesomeclass.js: For those who want to just use AwesomeClass without module feature (I think it's called UMD?). I.e. expose the AwesomeClass to global scope.
awesomeclass.es6.js: For those who want to use AwesomeClass by using import statement, like import { AwesomeClass } from "https://cdn.example.com/awesomeclass.es6.js";. I like this approach best and want to use this.
I should have something like awesomeclass.d.ts so those using TypeScript can use it. This one is especially tricky because so far I still don't understand how to make it work for 2nd scenario. TypeScript cannot get the type from an import statement from Javascript, and even ignoring that, I cannot get any typing for import statements.
In all cases, I would rather have only one js/ts file packed together if possible but not a deal breaker if I cannot (i.e. user will have to download Helper1.js and Helper2.js as well if I cannot).
Here's my current tsconfig.json:
{
"compileOnSave": true,
"compilerOptions": {
"noImplicitAny": true,
"target": "ES2020",
"module": "ES2020",
"declaration": true
},
"exclude": [
"node_modules"
]
}
There's quite a lot of things you're asking and each of them have several answers so I'll try to provide you with a bit of an overview of your options.
Compiling everything to a single file
One of the things you asked is how you can compile everything to a single file. You can do that in 2 different ways, either using webpack to bundle it for client or using typescript directly.
If you use typescript you have to set outFile to a specific file, in which case it will compile everything to that file, however you can only do that if your module is also set to amd or system, both of which are not ideal. While this works it's something I'd suggest you don't use.
Instead you should use webpack to bundle all your stuff with the output option, in which case webpack will use ts-loader to invoke typescript for you, compile your stuff and bundle it into a single file.
You should also note here that this is only applicable if you actually want to serve it through web and not if you're building a library. If you're building an npm package that you're planning on letting people install with something like npx packageName so that you can use it like import somePackage from some-package, then you should be compiling your stuff to a /lib directory into normal javascript and just let them import it as javascript. There's no reason for why you should provide them with the original typescript in that case.
How to build and distribute it
It really depends on what exactly you're building and how it'll be used, however overall you have 2 main options.
You can either host it somewhere on some server with a domain of your choice so that people can download it. Or you can put it anywhere like a normal git repo where people can download it. In this case you'll have to compile it with webpack yourself, upload it yourself and then just share the link with people i.e. https://example.com/downloads/awesome. Alternatively you can use webpack to render it server side and expose an API to people that they can call in order to get your code, then it will deliver the bundled javascript to them once they call the API i.e. https://api.example.com/awesome which will hit your API with a GET request, which will route to awesome and then you invoke webpack's compiler to bundle your code server side.
Your other option is to build your package like normal, compile it and then use the official npm registry to host your npm package. Using this option will allow people to npx package or npm i package on your code and also allow them to use it like import awesome from 'awesome'. If you go this route then using webpack isn't necessary, or it depends, because people using it will import it into their own project and build it into their own webpack setup and bundle if required. In this case all you have to do is compile your typescript to something like a /lib and allow them to install and import it.
From the things that you're asking/saying it seems to me that you're trying to create an npm package, for that all you need is to create the package, compile your typescript, set up an account on npm and push your package to their registry, from where you can let anyone install it. For this you also shouldn't care at all about compiling all your code to a single file because it doesn't matter, if they use import awesome from 'awesome' then that file can again import anything else inside your own package and they wouldn't know it. You can just tsc your code to an output directory and let them know which is the default export for that package.
If your code has to run in browser then I don't believe just using typescript will be enough, in that case you'll have to use webpack, you might also need babel if you need to support older browsers and polyfills, which is something you can add to webpack, then you'll use webpack to compile your bundle. Webpack will then invoke typescript, through ts-loader, which will compile and bundle your code for you ready for web. In this case you'll still need to push this code to the npm registry as a package so others can use it.
The choice between those options is entirely dependent on what it is and who's going to use it and how.
This should be the core requirements in the tscongfig.json file
{
"compilerOptions": {
"lib": ["es6", "es2020.promise", "dom", "es2020"],
"declaration": true,
"target": "es2020",
"module": "es2020"
}
}
While to pack everything in just one file, I think you have to use something like webpack; but I don't know enough this topic to help you on that, sorry.
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
Although I am able to start the npm project using npm start without any issues with webpack or babel, once I run npm test, I find the following error related to testing App.js using App.test.js (where App.js imports ApolloClient):
TypeError: Cannot assign to read only property '__esModule' of object '[object Object]'
| import ApolloClient from 'apollo-boost';
| ^
at node_modules/apollo-boost/lib/bundle.cjs.js:127:74
at Array.forEach (<anonymous>)
at Object.<anonymous> (node_modules/apollo-boost/lib/bundle.cjs.js:127:36)
Essentially, I'm confused as to why I get an error when running the test but not when starting the project.
I've tried adding in a number of babel plugins to both .babelrc and in my webpack config file:
#babel/plugin-transform-object-assign
#babel/plugin-transform-modules-commonjs
babel-plugin-transform-es2015-modules-commonjs
However, I haven't been able to resolve the issue. My thinking was that this is related to the fact that the file that fails to compile was originally CommonJS.
I was only able to find something relatively similar here, https://github.com/ReactTraining/react-router/pull/6758, but I didn't find a solution.
Is there something that I'm missing specifically related to running tests? I should also mention I've tried frameworks other than Jest and ran into the same issue.
EDIT:
I removed everything from App.test.js except the imports to isolate the issue so it just contains the following:
import React from 'react';
import { shallow } from 'enzyme/build';
import App from './App';
UPDATE:
I was able to resolve the initial error by upgrading apollo-boost from version 0.3.1 to 0.4.2. However, I now have a different error that is similarly frustrating. I am using Babel 7 and have added the plugin #babel/plugin-syntax-dynamic-import to both my .babelrc and to my webpack.config.js files. Despite this, I get the following error related to the use of a dynamic import in App.js when running the Jest to test App.test.js:
SyntaxError: Support for the experimental syntax 'dynamicImport' isn't currently enabled
Add #babel/plugin-syntax-dynamic-import (https://git.io/vb4Sv) to the 'plugins' section of your Babel config to enable parsing.
I'm not sure if there is a parsing error or something else, but I've tried numerous things that have not worked. The closest discussion I could find related to this problem is, https://github.com/facebook/jest/issues/5920, however, the proposed solutions don't work for me.
UPDATE:
One thing that I'm trying is to avoid duplication of the babel options as right now they're both in .babelrc and in the babel-loader options within webpack.config.js. From what I found online (Whats the difference when configuring webpack babel-loader vs configuring it within package.json?), the way to make webpack use the settings in .babelrc is to not specify options. However, doing so results in the same error described above showing up only this time when running npm start. I will add that the project that was originally created using create-react-app, however, in order to support multiple pages, I needed to customize webpack's configuration and so ejected from it. I'm not sure why this is so convoluted.
its probably a babel configuration issue, I'm pretty sure jest needs to be compiled to work with create-react-app...
did you specify a setup file in package.json:
"jest": {
"setupFiles": [
"/setupTests.js"
]
}
and in setupTests.js:
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
It turns out that one of the components in the project's src directory had its own local package.json file even though it wasn't being used and was not installed as a local dependency in the top level package.json (instead imports were done using relative urls). For some reason, the existence of this file changed the behavior of webpack and other tools when starting and testing the project such that none of the top level configurations were used for files within directories with separate package.json files. Once I removed these local package.json files from the components sub-directory, all the prior issues were resolved. One hallmark of this problem is that compilation errors were not showing up for JavaScript files that weren't nested under an alternate package.json file.
Hopefully this is useful for anyone that encounters similar errors as I don't think the cause can be directly determined from the compiler messages alone.
I have an ES6 import.
import MyAwesomeComponent from 'packageNameOnlyWithoutPath';
I want to inspect the file packageNameOnlyWithoutPath. But I can't find it. I looked in node_modules but I don't see it there. So it might be hiding out elsewhere in the app.
Is there a canonical way to find the path that leads to packageNameOnlyWithoutPath?
you might want to take a look at index.js file in the packageNameOnlyWithoutPath folder inside the node_modules.
Else use text editors which supports goToDefinition plugin
TL;DR: Check resolve aliases in Webpack (or similar bundler) config or .babelrc
There's two places you can check first.
If you are using a bundler like Webpack, resolve aliases can be declared in the Webpack config file (usually webpack.config.js).
But I have also recently started using pure babel and node. The reoslves can also be declared in the .babelrc file (cleaner approach IMHO).
You should find what you're looking for in one of the above.