mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
This refactors the code that compiles SourceTextModule for the built-in ESM loader to use a common routine so that it's easier to customize cache handling for the ESM loader. In addition this introduces a common symbol for import.meta and import() so that we don't need to create additional closures as handlers, since we can get all the information we need from the V8 callback already. This should reduce the memory footprint of ESM as well. PR-URL: https://github.com/nodejs/node/pull/52291 Refs: https://github.com/nodejs/node/issues/47472 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
435 lines
12 KiB
JavaScript
435 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('internal/assert');
|
|
const {
|
|
ArrayIsArray,
|
|
ArrayPrototypeForEach,
|
|
ArrayPrototypeIndexOf,
|
|
ArrayPrototypeSome,
|
|
ObjectDefineProperty,
|
|
ObjectFreeze,
|
|
ObjectGetPrototypeOf,
|
|
ObjectPrototypeHasOwnProperty,
|
|
ObjectSetPrototypeOf,
|
|
ReflectApply,
|
|
SafePromiseAllReturnVoid,
|
|
Symbol,
|
|
SymbolToStringTag,
|
|
TypeError,
|
|
} = primordials;
|
|
|
|
const {
|
|
isModuleNamespaceObject,
|
|
} = require('internal/util/types');
|
|
const {
|
|
customInspectSymbol,
|
|
emitExperimentalWarning,
|
|
getConstructorOf,
|
|
kEmptyObject,
|
|
} = require('internal/util');
|
|
const {
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_VM_MODULE_ALREADY_LINKED,
|
|
ERR_VM_MODULE_DIFFERENT_CONTEXT,
|
|
ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA,
|
|
ERR_VM_MODULE_LINK_FAILURE,
|
|
ERR_VM_MODULE_NOT_MODULE,
|
|
ERR_VM_MODULE_STATUS,
|
|
} = require('internal/errors').codes;
|
|
const {
|
|
validateBoolean,
|
|
validateBuffer,
|
|
validateFunction,
|
|
validateInt32,
|
|
validateObject,
|
|
validateUint32,
|
|
validateString,
|
|
validateInternalField,
|
|
} = require('internal/validators');
|
|
|
|
const binding = internalBinding('module_wrap');
|
|
const {
|
|
ModuleWrap,
|
|
kUninstantiated,
|
|
kInstantiating,
|
|
kInstantiated,
|
|
kEvaluating,
|
|
kEvaluated,
|
|
kErrored,
|
|
} = binding;
|
|
|
|
const STATUS_MAP = {
|
|
[kUninstantiated]: 'unlinked',
|
|
[kInstantiating]: 'linking',
|
|
[kInstantiated]: 'linked',
|
|
[kEvaluating]: 'evaluating',
|
|
[kEvaluated]: 'evaluated',
|
|
[kErrored]: 'errored',
|
|
};
|
|
|
|
let globalModuleId = 0;
|
|
const defaultModuleName = 'vm:module';
|
|
|
|
const kWrap = Symbol('kWrap');
|
|
const kContext = Symbol('kContext');
|
|
const kPerContextModuleId = Symbol('kPerContextModuleId');
|
|
const kLink = Symbol('kLink');
|
|
|
|
const { isContext } = require('internal/vm');
|
|
|
|
function isModule(object) {
|
|
if (typeof object !== 'object' || object === null || !ObjectPrototypeHasOwnProperty(object, kWrap)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class Module {
|
|
constructor(options) {
|
|
emitExperimentalWarning('VM Modules');
|
|
|
|
if (new.target === Module) {
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
throw new TypeError('Module is not a constructor');
|
|
}
|
|
|
|
const {
|
|
context,
|
|
sourceText,
|
|
syntheticExportNames,
|
|
syntheticEvaluationSteps,
|
|
} = options;
|
|
|
|
if (context !== undefined) {
|
|
validateObject(context, 'context');
|
|
if (!isContext(context)) {
|
|
throw new ERR_INVALID_ARG_TYPE('options.context', 'vm.Context',
|
|
context);
|
|
}
|
|
}
|
|
|
|
let { identifier } = options;
|
|
if (identifier !== undefined) {
|
|
validateString(identifier, 'options.identifier');
|
|
} else if (context === undefined) {
|
|
identifier = `${defaultModuleName}(${globalModuleId++})`;
|
|
} else if (context[kPerContextModuleId] !== undefined) {
|
|
const curId = context[kPerContextModuleId];
|
|
identifier = `${defaultModuleName}(${curId})`;
|
|
context[kPerContextModuleId] += 1;
|
|
} else {
|
|
identifier = `${defaultModuleName}(0)`;
|
|
ObjectDefineProperty(context, kPerContextModuleId, {
|
|
__proto__: null,
|
|
value: 1,
|
|
writable: true,
|
|
enumerable: false,
|
|
configurable: true,
|
|
});
|
|
}
|
|
|
|
let registry = { __proto__: null };
|
|
if (sourceText !== undefined) {
|
|
this[kWrap] = new ModuleWrap(identifier, context, sourceText,
|
|
options.lineOffset, options.columnOffset,
|
|
options.cachedData);
|
|
registry = {
|
|
__proto__: null,
|
|
initializeImportMeta: options.initializeImportMeta,
|
|
importModuleDynamically: options.importModuleDynamically ?
|
|
importModuleDynamicallyWrap(options.importModuleDynamically) :
|
|
undefined,
|
|
};
|
|
// This will take precedence over the referrer as the object being
|
|
// passed into the callbacks.
|
|
registry.callbackReferrer = this;
|
|
const { registerModule } = require('internal/modules/esm/utils');
|
|
registerModule(this[kWrap], registry);
|
|
} else {
|
|
assert(syntheticEvaluationSteps);
|
|
this[kWrap] = new ModuleWrap(identifier, context,
|
|
syntheticExportNames,
|
|
syntheticEvaluationSteps);
|
|
}
|
|
|
|
this[kContext] = context;
|
|
}
|
|
|
|
get identifier() {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
return this[kWrap].url;
|
|
}
|
|
|
|
get context() {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
return this[kContext];
|
|
}
|
|
|
|
get namespace() {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
if (this[kWrap].getStatus() < kInstantiated) {
|
|
throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking');
|
|
}
|
|
return this[kWrap].getNamespace();
|
|
}
|
|
|
|
get status() {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
return STATUS_MAP[this[kWrap].getStatus()];
|
|
}
|
|
|
|
get error() {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
if (this[kWrap].getStatus() !== kErrored) {
|
|
throw new ERR_VM_MODULE_STATUS('must be errored');
|
|
}
|
|
return this[kWrap].getError();
|
|
}
|
|
|
|
async link(linker) {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
validateFunction(linker, 'linker');
|
|
if (this.status === 'linked') {
|
|
throw new ERR_VM_MODULE_ALREADY_LINKED();
|
|
}
|
|
if (this.status !== 'unlinked') {
|
|
throw new ERR_VM_MODULE_STATUS('must be unlinked');
|
|
}
|
|
await this[kLink](linker);
|
|
this[kWrap].instantiate();
|
|
}
|
|
|
|
async evaluate(options = kEmptyObject) {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
validateObject(options, 'options');
|
|
|
|
let timeout = options.timeout;
|
|
if (timeout === undefined) {
|
|
timeout = -1;
|
|
} else {
|
|
validateUint32(timeout, 'options.timeout', true);
|
|
}
|
|
const { breakOnSigint = false } = options;
|
|
validateBoolean(breakOnSigint, 'options.breakOnSigint');
|
|
const status = this[kWrap].getStatus();
|
|
if (status !== kInstantiated &&
|
|
status !== kEvaluated &&
|
|
status !== kErrored) {
|
|
throw new ERR_VM_MODULE_STATUS(
|
|
'must be one of linked, evaluated, or errored',
|
|
);
|
|
}
|
|
await this[kWrap].evaluate(timeout, breakOnSigint);
|
|
}
|
|
|
|
[customInspectSymbol](depth, options) {
|
|
validateInternalField(this, kWrap, 'Module');
|
|
if (typeof depth === 'number' && depth < 0)
|
|
return this;
|
|
|
|
const constructor = getConstructorOf(this) || Module;
|
|
const o = { __proto__: { constructor } };
|
|
o.status = this.status;
|
|
o.identifier = this.identifier;
|
|
o.context = this.context;
|
|
|
|
ObjectSetPrototypeOf(o, ObjectGetPrototypeOf(this));
|
|
ObjectDefineProperty(o, SymbolToStringTag, {
|
|
__proto__: null,
|
|
value: constructor.name,
|
|
configurable: true,
|
|
});
|
|
|
|
// Lazy to avoid circular dependency
|
|
const { inspect } = require('internal/util/inspect');
|
|
return inspect(o, { ...options, customInspect: false });
|
|
}
|
|
}
|
|
|
|
const kDependencySpecifiers = Symbol('kDependencySpecifiers');
|
|
const kNoError = Symbol('kNoError');
|
|
|
|
class SourceTextModule extends Module {
|
|
#error = kNoError;
|
|
#statusOverride;
|
|
|
|
constructor(sourceText, options = kEmptyObject) {
|
|
validateString(sourceText, 'sourceText');
|
|
validateObject(options, 'options');
|
|
|
|
const {
|
|
lineOffset = 0,
|
|
columnOffset = 0,
|
|
initializeImportMeta,
|
|
importModuleDynamically,
|
|
context,
|
|
identifier,
|
|
cachedData,
|
|
} = options;
|
|
|
|
validateInt32(lineOffset, 'options.lineOffset');
|
|
validateInt32(columnOffset, 'options.columnOffset');
|
|
|
|
if (initializeImportMeta !== undefined) {
|
|
validateFunction(initializeImportMeta, 'options.initializeImportMeta');
|
|
}
|
|
|
|
if (importModuleDynamically !== undefined) {
|
|
validateFunction(importModuleDynamically, 'options.importModuleDynamically');
|
|
}
|
|
|
|
if (cachedData !== undefined) {
|
|
validateBuffer(cachedData, 'options.cachedData');
|
|
}
|
|
|
|
super({
|
|
sourceText,
|
|
context,
|
|
identifier,
|
|
lineOffset,
|
|
columnOffset,
|
|
cachedData,
|
|
initializeImportMeta,
|
|
importModuleDynamically,
|
|
});
|
|
|
|
this[kLink] = async (linker) => {
|
|
this.#statusOverride = 'linking';
|
|
|
|
const promises = this[kWrap].link(async (identifier, attributes) => {
|
|
const module = await linker(identifier, this, { attributes, assert: attributes });
|
|
if (!isModule(module)) {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
if (module.context !== this.context) {
|
|
throw new ERR_VM_MODULE_DIFFERENT_CONTEXT();
|
|
}
|
|
if (module.status === 'errored') {
|
|
throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${identifier}' resolved to an errored module`, module.error);
|
|
}
|
|
if (module.status === 'unlinked') {
|
|
await module[kLink](linker);
|
|
}
|
|
return module[kWrap];
|
|
});
|
|
|
|
try {
|
|
if (promises !== undefined) {
|
|
await SafePromiseAllReturnVoid(promises);
|
|
}
|
|
} catch (e) {
|
|
this.#error = e;
|
|
throw e;
|
|
} finally {
|
|
this.#statusOverride = undefined;
|
|
}
|
|
};
|
|
|
|
this[kDependencySpecifiers] = undefined;
|
|
}
|
|
|
|
get dependencySpecifiers() {
|
|
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
|
this[kDependencySpecifiers] ??= ObjectFreeze(this[kWrap].getStaticDependencySpecifiers());
|
|
return this[kDependencySpecifiers];
|
|
}
|
|
|
|
get status() {
|
|
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
|
if (this.#error !== kNoError) {
|
|
return 'errored';
|
|
}
|
|
if (this.#statusOverride) {
|
|
return this.#statusOverride;
|
|
}
|
|
return super.status;
|
|
}
|
|
|
|
get error() {
|
|
validateInternalField(this, kDependencySpecifiers, 'SourceTextModule');
|
|
if (this.#error !== kNoError) {
|
|
return this.#error;
|
|
}
|
|
return super.error;
|
|
}
|
|
|
|
createCachedData() {
|
|
const { status } = this;
|
|
if (status === 'evaluating' ||
|
|
status === 'evaluated' ||
|
|
status === 'errored') {
|
|
throw new ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA();
|
|
}
|
|
return this[kWrap].createCachedData();
|
|
}
|
|
}
|
|
|
|
class SyntheticModule extends Module {
|
|
constructor(exportNames, evaluateCallback, options = kEmptyObject) {
|
|
if (!ArrayIsArray(exportNames) ||
|
|
ArrayPrototypeSome(exportNames, (e) => typeof e !== 'string')) {
|
|
throw new ERR_INVALID_ARG_TYPE('exportNames',
|
|
'Array of unique strings',
|
|
exportNames);
|
|
} else {
|
|
ArrayPrototypeForEach(exportNames, (name, i) => {
|
|
if (ArrayPrototypeIndexOf(exportNames, name, i + 1) !== -1) {
|
|
throw new ERR_INVALID_ARG_VALUE(`exportNames.${name}`,
|
|
name,
|
|
'is duplicated');
|
|
}
|
|
});
|
|
}
|
|
validateFunction(evaluateCallback, 'evaluateCallback');
|
|
|
|
validateObject(options, 'options');
|
|
|
|
const { context, identifier } = options;
|
|
|
|
super({
|
|
syntheticExportNames: exportNames,
|
|
syntheticEvaluationSteps: evaluateCallback,
|
|
context,
|
|
identifier,
|
|
});
|
|
|
|
this[kLink] = () => this[kWrap].link(() => {
|
|
assert.fail('link callback should not be called');
|
|
});
|
|
}
|
|
|
|
setExport(name, value) {
|
|
validateInternalField(this, kWrap, 'SyntheticModule');
|
|
validateString(name, 'name');
|
|
if (this[kWrap].getStatus() < kInstantiated) {
|
|
throw new ERR_VM_MODULE_STATUS('must be linked');
|
|
}
|
|
this[kWrap].setExport(name, value);
|
|
}
|
|
}
|
|
|
|
function importModuleDynamicallyWrap(importModuleDynamically) {
|
|
const importModuleDynamicallyWrapper = async (...args) => {
|
|
const m = await ReflectApply(importModuleDynamically, this, args);
|
|
if (isModuleNamespaceObject(m)) {
|
|
return m;
|
|
}
|
|
if (!isModule(m)) {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
if (m.status === 'errored') {
|
|
throw m.error;
|
|
}
|
|
return m.namespace;
|
|
};
|
|
return importModuleDynamicallyWrapper;
|
|
}
|
|
|
|
module.exports = {
|
|
Module,
|
|
SourceTextModule,
|
|
SyntheticModule,
|
|
importModuleDynamicallyWrap,
|
|
};
|