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

- Removes redundant `instantiate` method - Refactors `link` to match the spec linking steps more accurately - Removes URL validation from SourceTextModule specifiers - DRYs some dynamic import logic Closes: https://github.com/nodejs/node/issues/29030 Co-Authored-By: Michaël Zasso <targos@protonmail.com> PR-URL: https://github.com/nodejs/node/pull/29776 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
334 lines
8.4 KiB
JavaScript
334 lines
8.4 KiB
JavaScript
'use strict';
|
|
|
|
const { Object, SafePromise } = primordials;
|
|
|
|
const { isModuleNamespaceObject } = require('internal/util/types');
|
|
const { isContext } = internalBinding('contextify');
|
|
const {
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_VM_MODULE_ALREADY_LINKED,
|
|
ERR_VM_MODULE_DIFFERENT_CONTEXT,
|
|
ERR_VM_MODULE_LINKING_ERRORED,
|
|
ERR_VM_MODULE_NOT_MODULE,
|
|
ERR_VM_MODULE_STATUS,
|
|
} = require('internal/errors').codes;
|
|
const {
|
|
getConstructorOf,
|
|
customInspectSymbol,
|
|
emitExperimentalWarning
|
|
} = require('internal/util');
|
|
const {
|
|
validateInt32,
|
|
validateUint32,
|
|
validateString,
|
|
} = 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 perContextModuleId = new WeakMap();
|
|
const wrapToModuleMap = new WeakMap();
|
|
|
|
const kNoError = Symbol('kNoError');
|
|
|
|
class SourceTextModule {
|
|
#wrap;
|
|
#identifier;
|
|
#context;
|
|
#dependencySpecifiers;
|
|
#statusOverride;
|
|
#error = kNoError;
|
|
|
|
constructor(source, options = {}) {
|
|
emitExperimentalWarning('vm.SourceTextModule');
|
|
|
|
validateString(source, 'source');
|
|
if (typeof options !== 'object' || options === null)
|
|
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
|
|
|
|
const {
|
|
context,
|
|
lineOffset = 0,
|
|
columnOffset = 0,
|
|
initializeImportMeta,
|
|
importModuleDynamically,
|
|
} = options;
|
|
|
|
if (context !== undefined) {
|
|
if (typeof context !== 'object' || context === null) {
|
|
throw new ERR_INVALID_ARG_TYPE('options.context', 'Object', context);
|
|
}
|
|
if (!isContext(context)) {
|
|
throw new ERR_INVALID_ARG_TYPE('options.context', 'vm.Context',
|
|
context);
|
|
}
|
|
}
|
|
|
|
validateInt32(lineOffset, 'options.lineOffset');
|
|
validateInt32(columnOffset, 'options.columnOffset');
|
|
|
|
if (initializeImportMeta !== undefined &&
|
|
typeof initializeImportMeta !== 'function') {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'options.initializeImportMeta', 'function', initializeImportMeta);
|
|
}
|
|
|
|
if (importModuleDynamically !== undefined &&
|
|
typeof importModuleDynamically !== 'function') {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'options.importModuleDynamically', 'function', importModuleDynamically);
|
|
}
|
|
|
|
let { identifier } = options;
|
|
if (identifier !== undefined) {
|
|
validateString(identifier, 'options.identifier');
|
|
} else if (context === undefined) {
|
|
identifier = `${defaultModuleName}(${globalModuleId++})`;
|
|
} else if (perContextModuleId.has(context)) {
|
|
const curId = perContextModuleId.get(context);
|
|
identifier = `${defaultModuleName}(${curId})`;
|
|
perContextModuleId.set(context, curId + 1);
|
|
} else {
|
|
identifier = `${defaultModuleName}(0)`;
|
|
perContextModuleId.set(context, 1);
|
|
}
|
|
|
|
this.#wrap = new ModuleWrap(
|
|
source, identifier, context,
|
|
lineOffset, columnOffset,
|
|
);
|
|
wrapToModuleMap.set(this.#wrap, this);
|
|
this.#identifier = identifier;
|
|
this.#context = context;
|
|
|
|
binding.callbackMap.set(this.#wrap, {
|
|
initializeImportMeta,
|
|
importModuleDynamically: importModuleDynamically ?
|
|
importModuleDynamicallyWrap(importModuleDynamically) :
|
|
undefined,
|
|
});
|
|
}
|
|
|
|
get status() {
|
|
try {
|
|
this.#error;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
if (this.#error !== kNoError) {
|
|
return 'errored';
|
|
}
|
|
if (this.#statusOverride) {
|
|
return this.#statusOverride;
|
|
}
|
|
return STATUS_MAP[this.#wrap.getStatus()];
|
|
}
|
|
|
|
get identifier() {
|
|
try {
|
|
return this.#identifier;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
}
|
|
|
|
get context() {
|
|
try {
|
|
return this.#context;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
}
|
|
|
|
get namespace() {
|
|
try {
|
|
this.#wrap;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
if (this.#wrap.getStatus() < kInstantiated) {
|
|
throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking');
|
|
}
|
|
return this.#wrap.getNamespace();
|
|
}
|
|
|
|
get dependencySpecifiers() {
|
|
try {
|
|
this.#dependencySpecifiers;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
if (this.#dependencySpecifiers === undefined) {
|
|
this.#dependencySpecifiers = this.#wrap.getStaticDependencySpecifiers();
|
|
}
|
|
return this.#dependencySpecifiers;
|
|
}
|
|
|
|
get error() {
|
|
try {
|
|
this.#error;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
if (this.#error !== kNoError) {
|
|
return this.#error;
|
|
}
|
|
if (this.#wrap.getStatus() !== kErrored) {
|
|
throw new ERR_VM_MODULE_STATUS('must be errored');
|
|
}
|
|
return this.#wrap.getError();
|
|
}
|
|
|
|
async link(linker) {
|
|
try {
|
|
this.#link;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
|
|
if (typeof linker !== 'function') {
|
|
throw new ERR_INVALID_ARG_TYPE('linker', 'function', linker);
|
|
}
|
|
if (this.status !== 'unlinked') {
|
|
throw new ERR_VM_MODULE_ALREADY_LINKED();
|
|
}
|
|
|
|
await this.#link(linker);
|
|
|
|
this.#wrap.instantiate();
|
|
}
|
|
|
|
#link = async function(linker) {
|
|
this.#statusOverride = 'linking';
|
|
|
|
const promises = this.#wrap.link(async (identifier) => {
|
|
const module = await linker(identifier, this);
|
|
try {
|
|
module.#wrap;
|
|
} catch {
|
|
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_LINKING_ERRORED();
|
|
}
|
|
if (module.status === 'unlinked') {
|
|
await module.#link(linker);
|
|
}
|
|
return module.#wrap;
|
|
});
|
|
|
|
try {
|
|
if (promises !== undefined) {
|
|
await SafePromise.all(promises);
|
|
}
|
|
} catch (e) {
|
|
this.#error = e;
|
|
throw e;
|
|
} finally {
|
|
this.#statusOverride = undefined;
|
|
}
|
|
};
|
|
|
|
|
|
async evaluate(options = {}) {
|
|
try {
|
|
this.#wrap;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
|
|
if (typeof options !== 'object' || options === null) {
|
|
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
|
|
}
|
|
|
|
let timeout = options.timeout;
|
|
if (timeout === undefined) {
|
|
timeout = -1;
|
|
} else {
|
|
validateUint32(timeout, 'options.timeout', true);
|
|
}
|
|
const { breakOnSigint = false } = options;
|
|
if (typeof breakOnSigint !== 'boolean') {
|
|
throw new ERR_INVALID_ARG_TYPE('options.breakOnSigint', 'boolean',
|
|
breakOnSigint);
|
|
}
|
|
const status = this.#wrap.getStatus();
|
|
if (status !== kInstantiated &&
|
|
status !== kEvaluated &&
|
|
status !== kErrored) {
|
|
throw new ERR_VM_MODULE_STATUS(
|
|
'must be one of linked, evaluated, or errored'
|
|
);
|
|
}
|
|
const result = this.#wrap.evaluate(timeout, breakOnSigint);
|
|
return { __proto__: null, result };
|
|
}
|
|
|
|
static importModuleDynamicallyWrap(importModuleDynamically) {
|
|
// Named declaration for function name
|
|
const importModuleDynamicallyWrapper = async (...args) => {
|
|
const m = await importModuleDynamically(...args);
|
|
if (isModuleNamespaceObject(m)) {
|
|
return m;
|
|
}
|
|
try {
|
|
m.#wrap;
|
|
} catch {
|
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
|
}
|
|
if (m.status === 'errored') {
|
|
throw m.error;
|
|
}
|
|
return m.namespace;
|
|
};
|
|
return importModuleDynamicallyWrapper;
|
|
}
|
|
|
|
[customInspectSymbol](depth, options) {
|
|
let ctor = getConstructorOf(this);
|
|
ctor = ctor === null ? SourceTextModule : ctor;
|
|
|
|
if (typeof depth === 'number' && depth < 0)
|
|
return options.stylize(`[${ctor.name}]`, 'special');
|
|
|
|
const o = Object.create({ constructor: ctor });
|
|
o.status = this.status;
|
|
o.identifier = this.identifier;
|
|
o.context = this.context;
|
|
return require('internal/util/inspect').inspect(o, options);
|
|
}
|
|
}
|
|
|
|
// Declared as static to allow access to #wrap
|
|
const importModuleDynamicallyWrap =
|
|
SourceTextModule.importModuleDynamicallyWrap;
|
|
delete SourceTextModule.importModuleDynamicallyWrap;
|
|
|
|
module.exports = {
|
|
SourceTextModule,
|
|
wrapToModuleMap,
|
|
importModuleDynamicallyWrap,
|
|
};
|