77

enter image description here

I'm working on a node project (screenshot). I have a single function (urls) in helpers.js which I'm exporting at the bottom as:

module.exports = {
urls: urls,
};

In my index.js I'm trying to import it with:

import { urls } from './helpers';
const myUrls = urls(1,3);

When I run it I get

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/optionhomes11/nodeprojects/hayes/helpers' imported from /home/optionhomes11/nodeprojects/hayes/index.js Did you mean to import ../helpers.js?

What am I doing wrong?

NiñoScript
  • 4,523
  • 2
  • 27
  • 33
user1592380
  • 34,265
  • 92
  • 284
  • 515
  • You're mixing node's CJS module system (require/module.exports) with ES6 modules (import/export). Don't do that unless you are very sure you know what you're doing, there are some gotchas both subtle and gross. – Jared Smith Dec 21 '20 at 15:43

10 Answers10

156

When you are using ECMAScript modules you are forced to provide the file extension: https://nodejs.org/api/esm.html#esm_mandatory_file_extensions

So, on top of what other suggested of using "type": "module" on package.json you also need to specify the file extension import {urls} from './helpers.js'. You can also use the flag --es-module-specifier-resolution=node to make it resolve js files as modules just like it did before with require

Danielo515
  • 5,996
  • 4
  • 32
  • 66
  • 1
    This answer was the one that helped me! I was using import and the file was .js - Adding --es-module-specifier-resolution=node saved the day for me! Thanks! – letie Dec 10 '21 at 11:24
  • 3
    WOW!... Really? I'm glad I came upon this answer before spending daaaays on this. Thank you Danielo515. – Cody Feb 03 '22 at 21:44
  • This just worked for me, referencing my TS-extensioned exporting files as JS, where imported. The more I learn about JavaScript/TypeScript/Node, the more I realise I know nothing....this feels like it should not make sense. – Big Rich Apr 11 '22 at 04:55
  • 1
    Can't believe this was it, solved my problem. – Lautaro Jorge Garcia Jul 26 '22 at 02:26
  • 1
    How do you handle that in .ts files, for instance when transpiling an index.ts? Ideally it could rewrite all ".ts" imports to ".js"? I am surprise that a library like "react-bootstrap" for instance doesn't seem to use the extension in its ESM index (you can install it in a project and check the dist folder to see what happens) – Eric Burel Sep 28 '22 at 14:04
  • @EricBurel I think your best bet is to change the output extension and set it to transpile to mjs rather than js, which will instruct node to use the modules extensions – Danielo515 Oct 03 '22 at 09:37
  • @EricBurel Strangely enough, when I use `.js` extension with the imports, the tsc compiler doesnt seem to mind. – sayandcode Dec 03 '22 at 11:45
62

This Happens because when using ES Modules we are enforced to specify the file extension in the import statement

import * from "./demo.js" // Works fine
import * from "./demo" // Will throw error as you see

Note that : The above two options are both valid when using commonJs instead

60

You have used

"type":"module"

then make sure you have import file must be .js extension

Alamin
  • 1,878
  • 1
  • 14
  • 34
31

You're not doing anything wrong. The current resolution algorithm for EcmaScript Modules of file extensions and the ability to import directories that have an index file requires using an experimental flag. see esm, the bottom part.

so to make your work as it is, instead of

$ node index.js

you do:

$ node --experimental-specifier-resolution=node index.js

You can also create a script in your package.json

like so:

     "scripts": {
  "start": "NODE_OPTIONS='--experimental-specifier-resolution=node' node src/index.js
Dharman
  • 30,962
  • 25
  • 85
  • 135
odili
  • 565
  • 5
  • 6
  • 1
    Adding `--trace-warnings` flag will help to show where the warning is exactly, then add: `NODE_OPTIONS='--experimental-specifier-resolution=node --trace-warnings' node src/index.js` – Adrian Escutia Soto Nov 29 '21 at 02:01
11

Try this as below

search in your "/home/optionhomes11/nodeprojects/hayes/index.js"

And looking for "/home/optionhomes11/nodeprojects/hayes/helpers"

change

"/home/optionhomes11/nodeprojects/hayes/helpers"

to

"/home/optionhomes11/nodeprojects/hayes/helpers.js"
Jayson LP Chen
  • 121
  • 1
  • 3
7

You should be importing from './helpers', not '.helpers'.

rav2040
  • 111
  • 1
  • 4
7

//helpers.js

export default urls;

//index.js

import urls from './helpers.js';

//package.json (under name)

"type": "module"
Francis Ade
  • 328
  • 3
  • 8
2

I would also recommend you install the Path Intellisense VS code extension. It will really help when handling nested paths.

wisdom
  • 230
  • 2
  • 10
2

Answer 1

This answer does not require using a runtime flag --es-module-specifier-resolution=node at execution time

However, you have to modify your ts source code, which is a pain if there is are a lot of files. And, the modified files will no longer compile in "commonjs" mode, if you want to go back or use dual "commonjs"/"module" modes.

Modify your tsconfig.json to ensure at least these setting versions:

   compilerOptions:{
    "lib": ["es2020"],
    "module": "ES2022",
    "moduleResolution": "node",
    "target": "es2022",
   }

Works with typescript 4.6.3. (Note sure about 4.6.1 or lower).*

Modify index.js

import {urls} from "#helpers";

Modify package.json

  "imports": {
    "#helpers": "./helpers.js"
  }

The leading "#" is mandatory

Answer 2

In addition to not requiring the node runtime execution flag, this answer also satisifes:

  1. does not require changing your *.*ts source code (thus leaving it compilable under commonjs if you ever chose to do so(*note))
  2. In case you are producing a library, it produces output which can be consumed by either "commonjs" or "module" clients.

(*note) When using rollup, inline maps are required - so there may sometimes be advantage to using commonjs during development and switching to "module" for release.

First modify package.json, create rollup.config.js, and then perform a post tsc action using rollup.

package.json

...
"exports":{
  "require":"./index.cjs",
  "import":"./index.js"
},
"types": "./index.d.ts",
"type": "module"    // you already had this

rollup.config.js

// import resolve from "@rollup/plugin-node-resolve";
import dts from "rollup-plugin-dts";
import commonjs from "@rollup/plugin-commonjs";
import * as path from "path";
import pkg from "./package.json";

export default [
  {
    input: "index.js",
    external:[], // you may quash 'unresolved' warning by adding here
    output: [
      { file: pkg.exports.require, format: "cjs" },
      { file: pkg.exports.import, format: "es" },
    ],
    plugins: [
      commonjs(),
    ],
  },
  {
    input: "./index.d.ts",
    output: [
      { file: pkg.types, format: "es" }, 
    ],
    plugins: [dts()],
  },
];

Call tsc then rollup:

npx tsc 
npx rollup -c
Craig Hicks
  • 2,199
  • 20
  • 35
-5

//if you change package.json file, you should try to reload npm. Write this in terminal

npm i

  • 2
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 01 '21 at 14:28