mirror of
https://github.com/nodejs/node.git
synced 2025-05-06 22:56:06 +00:00

This PR updates the current `--experimental-modules` implementation based on the work of the modules team and reflects Phase 2 of our new modules plan. The largest differences from the current implementation include * `packge.type` which can be either `module` or `commonjs` - `type: "commonjs"`: - `.js` is parsed as commonjs - default for entry point without an extension is commonjs - `type: "module"`: - `.js` is parsed as esm - does not support loading JSON or Native Module by default - default for entry point without an extension is esm * `--entry-type=[mode]` - allows you set the type on entry point. * A new file extension `.cjs`. - this is specifically to support importing commonjs in the `module` mode. - this is only in the esm loader, the commonjs loader remains untouched, but the extension will work in the old loader if you use the full file path. * `--es-module-specifier-resolution=[type]` - options are `explicit` (default) and `node` - by default our loader will not allow for optional extensions in the import, the path for a module must include the extension if there is one - by default our loader will not allow for importing directories that have an index file - developers can use `--es-module-specifier-resolution=node` to enable the commonjs specifier resolution algorithm - This is not a “feature” but rather an implementation for experimentation. It is expected to change before the flag is removed * `--experimental-json-loader` - the only way to import json when `"type": "module"` - when enable all `import 'thing.json'` will go through the experimental loader independent of mode - based on https://github.com/whatwg/html/issues/4315 * You can use `package.main` to set an entry point for a module - the file extensions used in main will be resolved based on the `type` of the module Refs: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md Refs: https://github.com/GeoffreyBooth/node-import-file-specifier-resolution-proposal Refs: https://github.com/nodejs/modules/pull/180 Refs: https://github.com/nodejs/ecmascript-modules/pull/6 Refs: https://github.com/nodejs/ecmascript-modules/pull/12 Refs: https://github.com/nodejs/ecmascript-modules/pull/28 Refs: https://github.com/nodejs/modules/issues/255 Refs: https://github.com/whatwg/html/issues/4315 Refs: https://github.com/w3c/webcomponents/issues/770 Co-authored-by: Myles Borins <MylesBorins@google.com> Co-authored-by: John-David Dalton <john.david.dalton@gmail.com> Co-authored-by: Evan Plaice <evanplaice@gmail.com> Co-authored-by: Geoffrey Booth <webmaster@geoffreybooth.com> Co-authored-by: Michaël Zasso <targos@protonmail.com> PR-URL: https://github.com/nodejs/node/pull/26745 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Ben Coe <bencoe@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
170 lines
5.4 KiB
JavaScript
170 lines
5.4 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ERR_INVALID_RETURN_PROPERTY,
|
|
ERR_INVALID_RETURN_PROPERTY_VALUE,
|
|
ERR_INVALID_RETURN_VALUE,
|
|
ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK,
|
|
ERR_UNKNOWN_MODULE_FORMAT
|
|
} = require('internal/errors').codes;
|
|
const { URL } = require('url');
|
|
const { validateString } = require('internal/validators');
|
|
const ModuleMap = require('internal/modules/esm/module_map');
|
|
const ModuleJob = require('internal/modules/esm/module_job');
|
|
|
|
const defaultResolve = require('internal/modules/esm/default_resolve');
|
|
const createDynamicModule = require(
|
|
'internal/modules/esm/create_dynamic_module');
|
|
const { translators } = require('internal/modules/esm/translators');
|
|
const { ModuleWrap } = internalBinding('module_wrap');
|
|
|
|
const FunctionBind = Function.call.bind(Function.prototype.bind);
|
|
|
|
const debug = require('internal/util/debuglog').debuglog('esm');
|
|
|
|
/* A Loader instance is used as the main entry point for loading ES modules.
|
|
* Currently, this is a singleton -- there is only one used for loading
|
|
* the main module and everything in its dependency graph. */
|
|
class Loader {
|
|
constructor() {
|
|
// Methods which translate input code or other information
|
|
// into es modules
|
|
this.translators = translators;
|
|
|
|
// Registry of loaded modules, akin to `require.cache`
|
|
this.moduleMap = new ModuleMap();
|
|
|
|
// Map of already-loaded CJS modules to use
|
|
this.cjsCache = new Map();
|
|
|
|
// The resolver has the signature
|
|
// (specifier : string, parentURL : string, defaultResolve)
|
|
// -> Promise<{ url : string, format: string }>
|
|
// where defaultResolve is ModuleRequest.resolve (having the same
|
|
// signature itself).
|
|
// If `.format` on the returned value is 'dynamic', .dynamicInstantiate
|
|
// will be used as described below.
|
|
this._resolve = defaultResolve;
|
|
// This hook is only called when resolve(...).format is 'dynamic' and
|
|
// has the signature
|
|
// (url : string) -> Promise<{ exports: { ... }, execute: function }>
|
|
// Where `exports` is an object whose property names define the exported
|
|
// names of the generated module. `execute` is a function that receives
|
|
// an object with the same keys as `exports`, whose values are get/set
|
|
// functions for the actual exported values.
|
|
this._dynamicInstantiate = undefined;
|
|
// The index for assigning unique URLs to anonymous module evaluation
|
|
this.evalIndex = 0;
|
|
}
|
|
|
|
async resolve(specifier, parentURL) {
|
|
const isMain = parentURL === undefined;
|
|
if (!isMain)
|
|
validateString(parentURL, 'parentURL');
|
|
|
|
const resolved = await this._resolve(specifier, parentURL, defaultResolve);
|
|
|
|
if (typeof resolved !== 'object')
|
|
throw new ERR_INVALID_RETURN_VALUE(
|
|
'object', 'loader resolve', resolved
|
|
);
|
|
|
|
const { url, format } = resolved;
|
|
|
|
if (typeof url !== 'string')
|
|
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
|
|
'string', 'loader resolve', 'url', url
|
|
);
|
|
|
|
if (typeof format !== 'string')
|
|
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
|
|
'string', 'loader resolve', 'format', format
|
|
);
|
|
|
|
if (format === 'builtin')
|
|
return { url: `node:${url}`, format };
|
|
|
|
if (this._resolve !== defaultResolve) {
|
|
try {
|
|
new URL(url);
|
|
} catch {
|
|
throw new ERR_INVALID_RETURN_PROPERTY(
|
|
'url', 'loader resolve', 'url', url
|
|
);
|
|
}
|
|
}
|
|
|
|
if (format !== 'dynamic' && !url.startsWith('file:'))
|
|
throw new ERR_INVALID_RETURN_PROPERTY(
|
|
'file: url', 'loader resolve', 'url', url
|
|
);
|
|
|
|
return { url, format };
|
|
}
|
|
|
|
async eval(source, url = `eval:${++this.evalIndex}`) {
|
|
const evalInstance = async (url) => {
|
|
return {
|
|
module: new ModuleWrap(source, url),
|
|
reflect: undefined
|
|
};
|
|
};
|
|
const job = new ModuleJob(this, url, evalInstance, false);
|
|
this.moduleMap.set(url, job);
|
|
const { module, result } = await job.run();
|
|
return {
|
|
namespace: module.namespace(),
|
|
result
|
|
};
|
|
}
|
|
|
|
async import(specifier, parent) {
|
|
const job = await this.getModuleJob(specifier, parent);
|
|
const { module } = await job.run();
|
|
return module.namespace();
|
|
}
|
|
|
|
hook({ resolve, dynamicInstantiate }) {
|
|
// Use .bind() to avoid giving access to the Loader instance when called.
|
|
if (resolve !== undefined)
|
|
this._resolve = FunctionBind(resolve, null);
|
|
if (dynamicInstantiate !== undefined)
|
|
this._dynamicInstantiate = FunctionBind(dynamicInstantiate, null);
|
|
}
|
|
|
|
async getModuleJob(specifier, parentURL) {
|
|
const { url, format } = await this.resolve(specifier, parentURL);
|
|
let job = this.moduleMap.get(url);
|
|
if (job !== undefined)
|
|
return job;
|
|
|
|
let loaderInstance;
|
|
if (format === 'dynamic') {
|
|
if (typeof this._dynamicInstantiate !== 'function')
|
|
throw new ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK();
|
|
|
|
loaderInstance = async (url) => {
|
|
debug(`Translating dynamic ${url}`);
|
|
const { exports, execute } = await this._dynamicInstantiate(url);
|
|
return createDynamicModule(exports, url, (reflect) => {
|
|
debug(`Loading dynamic ${url}`);
|
|
execute(reflect.exports);
|
|
});
|
|
};
|
|
} else {
|
|
if (!translators.has(format))
|
|
throw new ERR_UNKNOWN_MODULE_FORMAT(format);
|
|
|
|
loaderInstance = translators.get(format);
|
|
}
|
|
|
|
job = new ModuleJob(this, url, loaderInstance, parentURL === undefined);
|
|
this.moduleMap.set(url, job);
|
|
return job;
|
|
}
|
|
}
|
|
|
|
Object.setPrototypeOf(Loader.prototype, null);
|
|
|
|
exports.Loader = Loader;
|