From b8239d211d32b6b2546ff07f0fa67fa63bee213e Mon Sep 17 00:00:00 2001 From: Andrea Giammarchi Date: Thu, 12 Oct 2017 18:43:23 -0300 Subject: [PATCH] Enable explicit `.m.js` intent for ESM As discussed in the [.mjs extension trade-offs](https://github.com/nodejs/node-eps/issues/57#issuecomment-336254657) post, it would be great if NodeJS could provide a way to explicitly opt-in as ESM, without needing to use a different extension. The purpose of this PR is to enable a universal convention for ESM that would work out of the box in both browsers and other JavaScript environment including SpiderMonkey and JSC. If the file is imported with a fully qualified path name, and such path name uses the `.m.js` convention, the format will be ESM and it will throw if such file does not respect such format. The current NodeJS diversion from the rest of the JavaScript environments is alarming for various reasons and consistency, as well as code reliability, is currently potentially compromised, as described in details in [this blog post](https://codeburst.io/the-javascript-modules-limbo-585eedbb182e). If there is anything else I could do in order to land this PR while ESM is still behind an experimental flag, please let me know, thank you. --- doc/api/esm.md | 5 ++++- lib/internal/loader/ModuleRequest.js | 3 ++- lib/module.js | 3 +++ test/es-module/test-es-m-basic-imports.mjs | 5 +++++ test/es-module/test-es-m-failing-require.js | 9 +++++++++ test/es-module/test-esm-ok.m.js | 5 +++++ 6 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 test/es-module/test-es-m-basic-imports.mjs create mode 100644 test/es-module/test-es-m-failing-require.js create mode 100644 test/es-module/test-esm-ok.m.js diff --git a/doc/api/esm.md b/doc/api/esm.md index bc25c88b9a82e9..03aa6c90a8fa99 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -19,11 +19,13 @@ and implementation is ready. Error messages are still being polished. The `--experimental-modules` flag can be used to enable features for loading ESM modules. -Once this has been set, files ending with `.mjs` will be able to be loaded +Once this has been set, files ending with `.mjs` or `.m.js` will be able to be loaded as ES Modules. ```sh node --experimental-modules my-app.mjs + +node --experimental-modules other-app.m.js ``` ## Features @@ -41,6 +43,7 @@ points into ESM graphs at run time. | Feature | Reason | | --- | --- | | `require('./foo.mjs')` | ES Modules have differing resolution and timing, use language standard `import()` | +| `require('./foo.m.js')` | same as above, use language standard `import()` | | `import()` | pending newer V8 release used in Node.js | | `import.meta` | pending V8 implementation | | Loader Hooks | pending Node.js EP creation/consensus | diff --git a/lib/internal/loader/ModuleRequest.js b/lib/internal/loader/ModuleRequest.js index 88e48ae9d3eaa1..9640cb1875336c 100644 --- a/lib/internal/loader/ModuleRequest.js +++ b/lib/internal/loader/ModuleRequest.js @@ -114,7 +114,8 @@ exports.resolve = (specifier, parentURL) => { case '.node': return { url: `${url}`, format: 'addon' }; case '.js': - return { url: `${url}`, format: 'cjs' }; + const format = url.pathname.slice(-5) === '.m.js' ? 'esm' : 'cjs'; + return { url: `${url}`, format }; default: throw new errors.Error('ERR_UNKNOWN_FILE_EXTENSION', internalURLModule.getPathFromURL(url)); diff --git a/lib/module.js b/lib/module.js index 73f3cc8dd8cde5..f2e58365b534f3 100644 --- a/lib/module.js +++ b/lib/module.js @@ -611,6 +611,9 @@ Module.prototype._compile = function(content, filename) { // Native extension for .js Module._extensions['.js'] = function(module, filename) { + if (experimentalModules && filename.slice(-5) === '.m.js') { + return Module._extensions['.mjs'](module, filename); + } var content = fs.readFileSync(filename, 'utf8'); module._compile(internalModule.stripBOM(content), filename); }; diff --git a/test/es-module/test-es-m-basic-imports.mjs b/test/es-module/test-es-m-basic-imports.mjs new file mode 100644 index 00000000000000..98ef35a313eccd --- /dev/null +++ b/test/es-module/test-es-m-basic-imports.mjs @@ -0,0 +1,5 @@ +// Flags: --experimental-modules +import assert from 'assert'; +import ok from './test-esm-ok.m.js'; + +assert(ok === true); diff --git a/test/es-module/test-es-m-failing-require.js b/test/es-module/test-es-m-failing-require.js new file mode 100644 index 00000000000000..b61c8a1a549b86 --- /dev/null +++ b/test/es-module/test-es-m-failing-require.js @@ -0,0 +1,9 @@ +// Flags: --experimental-modules +const assert = require('assert'); + +try { + require('./test-esm-ok.m.js'); + assert(false); +} catch(error) { + assert(/es\s*m(?:odule)?|\.m\.?js/i.test(error.message)); +} diff --git a/test/es-module/test-esm-ok.m.js b/test/es-module/test-esm-ok.m.js new file mode 100644 index 00000000000000..6712e1ab7dfca1 --- /dev/null +++ b/test/es-module/test-esm-ok.m.js @@ -0,0 +1,5 @@ +// Flags: --experimental-modules +/* eslint-disable required-modules */ + +const isJs = true; +export default isJs;