Search code examples
node.jsnpm-installsuppress-warnings

node internal error MODULE_NOT_FOUND by global install but not by local install


My goal is to get a cli I can execute every where.
Seem the better way is global install. But when I try, it raise an error like if a required module was missing. Can't resolve this even after an npm install -g <missing_module>

I am using nvm with several node version so I think it can be a root cause. But when I check in node_modules of the current node version used, I find missing module.

I do not understand.

I put the steps to reproduce here with a little exemple test2 project. Note than my issue come from another big project where I encounter issue on dotenv-safe/config too.

If I resolve for this given exemple I think I will be able to resolve for the others missing modules.

$ mkdir -p test2
$ cd test2
$ npm init -y
$ echo '[{"key": "1"}]' > jsonFile.json
$ touch index.js
$ chmod +x index.js

Add this in package.json :

  "type": "module",
  "bin": {
    "test2-cli": "./index.js"
  },
$ cat index.js
#!/usr/bin/env node

import jsonContent from './jsonFile.json' assert { type: "json"}
console.log(jsonContent)

$ ./index.js 
[ { key: '1' } ]
(node:5362) ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

Following João Pimentel advice, I add --experimental-modules tag. And I do not want the warning, so I add --require=suppress-experimental-warnings option.

So I replace in index.js

#!/usr/bin/env node

by

#!/usr/bin/env -S node --experimental-modules --require=suppress-experimental-warnings
$ ./index.js 
node:internal/modules/cjs/loader:1042
  throw err;
  ^

Error: Cannot find module 'suppress-experimental-warnings'
Require stack:
- internal/preload
    at Module._resolveFilename (node:internal/modules/cjs/loader:1039:15)
    at Module._load (node:internal/modules/cjs/loader:885:27)
    at Module.require (node:internal/modules/cjs/loader:1105:19)
    at Module._preloadModules (node:internal/modules/cjs/loader:1395:12)
    at loadPreloadModules (node:internal/process/pre_execution:621:5)
    at setupUserModules (node:internal/process/pre_execution:125:3)
    at prepareExecution (node:internal/process/pre_execution:116:5)
    at prepareMainThreadExecution (node:internal/process/pre_execution:36:3)
    at node:internal/main/run_main_module:10:1 {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ 'internal/preload' ]
}

Node.js v18.13.0
$ npm install suppress-experimental-warnings

added 1 package in 536ms

1 package is looking for funding
  run `npm fund` for details
$ ./index.js 
[ { key: '1' } ]

Ok, now I want to access to this cli every where in my terminal.

$ npm install -g .

added 2 packages in 1s

1 package is looking for funding
  run `npm fund` for details
$ cd ../
$ test2-cli
node:internal/modules/cjs/loader:1042
  throw err;
  ^

Error: Cannot find module 'suppress-experimental-warnings'
Require stack:
- internal/preload
    at Module._resolveFilename (node:internal/modules/cjs/loader:1039:15)
    at Module._load (node:internal/modules/cjs/loader:885:27)
    at Module.require (node:internal/modules/cjs/loader:1105:19)
    at Module._preloadModules (node:internal/modules/cjs/loader:1395:12)
    at loadPreloadModules (node:internal/process/pre_execution:621:5)
    at setupUserModules (node:internal/process/pre_execution:125:3)
    at prepareExecution (node:internal/process/pre_execution:116:5)
    at prepareMainThreadExecution (node:internal/process/pre_execution:36:3)
    at node:internal/main/run_main_module:10:1 {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ 'internal/preload' ]
}

Node.js v18.13.0
$ npm install -g suppress-experimental-warnings

added 1 package in 534ms

1 package is looking for funding
  run `npm fund` for details
$ test2-cli
node:internal/modules/cjs/loader:1042
  throw err;
  ^

Error: Cannot find module 'suppress-experimental-warnings'
Require stack:
- internal/preload
    at Module._resolveFilename (node:internal/modules/cjs/loader:1039:15)
    at Module._load (node:internal/modules/cjs/loader:885:27)
    at Module.require (node:internal/modules/cjs/loader:1105:19)
    at Module._preloadModules (node:internal/modules/cjs/loader:1395:12)
    at loadPreloadModules (node:internal/process/pre_execution:621:5)
    at setupUserModules (node:internal/process/pre_execution:125:3)
    at prepareExecution (node:internal/process/pre_execution:116:5)
    at prepareMainThreadExecution (node:internal/process/pre_execution:36:3)
    at node:internal/main/run_main_module:10:1 {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ 'internal/preload' ]
}

Node.js v18.13.0
$ nvm ls
        v6.14.4
       v14.15.5
       v16.10.0
       v16.17.0
->     v18.13.0
        v19.5.0
default -> lts/* (-> v18.13.0)
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v19.5.0) (default)
stable -> 19.5 (-> v19.5.0) (default)
lts/* -> lts/hydrogen (-> v18.13.0)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.12 (-> N/A)
lts/fermium -> v14.21.2 (-> N/A)
lts/gallium -> v16.19.0 (-> N/A)
lts/hydrogen -> v18.13.0
$ npm prefix -g
/home/bjam/.nvm/versions/node/v18.13.0
$ ls -la /home/bjam/.nvm/versions/node/v18.13.0/lib/node_modules/ | grep suppress-
drwxr-xr-x  3 bjam bjam 4096 Mar 17 11:00 suppress-experimental-warnings

Solution

  • I found a way to resolve it by replacing experimental json import by readFileSync combined to URL and import.meta.url to resolve relative path from the project.

    #!/usr/bin/env -S node 
    
    import { readFileSync } from 'fs'
    
    const filePath = new URL('jsonFile.json', import.meta.url)
    const jsonContent = JSON.parse(readFileSync(filePath, 'utf-8'))
    console.log(jsonContent)
    

    With readFileSync, have to think than a relative path is relative to the current working directory (where the script is called) and not relative to the project (where the package.json is stored).

    Relative path to the current working directory :

    readFileSync('jsonFile.json', 'utf-8')
    

    Relative path to the current file :

    new URL('jsonFile.json', import.meta.url)