mirror of
https://github.com/nodejs/node.git
synced 2025-05-08 20:39:30 +00:00

PR-URL: https://github.com/nodejs/node/pull/34605 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
255 lines
8.0 KiB
JavaScript
255 lines
8.0 KiB
JavaScript
'use strict';
|
|
|
|
// This is needed to avoid cycles in esm/resolve <-> cjs/loader
|
|
require('internal/modules/cjs/loader');
|
|
|
|
const {
|
|
FunctionPrototypeBind,
|
|
ObjectSetPrototypeOf,
|
|
SafeWeakMap,
|
|
} = primordials;
|
|
|
|
const {
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_INVALID_RETURN_PROPERTY,
|
|
ERR_INVALID_RETURN_PROPERTY_VALUE,
|
|
ERR_INVALID_RETURN_VALUE,
|
|
ERR_UNKNOWN_MODULE_FORMAT
|
|
} = require('internal/errors').codes;
|
|
const { URL, pathToFileURL } = require('internal/url');
|
|
const { validateString } = require('internal/validators');
|
|
const ModuleMap = require('internal/modules/esm/module_map');
|
|
const ModuleJob = require('internal/modules/esm/module_job');
|
|
|
|
const {
|
|
defaultResolve,
|
|
DEFAULT_CONDITIONS,
|
|
} = require('internal/modules/esm/resolve');
|
|
const { defaultGetFormat } = require('internal/modules/esm/get_format');
|
|
const { defaultGetSource } = require(
|
|
'internal/modules/esm/get_source');
|
|
const { defaultTransformSource } = require(
|
|
'internal/modules/esm/transform_source');
|
|
const { translators } = require(
|
|
'internal/modules/esm/translators');
|
|
const { getOptionValue } = require('internal/options');
|
|
|
|
/* 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 SafeWeakMap();
|
|
|
|
// This hook is called before the first root module is imported. It's a
|
|
// function that returns a piece of code that runs as a sloppy-mode script.
|
|
// The script may evaluate to a function that can be called with a
|
|
// `getBuiltin` helper that can be used to retrieve builtins.
|
|
// If the hook returns `null` instead of a source string, it opts out of
|
|
// running any preload code.
|
|
// The preload code runs as soon as the hook module has finished evaluating.
|
|
this._getGlobalPreloadCode = null;
|
|
// The resolver has the signature
|
|
// (specifier : string, parentURL : string, defaultResolve)
|
|
// -> Promise<{ url : string }>
|
|
// where defaultResolve is ModuleRequest.resolve (having the same
|
|
// signature itself).
|
|
this._resolve = defaultResolve;
|
|
// This hook is called after the module is resolved but before a translator
|
|
// is chosen to load it; the format returned by this function is the name
|
|
// of a translator.
|
|
this._getFormat = defaultGetFormat;
|
|
// This hook is called just before the source code of an ES module file
|
|
// is loaded.
|
|
this._getSource = defaultGetSource;
|
|
// This hook is called just after the source code of an ES module file
|
|
// is loaded, but before anything is done with the string.
|
|
this._transformSource = defaultTransformSource;
|
|
// 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 resolveResponse = await this._resolve(
|
|
specifier, { parentURL, conditions: DEFAULT_CONDITIONS }, defaultResolve);
|
|
if (typeof resolveResponse !== 'object') {
|
|
throw new ERR_INVALID_RETURN_VALUE(
|
|
'object', 'loader resolve', resolveResponse);
|
|
}
|
|
|
|
const { url } = resolveResponse;
|
|
if (typeof url !== 'string') {
|
|
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
|
|
'string', 'loader resolve', 'url', url);
|
|
}
|
|
return url;
|
|
}
|
|
|
|
async getFormat(url) {
|
|
const getFormatResponse = await this._getFormat(
|
|
url, {}, defaultGetFormat);
|
|
if (typeof getFormatResponse !== 'object') {
|
|
throw new ERR_INVALID_RETURN_VALUE(
|
|
'object', 'loader getFormat', getFormatResponse);
|
|
}
|
|
|
|
const { format } = getFormatResponse;
|
|
if (typeof format !== 'string') {
|
|
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
|
|
'string', 'loader getFormat', 'format', format);
|
|
}
|
|
|
|
if (format === 'builtin') {
|
|
return format;
|
|
}
|
|
|
|
if (this._resolve !== defaultResolve) {
|
|
try {
|
|
new URL(url);
|
|
} catch {
|
|
throw new ERR_INVALID_RETURN_PROPERTY(
|
|
'url', 'loader resolve', 'url', url
|
|
);
|
|
}
|
|
}
|
|
|
|
if (this._resolve === defaultResolve &&
|
|
!url.startsWith('file:') &&
|
|
!url.startsWith('data:')
|
|
) {
|
|
throw new ERR_INVALID_RETURN_PROPERTY(
|
|
'file: or data: url', 'loader resolve', 'url', url
|
|
);
|
|
}
|
|
|
|
return format;
|
|
}
|
|
|
|
async eval(
|
|
source,
|
|
url = pathToFileURL(`${process.cwd()}/[eval${++this.evalIndex}]`).href
|
|
) {
|
|
const evalInstance = (url) => {
|
|
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
|
|
const module = new ModuleWrap(url, undefined, source, 0, 0);
|
|
callbackMap.set(module, {
|
|
importModuleDynamically: (specifier, { url }) => {
|
|
return this.import(specifier, url);
|
|
}
|
|
});
|
|
|
|
return module;
|
|
};
|
|
const job = new ModuleJob(this, url, evalInstance, false, false);
|
|
this.moduleMap.set(url, job);
|
|
const { module } = await job.run();
|
|
return {
|
|
namespace: module.getNamespace(),
|
|
};
|
|
}
|
|
|
|
async import(specifier, parent) {
|
|
const job = await this.getModuleJob(specifier, parent);
|
|
const { module } = await job.run();
|
|
return module.getNamespace();
|
|
}
|
|
|
|
hook(hooks) {
|
|
const {
|
|
resolve,
|
|
dynamicInstantiate,
|
|
getFormat,
|
|
getSource,
|
|
transformSource,
|
|
getGlobalPreloadCode,
|
|
} = hooks;
|
|
|
|
// Use .bind() to avoid giving access to the Loader instance when called.
|
|
if (resolve !== undefined)
|
|
this._resolve = FunctionPrototypeBind(resolve, null);
|
|
if (dynamicInstantiate !== undefined) {
|
|
process.emitWarning(
|
|
'The dynamicInstantiate loader hook has been removed.');
|
|
}
|
|
if (getFormat !== undefined) {
|
|
this._getFormat = FunctionPrototypeBind(getFormat, null);
|
|
}
|
|
if (getSource !== undefined) {
|
|
this._getSource = FunctionPrototypeBind(getSource, null);
|
|
}
|
|
if (transformSource !== undefined) {
|
|
this._transformSource = FunctionPrototypeBind(transformSource, null);
|
|
}
|
|
if (getGlobalPreloadCode !== undefined) {
|
|
this._getGlobalPreloadCode =
|
|
FunctionPrototypeBind(getGlobalPreloadCode, null);
|
|
}
|
|
}
|
|
|
|
runGlobalPreloadCode() {
|
|
if (!this._getGlobalPreloadCode) {
|
|
return;
|
|
}
|
|
const preloadCode = this._getGlobalPreloadCode();
|
|
if (preloadCode === null) {
|
|
return;
|
|
}
|
|
|
|
if (typeof preloadCode !== 'string') {
|
|
throw new ERR_INVALID_RETURN_VALUE(
|
|
'string', 'loader getGlobalPreloadCode', preloadCode);
|
|
}
|
|
const { compileFunction } = require('vm');
|
|
const preloadInit = compileFunction(preloadCode, ['getBuiltin'], {
|
|
filename: '<preload>',
|
|
});
|
|
const { NativeModule } = require('internal/bootstrap/loaders');
|
|
|
|
preloadInit.call(globalThis, (builtinName) => {
|
|
if (NativeModule.canBeRequiredByUsers(builtinName)) {
|
|
return require(builtinName);
|
|
}
|
|
throw new ERR_INVALID_ARG_VALUE('builtinName', builtinName);
|
|
});
|
|
}
|
|
|
|
async getModuleJob(specifier, parentURL) {
|
|
const url = await this.resolve(specifier, parentURL);
|
|
const format = await this.getFormat(url);
|
|
let job = this.moduleMap.get(url);
|
|
// CommonJS will set functions for lazy job evaluation.
|
|
if (typeof job === 'function')
|
|
this.moduleMap.set(url, job = job());
|
|
if (job !== undefined)
|
|
return job;
|
|
|
|
if (!translators.has(format))
|
|
throw new ERR_UNKNOWN_MODULE_FORMAT(format);
|
|
|
|
const loaderInstance = translators.get(format);
|
|
|
|
const inspectBrk = parentURL === undefined &&
|
|
format === 'module' && getOptionValue('--inspect-brk');
|
|
job = new ModuleJob(this, url, loaderInstance, parentURL === undefined,
|
|
inspectBrk);
|
|
this.moduleMap.set(url, job);
|
|
return job;
|
|
}
|
|
}
|
|
|
|
ObjectSetPrototypeOf(Loader.prototype, null);
|
|
|
|
exports.Loader = Loader;
|