Repository: node
Issue: Discussion: New “ESM by default” mode
Labels: module,cli,esm,wasm,wasi

Building off of https://github.com/nodejs/node/pull/49295#issuecomment-1699430613, we’re considering a new mode where all of the current places where Node defaults to CommonJS would instead default to ESM. I think this should be enabled by flag, and once it ships we can potentially ship a [separate binary](https://github.com/nodejs/node/pull/49407) where it’s enabled by default; and/or we can someday make the new mode Node’s default in a semver-major change.

The use cases solved by such a mode are (and I’ll edit this comment to update this list as people think of more):

- I want to write ESM syntax by default without needing to opt into it, such as via `package.json` file or file extension or `--input-type=module`.

- I want to write what are typically called shell scripts in ESM JavaScript, where I can have a single [extensionless](https://github.com/nodejs/node/issues/49431) file anywhere on my disk that uses ESM syntax and it doesn’t need a nearby `package.json` file or a symlink or wrapper file in order to run as ESM.

- I want consistency in how the CLI handles references to files; currently `--import` accepts URL strings but the main entry point must be a path string (and therefore can’t be a `data:` URL or `https:` URL, such as to work with `--experimental-network-imports`).

- If this mode becomes enabled by default in a future version of Node, when using that version of Node I want to be able to start a new project and write ESM syntax without needing to take any steps to opt into enabling ESM support.

To handle these use cases, the changes that this mode should make are (and likewise I’ll edit this to reflect consensus):

- When a `.js` file is outside of any defined `package.json` scope, because there are no `package.json` files in its folder or any folders above it, it will be treated as ESM JavaScript.

- When a `.js` file is in a package scope where its nearest parent `package.json` lacks a `"type"` field, it will be interpreted per the conditions we establish in https://github.com/nodejs/node/issues/49494: when the package scope is under a folder named `node_modules`, the file will be treated as CommonJS; otherwise it will be treated as ESM.

- When an extensionless file is outside of any defined `package.json` scope, it can be run as an entry point or imported per the conditions we establish in https://github.com/nodejs/node/issues/49431: if the file begins with the Wasm magic bytes, it will be evaluated as Wasm, and likewise for any other future file types with defined headers that don’t conflict with JavaScript; otherwise it will be evaluated as ESM JavaScript. (Still subject to `--experimental-wasm-modules` until that stabilizes.)

- Input from STDIN or `--eval` would be treated as ESM unless `--input-type=commonjs` is passed. Or in other words, the default value of `--input-type` would flip from `commonjs` to `module`.

- The CLI will parse the main entry point as an URL string, similar to how the value of `--import` is parsed. `process.argv[1]` will not be parsed by the CommonJS loader.

This new flag will be named per the discussion in https://github.com/nodejs/node/issues/49541.

Prior art: https://github.com/nodejs/node/pull/32394, https://github.com/nodejs/node/pull/49295, https://github.com/nodejs/node/pull/49407

@LiviaMedeiros @nodejs/loaders @nodejs/wasi @nodejs/tsc