How to find an ES6 import module without a relative path? - javascript

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.

Related

Webpack can't resolve non-js `require`s from within node_modules

I've got a Next.js project configured to resolve imports that end in .web.js. This works outside of my node_modules directory. I did this by setting resolve.extensions = ['.web.js, '.js', '.jsx'] in my webpack config. I understand that this setting is responsible for resolving imports that don't have an extension, e.g. import _ from './component', when ./component.web.js exists.
I also have some node_modules that make use of this .web.js extension. They're private modules, but the idea stands. Let's say our node_modules looks like this. It may be worth noting that these modules have already been transpiled and as such use require rather than import.
- node_modules
- #foo
- bar.js
- baz.web.js
- baz.native.js
Now let's say that we have the following:
// bar.js
require("./baz");
If I try to import #foo/bar, the app will throw a module not found error on the line require("./baz"); saying that it can't be found. If I change it to require("./baz.web.js") or remove the line altogether then the app runs fine.
Why can webpack make these kind of resolutions outside of node_modules, but not within the directory? And how can I tell webpack to resolve those imports, too?
Depending on your module resolution strategy, you'll either find some files or not. Node.js resolves modules as outlined here. This means for you that require('./baz') is resolved to requesting /path/to/module/baz.js. Since your file is not actually named, it is not found. You can use require('./baz.web') instead.
As to whether Webpack can "automatically" handle which import to use, it probably comes down to using a plugin or having some sort of logic in bar.js to choose between baz.web and baz.native.

VS Code shows module not found even though WebPack build works

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.

Appending .js extension on relative import statements during Typescript compilation (ES6 modules)

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

Jest/Babel cannot find modules without the path prefix

I'm trying to follow the Jest getting started guide but with ES6 Modules and Babel.My root folder has two javascript files sumfn.js and sum.test.js. My sum.test.js file looks like this:
import { sum } from 'sumfn';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
However it seems like Jest is having trouble resolving sumfn, even though it clearly does find the file sunfn.js.
● Test suite failed to run
Cannot find module 'sumfn' from 'sum.test.js'
However, Jest was able to find:
'./sumfn.js'
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'json', 'jsx', 'ts', 'tsx', 'node'].
If I change the import line to use ./sumfn, it works. However, from what I read about ES6 imports, it seems like it should be able to find files in the same directory. Is this not supported in Jest perhaps?
You need to add the ./ to tell JavaScript The module you're looking for can be found in the following file path, relative to the current file. So you need to add ./ to say Look for sumfn.js in the current directory.
So one of the main reasons why I was confused is because there are a lot of out-dated ES6 module guides out there. In fact, many of the top results on Google seem to be outdated. I was looking at guides like this, this, and this, and they all said that you can import from a module name, without specifying the path, eg
import { double } from 'mymodule';
These are called "bare" import specifiers, and the guides said that it will by default search the current directory for a matching module. However, it seems like right now they are not supported in browsers.
Where it gets extremely confusing is that they are supported in BabelJS and Webpack, but it follows a different convention. For example, Webpack searches the paths specified in resolve.modules, which by default includes the node_modules folder. This is why the Create-React-App example can use statements like
import React from 'react';
It seems like the plan going forward is to let the environment determine how to resolve these "bare" specifiers (Source). Seems dangerous to let every environment resolve these specifiers differently, which could make it hard to make cross-compatible modules, but I guess that's the current plan for now.

Import from node_modules not recognized in es6 modules in browser

I'm trying to use lodash in my web application. I have installed lodash using npm in my local project.
I plan on using the ES6 modules in my code.
Here is my main.js file:
import * as _ from "lodash";
_.each([1, 2, 3, 4], (i) => {
console.log('index each ' + i);
});
And I have included it in index.html as:
<script src="js/main.js", type="module"></script>
But I get the following error in the browser console.
Uncaught TypeError: Failed to resolve module specifier "lodash".
Relative references must start with either "/", "./", or "../".
Note: I do not wish to use any bundling tool.
If you don't wish to use any bundling tools, you will need to provide a path to the lodash folder within node_modules, relative to the JavaScript file that you have the import statement in.
If you do not wish to use a bundler, it would also be worthwhile importing from the specific file, the function you need. For example:
import _each from '../node_modules/lodash/each'
As of 2021, please consider the following statement by Márton Salomváry (Jan 2018):
Unfortunately even most libraries authored or published in ES6 module format will not work because they target transpilers and rely on the Node.js ecosystem. Why is that a problem? Using bare module paths like import _ from 'lodash' is currently invalid, browsers don’t know what to do with them.
And also the statement by Jake Archibald (May 2017):
"Bare" import specifiers aren't currently supported.
Valid module specifiers must match one of the following:
A full non-relative URL.
Starts with /.
Starts with ./.
Starts with ../.
And javascript.info:
In the browser, import must get either a relative or absolute URL. Modules without any path are called “bare” modules. Such modules are not allowed in import.
Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have their own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet.
Bundlers facilitate the use of "Bare Imports" which is not supported by the browser yet. Unless you bundle your code, I recommend using the solution proposed by #Asler. Besides, a lot of work is currently being done to study the implementation of "Bare Imports" in the browser, please follow this link if you want to monitor the overall progress.
Eventually you can't use JS modules on browser like that. These modules are for webpack or other bundler.
Try module lodash-es
import each from '../node_modules/lodash-es/each.js'
If you are trying to import css file, make sure to mention .css in import statement.
you can add your node_modules to the public dirs, so you can easily shorten your importing syntax from ../../../../node_modules/my-package into /my-package
also, you need to specify the full path including the file and the extension
import mod from "/my-package/file.mjs"

Categories