Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command line argument to force launch node.js file in "ES modules" mode, regardless of extension #41136

Closed
MurzNN opened this issue Dec 10, 2021 · 5 comments
Labels
duplicate Issues and PRs that are duplicates of other issues or PRs. esm Issues and PRs related to the ECMAScript Modules implementation. feature request Issues that request new features to be added to Node.js.

Comments

@MurzNN
Copy link

MurzNN commented Dec 10, 2021

Is your feature request related to a problem? Please describe.
I have a command line script file, in which I want to use "ES modules" features like top-level await and import construction.
Let's create the executable readdir.mjs script file (using shebang) with this content:

#!/usr/bin/env node

import fs from 'fs';
import util from 'util';
const readdir = util.promisify(fs.readdir);
let result = await readdir('.');
console.log(result);

And launch it via ./readdir.mjs console command - it works well, output is:

[ 'readdir.mjs' ]

Let's remove the "strange for regular scripts" .mjs extension via renaming this file to simply readdir, and launch it via ./readdir - we will got an error:

$ ./readdir 
(node:70589) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/home/user/nodetest/readdir:3
import fs from 'fs';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:1001:16)
    at Module._compile (internal/modules/cjs/loader.js:1049:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
    at internal/main/run_main_module.js:17:47

Describe the solution you'd like
We need to have an easy way to use ES modules features in node.js script files regardless of it's extension, like any other language allows!
So it would be good to add some command line argument like node --type=module, that will parse the current file as an ES modules type.

@MurzNN MurzNN added the feature request Issues that request new features to be added to Node.js. label Dec 10, 2021
@MurzNN
Copy link
Author

MurzNN commented Dec 10, 2021

There is a workaround to set type=module in package.json file, but even with this file it not working well without extension!

Let's create the readdir script file (without extension) and package.json file with content like this:

{
  "name": "scripts",
  "version": "1.0.0",
  "main": "readdir",
  "license": "MIT",
  "type": "module"
}

On launch we got an error:

$ ./readdir 
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /home/user/nodetest/readdir
    at new NodeError (internal/errors.js:322:7)
    at Loader.defaultGetFormat [as _getFormat] (internal/modules/esm/get_format.js:71:15)
    at Loader.getFormat (internal/modules/esm/loader.js:105:42)
    at Loader.getModuleJob (internal/modules/esm/loader.js:243:31)
    at async Loader.import (internal/modules/esm/loader.js:177:17)
    at async Object.loadESM (internal/process/esm_loader.js:68:5) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

If we try to rename readdir file to readdir.js - it starts working:

$ ./readdir.js
[ 'package.json', 'readdir.js' ]

But I need to have my readdir script without strange .mjs extension and as a single file, without redundant package.json file!

@MurzNN
Copy link
Author

MurzNN commented Dec 10, 2021

Because very often in projects we have a scripts folder, that contain mix of interchangeable scripts in different languages (node.js, python, bash, perl, etc), in which correct interpreter is set using shebang (#!/usr/bin/env bash, etc).

So if we force set the .js extension to a node.js script, it would be confusing to replace it with a version in python without renaming the file.
But if we rename myscript.js to myscript - we should also rename it in all depending script, that calls that script, that is a pain!

@aduh95
Copy link
Contributor

aduh95 commented Dec 10, 2021

This is being discussed in #37857. Your current options for an extensionless node executable file are:

  • Make an extensionless file a simple proxy to the actual .mjs script:
    #!/usr/bin/env node
    import('./myscript.mjs').catch((err) => { console.error(err); process.exit(1) })
  • Use an experimental loader that forces the resolution of extensionless files as ESM.

Closing as duplicate.

@aduh95 aduh95 closed this as completed Dec 10, 2021
@aduh95 aduh95 added duplicate Issues and PRs that are duplicates of other issues or PRs. esm Issues and PRs related to the ECMAScript Modules implementation. labels Dec 10, 2021
@MurzNN
Copy link
Author

MurzNN commented Dec 11, 2021

@aduh95 Thank you for the explanation, can you please tell me more about Use an experimental loader that forces the resolution of extensionless files as ESM? Can't find what exactly I must do for this...

Do you mean the --experimental-loader option, it should be enough to use in shebang of ./readdir extensionless script?

#!/usr/bin/env -S node --experimental-loader

But seem it needs Node v17 and even don't work in it:

$ docker run -it --rm --name example -v "$PWD":/usr/src/app -w /usr/src/app node:17 node --experimental-loader ./readdir
(node:1) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/errors:464
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /usr/src/app/readdir
    at new NodeError (node:internal/errors:371:5)
    at Object.file: (node:internal/modules/esm/get_format:72:15)
    at defaultGetFormat (node:internal/modules/esm/get_format:85:38)
    at defaultLoad (node:internal/modules/esm/load:22:14)
    at ESMLoader.load (node:internal/modules/esm/loader:353:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:274:58)
    at new ModuleJob (node:internal/modules/esm/module_job:66:26)
    at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:291:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:255:34)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

Node.js v17.2.0

And "Make an extensionless file a simple proxy to the actual .mjs script" is not the way, because I need to have only the one single file with name "readdir", not the bundle of files (readdir as wrapper and readdir.mjs as main file, or package.json additionally).

@aduh95
Copy link
Contributor

aduh95 commented Dec 11, 2021

@aduh95 Thank you for the explanation, can you please tell me more about Use an experimental loader that forces the resolution of extensionless files as ESM? Can't find what exactly I must do for this...

I mean you can provide a loader to customize the behavior of Node.js. You could use a loader that forces the entry point to be parsed as ESM no matter its extension:

let entrypoint = true;
export async function load(url, context, defaultLoad) {
  if (entrypoint) {
    context.format = "module";
    entrypoint = false;
  }
  return defaultLoad(url, context, defaultLoad);
}
node --experimental-loader 'data:text/javascript,let%20e=true;export%20function%20load(t,o,r)%7Bif(e)%7Bo.format=%22module%22;e=false%7Dreturn%20r(t,o,r)%7D' readdir

Note that this is still experimental, I wouldn't recommend using it for something meant for production. Support on older version of Node.js may vary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate Issues and PRs that are duplicates of other issues or PRs. esm Issues and PRs related to the ECMAScript Modules implementation. feature request Issues that request new features to be added to Node.js.
Projects
None yet
Development

No branches or pull requests

2 participants