-
Notifications
You must be signed in to change notification settings - Fork 45
Proposal: --default-type #335
Description
This is a proposal to provide a way to change Node’s default module system from CommonJS to ESM (#318).
There are two places where the module system can be specified, with a lack of specificity defaulting to CommonJS:
-
package.json"type"field, where the lack of the field is interpreted as"commonjs". -
--input-type, where the lack of the flag is interpreted ascommonjs.
We propose a new flag --default-type, to control the default values of package.json "type" and --input-type:
-
--default-type=commonjsis the same as the current behavior, where the lack of the flag or field is interpreted as CommonJS. -
--default-type=modulewould cause the lack of the flag or field to be interpreted as ESM.
The expectation is that users who prefer ESM to be their default would set this flag in their NODE_OPTIONS, e.g. NODE_OPTIONS=--default-type=module.
With the flag set to module, some potential use cases are:
-
Commands like
node --eval 'import { version } from "process"; console.log(version)'could be run without needing--input-type=module. -
Extensionless files to be run like shell scripts could use ESM syntax, without needing to be symlinks to
.mjsfiles or use other workarounds.
Under --default-type=module, most typical projects with dozens of CommonJS dependencies would break. Most, if not all, of those dependencies would lack "type": "commonjs" in their package.json files, at least for now while public awareness of the new field grows. One potential solution is a script that users could run to add the field for any dependencies that lack it:
#!/usr/bin/env node
const { argv, exit } = require('process');
const { basename, resolve } = require('path');
const { readdir, open } = require('fs').promises;
if (argv.length !== 3 || argv[2] === '-h' || argv[2] === '--help') {
const scriptCommand = basename(argv[1]);
console.log(`This script searches a folder and its subfolders for package.json files,
adding "type": "commonjs" to any such files that lack a "type" field.
Usage: ${scriptCommand} folder
Example: ${scriptCommand} ./node_modules`);
exit(1);
} else {
walk(resolve(argv[2])).catch(console.error);
}
async function walk(folder) {
const entries = await readdir(folder, {withFileTypes: true});
await Promise.all(entries.map(async (entry) => {
if (entry.isDirectory()) {
return walk(resolve(folder, entry.name));
} else if (entry.name === 'package.json') {
const resolvedFilePath = resolve(folder, entry.name);
let handle;
try {
handle = await open(resolvedFilePath, 'r+');
const manifest = JSON.parse(await handle.readFile('utf-8'));
if (manifest.type === undefined) {
manifest.type = 'commonjs';
await handle.writeFile(JSON.stringify(manifest, null, 2));
}
} catch (exception) {
console.error(`Error updating ${resolvedFilePath}:`, exception);
} finally {
if (handle) {
await handle.close();
}
}
}
}));
}We expect that package managers might soon begin to add "type": "commonjs" automatically for dependencies that lack a "type" field. If they don’t, and no other userland solution finds broad acceptance, another solution is for Node to add a third option to --default-type, namely --default-type=module-except-dependencies. This would behave the same as --default-type=module except that it wouldn’t apply to folders under a node_modules folder. We would prefer not to need to implement this, however, as a “pure” solution where package managers or userland utilities bridge the gap would let Node provide a more ideal API. This option can always be added later if it turns out to be necessary.