diff --git a/.eslintrc.js b/.eslintrc.js index 5eed8409d2..5987e8c654 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,6 +41,7 @@ module.exports = { 'test/es-module/test-esm-type-flag.js', 'test/es-module/test-esm-type-flag-alias.js', '*.mjs', + 'test/es-module/test-esm-example-loader.js', ], parserOptions: { sourceType: 'module' }, }, diff --git a/doc/api/cli.md b/doc/api/cli.md index de822b6a06..62e13496c0 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -268,6 +268,13 @@ default) is not firewall-protected.** See the [debugging security implications][] section for more information. +### `--loader=file` + + +Specify the `file` of the custom [experimental ECMAScript Module][] loader. + ### `--max-http-header-size=size` + +To customize the default module resolution, loader hooks can optionally be +provided via a `--loader ./loader-name.mjs` argument to Node.js. + +When hooks are used they only apply to ES module loading and not to any +CommonJS modules loaded. + +### Resolve hook + +The resolve hook returns the resolved file URL and module format for a +given module specifier and parent file URL: + +```js +const baseURL = new URL('file://'); +baseURL.pathname = `${process.cwd()}/`; + +export async function resolve(specifier, + parentModuleURL = baseURL, + defaultResolver) { + return { + url: new URL(specifier, parentModuleURL).href, + format: 'esm' + }; +} +``` + +The `parentModuleURL` is provided as `undefined` when performing main Node.js +load itself. + +The default Node.js ES module resolution function is provided as a third +argument to the resolver for easy compatibility workflows. + +In addition to returning the resolved file URL value, the resolve hook also +returns a `format` property specifying the module format of the resolved +module. This can be one of the following: + +| `format` | Description | +| --- | --- | +| `'module'` | Load a standard JavaScript module | +| `'commonjs'` | Load a Node.js CommonJS module | +| `'builtin'` | Load a Node.js builtin module | +| `'dynamic'` | Use a [dynamic instantiate hook][] | + +For example, a dummy loader to load JavaScript restricted to browser resolution +rules with only JS file extension and Node.js builtin modules support could +be written: + +```js +import path from 'path'; +import process from 'process'; +import Module from 'module'; + +const builtins = Module.builtinModules; +const JS_EXTENSIONS = new Set(['.js', '.mjs']); + +const baseURL = new URL('file://'); +baseURL.pathname = `${process.cwd()}/`; + +export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) { + if (builtins.includes(specifier)) { + return { + url: specifier, + format: 'builtin' + }; + } + if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { + // For node_modules support: + // return defaultResolve(specifier, parentModuleURL); + throw new Error( + `imports must begin with '/', './', or '../'; '${specifier}' does not`); + } + const resolved = new URL(specifier, parentModuleURL); + const ext = path.extname(resolved.pathname); + if (!JS_EXTENSIONS.has(ext)) { + throw new Error( + `Cannot load file with non-JavaScript file extension ${ext}.`); + } + return { + url: resolved.href, + format: 'esm' + }; +} +``` + +With this loader, running: + +```console +NODE_OPTIONS='--experimental-modules --loader ./custom-loader.mjs' node x.js +``` + +would load the module `x.js` as an ES module with relative resolution support +(with `node_modules` loading skipped in this example). + +### Dynamic instantiate hook + +To create a custom dynamic module that doesn't correspond to one of the +existing `format` interpretations, the `dynamicInstantiate` hook can be used. +This hook is called only for modules that return `format: 'dynamic'` from +the `resolve` hook. + +```js +export async function dynamicInstantiate(url) { + return { + exports: ['customExportName'], + execute: (exports) => { + // Get and set functions provided for pre-allocated export names + exports.customExportName.set('value'); + } + }; +} +``` + +With the list of module exports provided upfront, the `execute` function will +then be called at the exact point of module evaluation order for that module +in the import tree. + [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md +[dynamic instantiate hook]: #esm_dynamic_instantiate_hook [`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename [ESM Minimal Kernel]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index e405dc8b30..803c854d9a 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -7,6 +7,7 @@ const { ERR_INVALID_TYPE_FLAG, ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, } = require('internal/errors').codes; +const { emitExperimentalWarning } = require('internal/util'); const type = require('internal/options').getOptionValue('--type'); if (type && type !== 'commonjs' && type !== 'module') @@ -14,6 +15,7 @@ if (type && type !== 'commonjs' && type !== 'module') exports.typeFlag = type; const { Loader } = require('internal/modules/esm/loader'); +const { pathToFileURL } = require('internal/url'); const { wrapToModuleMap, } = require('internal/vm/source_text_module'); @@ -44,8 +46,16 @@ exports.loaderPromise = new Promise((resolve) => loaderResolve = resolve); exports.ESMLoader = undefined; exports.initializeLoader = function(cwd, userLoader) { - const ESMLoader = new Loader(); + let ESMLoader = new Loader(); const loaderPromise = (async () => { + if (userLoader) { + emitExperimentalWarning('--loader'); + const hooks = await ESMLoader.import( + userLoader, pathToFileURL(`${cwd}/`).href); + ESMLoader = new Loader(); + ESMLoader.hook(hooks); + exports.ESMLoader = ESMLoader; + } return ESMLoader; })(); loaderResolve(loaderPromise); diff --git a/src/node_options.cc b/src/node_options.cc index e76e1a0282..413169fde4 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -101,6 +101,10 @@ void PerIsolateOptions::CheckOptions(std::vector* errors) { } void EnvironmentOptions::CheckOptions(std::vector* errors) { + if (!userland_loader.empty() && !experimental_modules) { + errors->push_back("--loader requires --experimental-modules be enabled"); + } + if (syntax_check_only && has_eval_string) { errors->push_back("either --check or --eval can be used, not both"); } @@ -242,6 +246,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "(default: llhttp).", &EnvironmentOptions::http_parser, kAllowedInEnvironment); + AddOption("--loader", + "(with --experimental-modules) use the specified file as a " + "custom loader", + &EnvironmentOptions::userland_loader, + kAllowedInEnvironment); AddOption("--no-deprecation", "silence deprecation warnings", &EnvironmentOptions::no_deprecation, diff --git a/src/node_options.h b/src/node_options.h index ee4cc98c9b..83c3be8674 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -105,6 +105,7 @@ class EnvironmentOptions : public Options { bool trace_deprecation = false; bool trace_sync_io = false; bool trace_warnings = false; + std::string userland_loader; bool syntax_check_only = false; bool has_eval_string = false; diff --git a/test/es-module/test-esm-example-loader.js b/test/es-module/test-esm-example-loader.js new file mode 100644 index 0000000000..0b0001acea --- /dev/null +++ b/test/es-module/test-esm-example-loader.js @@ -0,0 +1,6 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/example-loader.mjs +/* eslint-disable node-core/required-modules */ +import assert from 'assert'; +import ok from '../fixtures/es-modules/test-esm-ok.mjs'; + +assert(ok); diff --git a/test/es-module/test-esm-loader-dependency.mjs b/test/es-module/test-esm-loader-dependency.mjs new file mode 100644 index 0000000000..1ed8685a6f --- /dev/null +++ b/test/es-module/test-esm-loader-dependency.mjs @@ -0,0 +1,5 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/loader-with-dep.mjs +/* eslint-disable node-core/required-modules */ +import '../fixtures/es-modules/test-esm-ok.mjs'; + +// We just test that this module doesn't fail loading diff --git a/test/es-module/test-esm-loader-invalid-format.mjs b/test/es-module/test-esm-loader-invalid-format.mjs new file mode 100644 index 0000000000..c3f3a87407 --- /dev/null +++ b/test/es-module/test-esm-loader-invalid-format.mjs @@ -0,0 +1,12 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/loader-invalid-format.mjs +/* eslint-disable node-core/required-modules */ +import { expectsError, mustCall } from '../common/index.mjs'; +import assert from 'assert'; + +import('../fixtures/es-modules/test-esm-ok.mjs') +.then(assert.fail, expectsError({ + code: 'ERR_INVALID_RETURN_PROPERTY_VALUE', + message: 'Expected string to be returned for the "format" from the ' + + '"loader resolve" function but got type undefined.' +})) +.then(mustCall()); diff --git a/test/es-module/test-esm-loader-invalid-url.mjs b/test/es-module/test-esm-loader-invalid-url.mjs new file mode 100644 index 0000000000..9cf17b2478 --- /dev/null +++ b/test/es-module/test-esm-loader-invalid-url.mjs @@ -0,0 +1,14 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/loader-invalid-url.mjs +/* eslint-disable node-core/required-modules */ + +import { expectsError, mustCall } from '../common/index.mjs'; +import assert from 'assert'; + +import('../fixtures/es-modules/test-esm-ok.mjs') +.then(assert.fail, expectsError({ + code: 'ERR_INVALID_RETURN_PROPERTY', + message: 'Expected a valid url to be returned for the "url" from the ' + + '"loader resolve" function but got ' + + '../fixtures/es-modules/test-esm-ok.mjs.' +})) +.then(mustCall()); diff --git a/test/es-module/test-esm-loader-missing-dynamic-instantiate-hook.mjs b/test/es-module/test-esm-loader-missing-dynamic-instantiate-hook.mjs new file mode 100644 index 0000000000..ab2da7adce --- /dev/null +++ b/test/es-module/test-esm-loader-missing-dynamic-instantiate-hook.mjs @@ -0,0 +1,10 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs +/* eslint-disable node-core/required-modules */ + +import { expectsError } from '../common/index.mjs'; + +import('test').catch(expectsError({ + code: 'ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK', + message: 'The ES Module loader may not return a format of \'dynamic\' ' + + 'when no dynamicInstantiate function was provided' +})); diff --git a/test/es-module/test-esm-named-exports.mjs b/test/es-module/test-esm-named-exports.mjs new file mode 100644 index 0000000000..e235f598cb --- /dev/null +++ b/test/es-module/test-esm-named-exports.mjs @@ -0,0 +1,9 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs +/* eslint-disable node-core/required-modules */ +import '../common/index.mjs'; +import { readFile } from 'fs'; +import assert from 'assert'; +import ok from '../fixtures/es-modules/test-esm-ok.mjs'; + +assert(ok); +assert(readFile); diff --git a/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs b/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs new file mode 100644 index 0000000000..2ca0f56581 --- /dev/null +++ b/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs @@ -0,0 +1,3 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/not-found-assert-loader.mjs +/* eslint-disable node-core/required-modules */ +import './not-found.js'; diff --git a/test/es-module/test-esm-preserve-symlinks-not-found.mjs b/test/es-module/test-esm-preserve-symlinks-not-found.mjs new file mode 100644 index 0000000000..b5be2d7e63 --- /dev/null +++ b/test/es-module/test-esm-preserve-symlinks-not-found.mjs @@ -0,0 +1,3 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/not-found-assert-loader.mjs +/* eslint-disable node-core/required-modules */ +import './not-found.mjs'; diff --git a/test/es-module/test-esm-resolve-hook.mjs b/test/es-module/test-esm-resolve-hook.mjs new file mode 100644 index 0000000000..e326d20b6d --- /dev/null +++ b/test/es-module/test-esm-resolve-hook.mjs @@ -0,0 +1,8 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/js-loader.mjs +/* eslint-disable node-core/required-modules */ +import { namedExport } from '../fixtures/es-module-loaders/js-as-esm.js'; +import assert from 'assert'; +import ok from '../fixtures/es-modules/test-esm-ok.mjs'; + +assert(ok); +assert(namedExport); diff --git a/test/es-module/test-esm-shared-loader-dep.mjs b/test/es-module/test-esm-shared-loader-dep.mjs new file mode 100644 index 0000000000..b8953ab1ec --- /dev/null +++ b/test/es-module/test-esm-shared-loader-dep.mjs @@ -0,0 +1,11 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/loader-shared-dep.mjs +/* eslint-disable node-core/required-modules */ +import { createRequire } from '../common/index.mjs'; + +import assert from 'assert'; +import '../fixtures/es-modules/test-esm-ok.mjs'; + +const require = createRequire(import.meta.url); +const dep = require('../fixtures/es-module-loaders/loader-dep.js'); + +assert.strictEqual(dep.format, 'module'); diff --git a/test/es-module/test-esm-throw-undefined.mjs b/test/es-module/test-esm-throw-undefined.mjs new file mode 100644 index 0000000000..97e917da5e --- /dev/null +++ b/test/es-module/test-esm-throw-undefined.mjs @@ -0,0 +1,16 @@ +// Flags: --experimental-modules +/* eslint-disable node-core/required-modules */ + +import '../common/index.mjs'; +import assert from 'assert'; + +async function doTest() { + await assert.rejects( + async () => { + await import('../fixtures/es-module-loaders/throw-undefined.mjs'); + }, + (e) => e === undefined + ); +} + +doTest(); diff --git a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs new file mode 100644 index 0000000000..a944c4fd5e --- /dev/null +++ b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs @@ -0,0 +1,24 @@ +import module from 'module'; + +export function dynamicInstantiate(url) { + const builtinInstance = module._load(url.substr(5)); + const builtinExports = ['default', ...Object.keys(builtinInstance)]; + return { + exports: builtinExports, + execute: exports => { + for (let name of builtinExports) + exports[name].set(builtinInstance[name]); + exports.default.set(builtinInstance); + } + }; +} + +export function resolve(specifier, base, defaultResolver) { + if (module.builtinModules.includes(specifier)) { + return { + url: `node:${specifier}`, + format: 'dynamic' + }; + } + return defaultResolver(specifier, base); +} diff --git a/test/fixtures/es-module-loaders/example-loader.mjs b/test/fixtures/es-module-loaders/example-loader.mjs new file mode 100644 index 0000000000..d8e0ddcba3 --- /dev/null +++ b/test/fixtures/es-module-loaders/example-loader.mjs @@ -0,0 +1,34 @@ +import url from 'url'; +import path from 'path'; +import process from 'process'; +import { builtinModules } from 'module'; + +const JS_EXTENSIONS = new Set(['.js', '.mjs']); + +const baseURL = new url.URL('file://'); +baseURL.pathname = process.cwd() + '/'; + +export function resolve(specifier, parentModuleURL = baseURL /*, defaultResolve */) { + if (builtinModules.includes(specifier)) { + return { + url: specifier, + format: 'builtin' + }; + } + if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { + // For node_modules support: + // return defaultResolve(specifier, parentModuleURL); + throw new Error( + `imports must begin with '/', './', or '../'; '${specifier}' does not`); + } + const resolved = new url.URL(specifier, parentModuleURL); + const ext = path.extname(resolved.pathname); + if (!JS_EXTENSIONS.has(ext)) { + throw new Error( + `Cannot load file with non-JavaScript file extension ${ext}.`); + } + return { + url: resolved.href, + format: 'module' + }; +} diff --git a/test/fixtures/es-module-loaders/js-as-esm.js b/test/fixtures/es-module-loaders/js-as-esm.js new file mode 100644 index 0000000000..b4d2741b2f --- /dev/null +++ b/test/fixtures/es-module-loaders/js-as-esm.js @@ -0,0 +1 @@ +export const namedExport = 'named-export'; diff --git a/test/fixtures/es-module-loaders/js-loader.mjs b/test/fixtures/es-module-loaders/js-loader.mjs new file mode 100644 index 0000000000..4b8a0fc365 --- /dev/null +++ b/test/fixtures/es-module-loaders/js-loader.mjs @@ -0,0 +1,20 @@ +import { URL } from 'url'; +import { builtinModules } from 'module'; + +const baseURL = new URL('file://'); +baseURL.pathname = process.cwd() + '/'; + +export function resolve (specifier, base = baseURL) { + if (builtinModules.includes(specifier)) { + return { + url: specifier, + format: 'builtin' + }; + } + // load all dependencies as esm, regardless of file extension + const url = new URL(specifier, base).href; + return { + url, + format: 'module' + }; +} diff --git a/test/fixtures/es-module-loaders/loader-dep.js b/test/fixtures/es-module-loaders/loader-dep.js new file mode 100644 index 0000000000..c8154ac5db --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-dep.js @@ -0,0 +1 @@ +exports.format = 'module'; diff --git a/test/fixtures/es-module-loaders/loader-invalid-format.mjs b/test/fixtures/es-module-loaders/loader-invalid-format.mjs new file mode 100644 index 0000000000..17a0dcd04d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-invalid-format.mjs @@ -0,0 +1,8 @@ +export async function resolve(specifier, parentModuleURL, defaultResolve) { + if (parentModuleURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { + return { + url: 'file:///asdf' + }; + } + return defaultResolve(specifier, parentModuleURL); +} diff --git a/test/fixtures/es-module-loaders/loader-invalid-url.mjs b/test/fixtures/es-module-loaders/loader-invalid-url.mjs new file mode 100644 index 0000000000..f653155899 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-invalid-url.mjs @@ -0,0 +1,10 @@ +/* eslint-disable node-core/required-modules */ +export async function resolve(specifier, parentModuleURL, defaultResolve) { + if (parentModuleURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { + return { + url: specifier, + format: 'esm' + }; + } + return defaultResolve(specifier, parentModuleURL); +} diff --git a/test/fixtures/es-module-loaders/loader-shared-dep.mjs b/test/fixtures/es-module-loaders/loader-shared-dep.mjs new file mode 100644 index 0000000000..3acafcce1e --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-shared-dep.mjs @@ -0,0 +1,11 @@ +import assert from 'assert'; + +import {createRequire} from '../../common/index.mjs'; + +const require = createRequire(import.meta.url); +const dep = require('./loader-dep.js'); + +export function resolve(specifier, base, defaultResolve) { + assert.strictEqual(dep.format, 'module'); + return defaultResolve(specifier, base); +} diff --git a/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs b/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs new file mode 100644 index 0000000000..e7c6c8ff34 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs @@ -0,0 +1,6 @@ +export async function resolve(specifier, parent, defaultResolve) { + if (specifier === 'unknown-builtin-module') { + return { url: 'unknown-builtin-module', format: 'builtin' }; + } + return defaultResolve(specifier, parent); +} \ No newline at end of file diff --git a/test/fixtures/es-module-loaders/loader-with-dep.mjs b/test/fixtures/es-module-loaders/loader-with-dep.mjs new file mode 100644 index 0000000000..5afd3b2e21 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-with-dep.mjs @@ -0,0 +1,11 @@ +import {createRequire} from '../../common/index.mjs'; + +const require = createRequire(import.meta.url); +const dep = require('./loader-dep.js'); + +export function resolve (specifier, base, defaultResolve) { + return { + url: defaultResolve(specifier, base).url, + format: dep.format + }; +} diff --git a/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs b/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs new file mode 100644 index 0000000000..6993747fcc --- /dev/null +++ b/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs @@ -0,0 +1,6 @@ +export function resolve(specifier, parentModule, defaultResolver) { + if (specifier !== 'test') { + return defaultResolver(specifier, parentModule); + } + return { url: 'file://', format: 'dynamic' }; +} diff --git a/test/fixtures/es-module-loaders/module-named-exports.mjs b/test/fixtures/es-module-loaders/module-named-exports.mjs new file mode 100644 index 0000000000..04f7f43ebd --- /dev/null +++ b/test/fixtures/es-module-loaders/module-named-exports.mjs @@ -0,0 +1,2 @@ +export const foo = 'foo'; +export const bar = 'bar'; diff --git a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs new file mode 100644 index 0000000000..d3eebcd47e --- /dev/null +++ b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs @@ -0,0 +1,22 @@ +import assert from 'assert'; + +// a loader that asserts that the defaultResolve will throw "not found" +// (skipping the top-level main of course) +let mainLoad = true; +export async function resolve (specifier, base, defaultResolve) { + if (mainLoad) { + mainLoad = false; + return defaultResolve(specifier, base); + } + try { + await defaultResolve(specifier, base); + } + catch (e) { + assert.strictEqual(e.code, 'ERR_MODULE_NOT_FOUND'); + return { + format: 'builtin', + url: 'fs' + }; + } + assert.fail(`Module resolution for ${specifier} should be throw ERR_MODULE_NOT_FOUND`); +} diff --git a/test/fixtures/es-module-loaders/syntax-error-import.mjs b/test/fixtures/es-module-loaders/syntax-error-import.mjs new file mode 100644 index 0000000000..3a6bc5effc --- /dev/null +++ b/test/fixtures/es-module-loaders/syntax-error-import.mjs @@ -0,0 +1 @@ +import { foo, notfound } from './module-named-exports.mjs'; diff --git a/test/fixtures/es-module-loaders/syntax-error.mjs b/test/fixtures/es-module-loaders/syntax-error.mjs new file mode 100644 index 0000000000..bda4a7e6eb --- /dev/null +++ b/test/fixtures/es-module-loaders/syntax-error.mjs @@ -0,0 +1,2 @@ +'use strict'; +await async () => 0; diff --git a/test/fixtures/es-module-loaders/throw-undefined.mjs b/test/fixtures/es-module-loaders/throw-undefined.mjs new file mode 100644 index 0000000000..0349ae112d --- /dev/null +++ b/test/fixtures/es-module-loaders/throw-undefined.mjs @@ -0,0 +1,4 @@ +'use strict'; +/* eslint-disable node-core/required-modules */ + +throw undefined; diff --git a/test/message/esm_display_syntax_error.out b/test/message/esm_display_syntax_error.out index 2700fd894c..8f17d5cd7a 100644 --- a/test/message/esm_display_syntax_error.out +++ b/test/message/esm_display_syntax_error.out @@ -2,5 +2,6 @@ file:///*/test/message/esm_display_syntax_error.mjs:3 await async () => 0; ^^^^^ + SyntaxError: Unexpected reserved word at Loader. (internal/modules/esm/translators.js:*:*) diff --git a/test/message/esm_display_syntax_error_import.mjs b/test/message/esm_display_syntax_error_import.mjs new file mode 100644 index 0000000000..12d10270e9 --- /dev/null +++ b/test/message/esm_display_syntax_error_import.mjs @@ -0,0 +1,7 @@ +// Flags: --experimental-modules +/* eslint-disable no-unused-vars, node-core/required-modules */ +import '../common/index.mjs'; +import { + foo, + notfound +} from '../fixtures/es-module-loaders/module-named-exports.mjs'; diff --git a/test/message/esm_display_syntax_error_import.out b/test/message/esm_display_syntax_error_import.out new file mode 100644 index 0000000000..48f2e2fb74 --- /dev/null +++ b/test/message/esm_display_syntax_error_import.out @@ -0,0 +1,6 @@ +(node:*) ExperimentalWarning: The ESM module loader is experimental. +file:///*/test/message/esm_display_syntax_error_import.mjs:6 + notfound + ^^^^^^^^ +SyntaxError: The requested module '../fixtures/es-module-loaders/module-named-exports.mjs' does not provide an export named 'notfound' + at ModuleJob._instantiate (internal/modules/esm/module_job.js:*:*) diff --git a/test/message/esm_display_syntax_error_import_module.mjs b/test/message/esm_display_syntax_error_import_module.mjs new file mode 100644 index 0000000000..a53bbbcd19 --- /dev/null +++ b/test/message/esm_display_syntax_error_import_module.mjs @@ -0,0 +1,4 @@ +// Flags: --experimental-modules +/* eslint-disable node-core/required-modules */ +import '../common/index.mjs'; +import '../fixtures/es-module-loaders/syntax-error-import.mjs'; diff --git a/test/message/esm_display_syntax_error_import_module.out b/test/message/esm_display_syntax_error_import_module.out new file mode 100644 index 0000000000..3e1024db8a --- /dev/null +++ b/test/message/esm_display_syntax_error_import_module.out @@ -0,0 +1,6 @@ +(node:*) ExperimentalWarning: The ESM module loader is experimental. +file:///*/test/fixtures/es-module-loaders/syntax-error-import.mjs:1 +import { foo, notfound } from './module-named-exports.mjs'; + ^^^^^^^^ +SyntaxError: The requested module './module-named-exports.mjs' does not provide an export named 'notfound' + at ModuleJob._instantiate (internal/modules/esm/module_job.js:*:*) diff --git a/test/message/esm_display_syntax_error_module.mjs b/test/message/esm_display_syntax_error_module.mjs new file mode 100644 index 0000000000..5905d2a954 --- /dev/null +++ b/test/message/esm_display_syntax_error_module.mjs @@ -0,0 +1,4 @@ +// Flags: --experimental-modules +/* eslint-disable node-core/required-modules */ +import '../common/index.mjs'; +import '../fixtures/es-module-loaders/syntax-error.mjs'; diff --git a/test/message/esm_display_syntax_error_module.out b/test/message/esm_display_syntax_error_module.out new file mode 100644 index 0000000000..7dbbd32f1f --- /dev/null +++ b/test/message/esm_display_syntax_error_module.out @@ -0,0 +1,7 @@ +(node:*) ExperimentalWarning: The ESM module loader is experimental. +file:///*/test/fixtures/es-module-loaders/syntax-error.mjs:2 +await async () => 0; +^^^^^ + +SyntaxError: Unexpected reserved word + at Loader. (internal/modules/esm/translators.js:*:*) diff --git a/test/parallel/test-loaders-unknown-builtin-module.mjs b/test/parallel/test-loaders-unknown-builtin-module.mjs new file mode 100644 index 0000000000..5f47f191f5 --- /dev/null +++ b/test/parallel/test-loaders-unknown-builtin-module.mjs @@ -0,0 +1,13 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs +/* eslint-disable node-core/required-modules */ +import { expectsError, mustCall } from '../common/index.mjs'; +import assert from 'assert'; + +const unknownBuiltinModule = 'unknown-builtin-module'; + +import(unknownBuiltinModule) +.then(assert.fail, expectsError({ + code: 'ERR_UNKNOWN_BUILTIN_MODULE', + message: `No such built-in module: ${unknownBuiltinModule}` +})) +.then(mustCall());