vm: add dynamic import support
PR-URL: https://github.com/nodejs/node/pull/22381 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
This commit is contained in:
parent
124a8e2123
commit
4c37df779c
@ -1779,6 +1779,11 @@ The V8 `BreakIterator` API was used but the full ICU data set is not installed.
|
|||||||
While using the Performance Timing API (`perf_hooks`), no valid performance
|
While using the Performance Timing API (`perf_hooks`), no valid performance
|
||||||
entry types were found.
|
entry types were found.
|
||||||
|
|
||||||
|
<a id="ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING"></a>
|
||||||
|
### ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
|
||||||
|
|
||||||
|
A dynamic import callback was not specified.
|
||||||
|
|
||||||
<a id="ERR_VM_MODULE_ALREADY_LINKED"></a>
|
<a id="ERR_VM_MODULE_ALREADY_LINKED"></a>
|
||||||
### ERR_VM_MODULE_ALREADY_LINKED
|
### ERR_VM_MODULE_ALREADY_LINKED
|
||||||
|
|
||||||
|
@ -167,10 +167,19 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });
|
|||||||
in stack traces produced by this `Module`.
|
in stack traces produced by this `Module`.
|
||||||
* `columnOffset` {integer} Specifies the column number offset that is
|
* `columnOffset` {integer} Specifies the column number offset that is
|
||||||
displayed in stack traces produced by this `Module`.
|
displayed in stack traces produced by this `Module`.
|
||||||
* `initalizeImportMeta` {Function} Called during evaluation of this `Module`
|
* `initializeImportMeta` {Function} Called during evaluation of this `Module`
|
||||||
to initialize the `import.meta`. This function has the signature `(meta,
|
to initialize the `import.meta`. This function has the signature `(meta,
|
||||||
module)`, where `meta` is the `import.meta` object in the `Module`, and
|
module)`, where `meta` is the `import.meta` object in the `Module`, and
|
||||||
`module` is this `vm.SourceTextModule` object.
|
`module` is this `vm.SourceTextModule` object.
|
||||||
|
* `importModuleDynamically` {Function} Called during evaluation of this
|
||||||
|
module when `import()` is called. This function has the signature
|
||||||
|
`(specifier, module)` where `specifier` is the specifier passed to
|
||||||
|
`import()` and `module` is this `vm.SourceTextModule`. If this option is
|
||||||
|
not specified, calls to `import()` will reject with
|
||||||
|
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. This method can return a
|
||||||
|
[Module Namespace Object][], but returning a `vm.SourceTextModule` is
|
||||||
|
recommended in order to take advantage of error tracking, and to avoid
|
||||||
|
issues with namespaces that contain `then` function exports.
|
||||||
|
|
||||||
Creates a new ES `Module` object.
|
Creates a new ES `Module` object.
|
||||||
|
|
||||||
@ -436,6 +445,15 @@ changes:
|
|||||||
The `cachedDataProduced` value will be set to either `true` or `false`
|
The `cachedDataProduced` value will be set to either `true` or `false`
|
||||||
depending on whether code cache data is produced successfully.
|
depending on whether code cache data is produced successfully.
|
||||||
This option is deprecated in favor of `script.createCachedData()`.
|
This option is deprecated in favor of `script.createCachedData()`.
|
||||||
|
* `importModuleDynamically` {Function} Called during evaluation of this
|
||||||
|
module when `import()` is called. This function has the signature
|
||||||
|
`(specifier, module)` where `specifier` is the specifier passed to
|
||||||
|
`import()` and `module` is this `vm.SourceTextModule`. If this option is
|
||||||
|
not specified, calls to `import()` will reject with
|
||||||
|
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. This method can return a
|
||||||
|
[Module Namespace Object][], but returning a `vm.SourceTextModule` is
|
||||||
|
recommended in order to take advantage of error tracking, and to avoid
|
||||||
|
issues with namespaces that contain `then` function exports.
|
||||||
|
|
||||||
Creating a new `vm.Script` object compiles `code` but does not run it. The
|
Creating a new `vm.Script` object compiles `code` but does not run it. The
|
||||||
compiled `vm.Script` can be run later multiple times. The `code` is not bound to
|
compiled `vm.Script` can be run later multiple times. The `code` is not bound to
|
||||||
@ -945,6 +963,7 @@ associating it with the `sandbox` object is what this document refers to as
|
|||||||
"contextifying" the `sandbox`.
|
"contextifying" the `sandbox`.
|
||||||
|
|
||||||
[`Error`]: errors.html#errors_class_error
|
[`Error`]: errors.html#errors_class_error
|
||||||
|
[`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`]: errors.html#ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING
|
||||||
[`URL`]: url.html#url_class_url
|
[`URL`]: url.html#url_class_url
|
||||||
[`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
|
[`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
|
||||||
[`script.runInContext()`]: #vm_script_runincontext_contextifiedsandbox_options
|
[`script.runInContext()`]: #vm_script_runincontext_contextifiedsandbox_options
|
||||||
@ -954,6 +973,7 @@ associating it with the `sandbox` object is what this document refers to as
|
|||||||
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
|
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
|
||||||
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
|
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
|
||||||
[GetModuleNamespace]: https://tc39.github.io/ecma262/#sec-getmodulenamespace
|
[GetModuleNamespace]: https://tc39.github.io/ecma262/#sec-getmodulenamespace
|
||||||
|
[Module Namespace Object]: https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects
|
||||||
[ECMAScript Module Loader]: esm.html#esm_ecmascript_modules
|
[ECMAScript Module Loader]: esm.html#esm_ecmascript_modules
|
||||||
[Evaluate() concrete method]: https://tc39.github.io/ecma262/#sec-moduleevaluation
|
[Evaluate() concrete method]: https://tc39.github.io/ecma262/#sec-moduleevaluation
|
||||||
[HostResolveImportedModule]: https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule
|
[HostResolveImportedModule]: https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule
|
||||||
|
@ -107,6 +107,8 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create this WeakMap in js-land because V8 has no C++ API for WeakMap
|
||||||
|
internalBinding('module_wrap').callbackMap = new WeakMap();
|
||||||
const { ContextifyScript } = internalBinding('contextify');
|
const { ContextifyScript } = internalBinding('contextify');
|
||||||
|
|
||||||
// Set up NativeModule
|
// Set up NativeModule
|
||||||
|
@ -873,6 +873,8 @@ E('ERR_V8BREAKITERATOR',
|
|||||||
// This should probably be a `TypeError`.
|
// This should probably be a `TypeError`.
|
||||||
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
|
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
|
||||||
'At least one valid performance entry type is required', Error);
|
'At least one valid performance entry type is required', Error);
|
||||||
|
E('ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING',
|
||||||
|
'A dynamic import callback was not specified.', TypeError);
|
||||||
E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked', Error);
|
E('ERR_VM_MODULE_ALREADY_LINKED', 'Module has already been linked', Error);
|
||||||
E('ERR_VM_MODULE_DIFFERENT_CONTEXT',
|
E('ERR_VM_MODULE_DIFFERENT_CONTEXT',
|
||||||
'Linked modules must use the same context', Error);
|
'Linked modules must use the same context', Error);
|
||||||
|
@ -29,6 +29,7 @@ const assert = require('assert').ok;
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const internalFS = require('internal/fs/utils');
|
const internalFS = require('internal/fs/utils');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const { URL } = require('url');
|
||||||
const {
|
const {
|
||||||
internalModuleReadJSON,
|
internalModuleReadJSON,
|
||||||
internalModuleStat
|
internalModuleStat
|
||||||
@ -656,6 +657,13 @@ Module.prototype.require = function(id) {
|
|||||||
// (needed for setting breakpoint when called with --inspect-brk)
|
// (needed for setting breakpoint when called with --inspect-brk)
|
||||||
var resolvedArgv;
|
var resolvedArgv;
|
||||||
|
|
||||||
|
function normalizeReferrerURL(referrer) {
|
||||||
|
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
|
||||||
|
return pathToFileURL(referrer).href;
|
||||||
|
}
|
||||||
|
return new URL(referrer).href;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Run the file contents in the correct scope or sandbox. Expose
|
// Run the file contents in the correct scope or sandbox. Expose
|
||||||
// the correct helper variables (require, module, exports) to
|
// the correct helper variables (require, module, exports) to
|
||||||
@ -671,7 +679,12 @@ Module.prototype._compile = function(content, filename) {
|
|||||||
var compiledWrapper = vm.runInThisContext(wrapper, {
|
var compiledWrapper = vm.runInThisContext(wrapper, {
|
||||||
filename: filename,
|
filename: filename,
|
||||||
lineOffset: 0,
|
lineOffset: 0,
|
||||||
displayErrors: true
|
displayErrors: true,
|
||||||
|
importModuleDynamically: experimentalModules ? async (specifier) => {
|
||||||
|
if (asyncESM === undefined) lazyLoadESM();
|
||||||
|
const loader = await asyncESM.loaderPromise;
|
||||||
|
return loader.import(specifier, normalizeReferrerURL(filename));
|
||||||
|
} : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
var inspectorWrapper = null;
|
var inspectorWrapper = null;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { NativeModule } = require('internal/bootstrap/loaders');
|
const { NativeModule } = require('internal/bootstrap/loaders');
|
||||||
const { ModuleWrap } = internalBinding('module_wrap');
|
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
|
||||||
const {
|
const {
|
||||||
stripShebang,
|
stripShebang,
|
||||||
stripBOM
|
stripBOM
|
||||||
@ -15,6 +15,8 @@ const { _makeLong } = require('path');
|
|||||||
const { SafeMap } = require('internal/safe_globals');
|
const { SafeMap } = require('internal/safe_globals');
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
const { debuglog, promisify } = require('util');
|
const { debuglog, promisify } = require('util');
|
||||||
|
const esmLoader = require('internal/process/esm_loader');
|
||||||
|
|
||||||
const readFileAsync = promisify(fs.readFile);
|
const readFileAsync = promisify(fs.readFile);
|
||||||
const readFileSync = fs.readFileSync;
|
const readFileSync = fs.readFileSync;
|
||||||
const StringReplace = Function.call.bind(String.prototype.replace);
|
const StringReplace = Function.call.bind(String.prototype.replace);
|
||||||
@ -25,13 +27,27 @@ const debug = debuglog('esm');
|
|||||||
const translators = new SafeMap();
|
const translators = new SafeMap();
|
||||||
module.exports = translators;
|
module.exports = translators;
|
||||||
|
|
||||||
|
function initializeImportMeta(meta, { url }) {
|
||||||
|
meta.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importModuleDynamically(specifier, { url }) {
|
||||||
|
const loader = await esmLoader.loaderPromise;
|
||||||
|
return loader.import(specifier, url);
|
||||||
|
}
|
||||||
|
|
||||||
// Strategy for loading a standard JavaScript module
|
// Strategy for loading a standard JavaScript module
|
||||||
translators.set('esm', async (url) => {
|
translators.set('esm', async (url) => {
|
||||||
const source = `${await readFileAsync(new URL(url))}`;
|
const source = `${await readFileAsync(new URL(url))}`;
|
||||||
debug(`Translating StandardModule ${url}`);
|
debug(`Translating StandardModule ${url}`);
|
||||||
|
const module = new ModuleWrap(stripShebang(source), url);
|
||||||
|
callbackMap.set(module, {
|
||||||
|
initializeImportMeta,
|
||||||
|
importModuleDynamically,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
module: new ModuleWrap(stripShebang(source), url),
|
module,
|
||||||
reflect: undefined
|
reflect: undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,40 +2,42 @@
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
setImportModuleDynamicallyCallback,
|
setImportModuleDynamicallyCallback,
|
||||||
setInitializeImportMetaObjectCallback
|
setInitializeImportMetaObjectCallback,
|
||||||
|
callbackMap,
|
||||||
} = internalBinding('module_wrap');
|
} = internalBinding('module_wrap');
|
||||||
|
|
||||||
const { pathToFileURL } = require('internal/url');
|
const { pathToFileURL } = require('internal/url');
|
||||||
const Loader = require('internal/modules/esm/loader');
|
const Loader = require('internal/modules/esm/loader');
|
||||||
const path = require('path');
|
|
||||||
const { URL } = require('url');
|
|
||||||
const {
|
const {
|
||||||
initImportMetaMap,
|
wrapToModuleMap,
|
||||||
wrapToModuleMap
|
|
||||||
} = require('internal/vm/source_text_module');
|
} = require('internal/vm/source_text_module');
|
||||||
|
const {
|
||||||
function normalizeReferrerURL(referrer) {
|
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
|
||||||
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
|
} = require('internal/errors').codes;
|
||||||
return pathToFileURL(referrer).href;
|
|
||||||
}
|
|
||||||
return new URL(referrer).href;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeImportMetaObject(wrap, meta) {
|
function initializeImportMetaObject(wrap, meta) {
|
||||||
const vmModule = wrapToModuleMap.get(wrap);
|
if (callbackMap.has(wrap)) {
|
||||||
if (vmModule === undefined) {
|
const { initializeImportMeta } = callbackMap.get(wrap);
|
||||||
// This ModuleWrap belongs to the Loader.
|
|
||||||
meta.url = wrap.url;
|
|
||||||
} else {
|
|
||||||
const initializeImportMeta = initImportMetaMap.get(vmModule);
|
|
||||||
if (initializeImportMeta !== undefined) {
|
if (initializeImportMeta !== undefined) {
|
||||||
// This ModuleWrap belongs to vm.SourceTextModule,
|
initializeImportMeta(meta, wrapToModuleMap.get(wrap) || wrap);
|
||||||
// initializer callback was provided.
|
|
||||||
initializeImportMeta(meta, vmModule);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function importModuleDynamicallyCallback(wrap, specifier) {
|
||||||
|
if (callbackMap.has(wrap)) {
|
||||||
|
const { importModuleDynamically } = callbackMap.get(wrap);
|
||||||
|
if (importModuleDynamically !== undefined) {
|
||||||
|
return importModuleDynamically(
|
||||||
|
specifier, wrapToModuleMap.get(wrap) || wrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING();
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitializeImportMetaObjectCallback(initializeImportMetaObject);
|
||||||
|
setImportModuleDynamicallyCallback(importModuleDynamicallyCallback);
|
||||||
|
|
||||||
let loaderResolve;
|
let loaderResolve;
|
||||||
exports.loaderPromise = new Promise((resolve, reject) => {
|
exports.loaderPromise = new Promise((resolve, reject) => {
|
||||||
loaderResolve = resolve;
|
loaderResolve = resolve;
|
||||||
@ -44,8 +46,6 @@ exports.loaderPromise = new Promise((resolve, reject) => {
|
|||||||
exports.ESMLoader = undefined;
|
exports.ESMLoader = undefined;
|
||||||
|
|
||||||
exports.setup = function() {
|
exports.setup = function() {
|
||||||
setInitializeImportMetaObjectCallback(initializeImportMetaObject);
|
|
||||||
|
|
||||||
let ESMLoader = new Loader();
|
let ESMLoader = new Loader();
|
||||||
const loaderPromise = (async () => {
|
const loaderPromise = (async () => {
|
||||||
const userLoader = process.binding('config').userLoader;
|
const userLoader = process.binding('config').userLoader;
|
||||||
@ -60,10 +60,5 @@ exports.setup = function() {
|
|||||||
})();
|
})();
|
||||||
loaderResolve(loaderPromise);
|
loaderResolve(loaderPromise);
|
||||||
|
|
||||||
setImportModuleDynamicallyCallback(async (referrer, specifier) => {
|
|
||||||
const loader = await loaderPromise;
|
|
||||||
return loader.import(specifier, normalizeReferrerURL(referrer));
|
|
||||||
});
|
|
||||||
|
|
||||||
exports.ESMLoader = ESMLoader;
|
exports.ESMLoader = ESMLoader;
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { isModuleNamespaceObject } = require('util').types;
|
||||||
const { URL } = require('internal/url');
|
const { URL } = require('internal/url');
|
||||||
const { isContext } = internalBinding('contextify');
|
const { isContext } = internalBinding('contextify');
|
||||||
const {
|
const {
|
||||||
@ -9,7 +10,7 @@ const {
|
|||||||
ERR_VM_MODULE_LINKING_ERRORED,
|
ERR_VM_MODULE_LINKING_ERRORED,
|
||||||
ERR_VM_MODULE_NOT_LINKED,
|
ERR_VM_MODULE_NOT_LINKED,
|
||||||
ERR_VM_MODULE_NOT_MODULE,
|
ERR_VM_MODULE_NOT_MODULE,
|
||||||
ERR_VM_MODULE_STATUS
|
ERR_VM_MODULE_STATUS,
|
||||||
} = require('internal/errors').codes;
|
} = require('internal/errors').codes;
|
||||||
const {
|
const {
|
||||||
getConstructorOf,
|
getConstructorOf,
|
||||||
@ -21,6 +22,7 @@ const { validateInt32, validateUint32 } = require('internal/validators');
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
ModuleWrap,
|
ModuleWrap,
|
||||||
|
callbackMap,
|
||||||
kUninstantiated,
|
kUninstantiated,
|
||||||
kInstantiating,
|
kInstantiating,
|
||||||
kInstantiated,
|
kInstantiated,
|
||||||
@ -43,8 +45,6 @@ const perContextModuleId = new WeakMap();
|
|||||||
const wrapMap = new WeakMap();
|
const wrapMap = new WeakMap();
|
||||||
const dependencyCacheMap = new WeakMap();
|
const dependencyCacheMap = new WeakMap();
|
||||||
const linkingStatusMap = new WeakMap();
|
const linkingStatusMap = new WeakMap();
|
||||||
// vm.SourceTextModule -> function
|
|
||||||
const initImportMetaMap = new WeakMap();
|
|
||||||
// ModuleWrap -> vm.SourceTextModule
|
// ModuleWrap -> vm.SourceTextModule
|
||||||
const wrapToModuleMap = new WeakMap();
|
const wrapToModuleMap = new WeakMap();
|
||||||
const defaultModuleName = 'vm:module';
|
const defaultModuleName = 'vm:module';
|
||||||
@ -63,7 +63,8 @@ class SourceTextModule {
|
|||||||
context,
|
context,
|
||||||
lineOffset = 0,
|
lineOffset = 0,
|
||||||
columnOffset = 0,
|
columnOffset = 0,
|
||||||
initializeImportMeta
|
initializeImportMeta,
|
||||||
|
importModuleDynamically,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
if (context !== undefined) {
|
if (context !== undefined) {
|
||||||
@ -96,13 +97,16 @@ class SourceTextModule {
|
|||||||
validateInt32(lineOffset, 'options.lineOffset');
|
validateInt32(lineOffset, 'options.lineOffset');
|
||||||
validateInt32(columnOffset, 'options.columnOffset');
|
validateInt32(columnOffset, 'options.columnOffset');
|
||||||
|
|
||||||
if (initializeImportMeta !== undefined) {
|
if (initializeImportMeta !== undefined &&
|
||||||
if (typeof initializeImportMeta === 'function') {
|
typeof initializeImportMeta !== 'function') {
|
||||||
initImportMetaMap.set(this, initializeImportMeta);
|
|
||||||
} else {
|
|
||||||
throw new ERR_INVALID_ARG_TYPE(
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
'options.initializeImportMeta', 'function', initializeImportMeta);
|
'options.initializeImportMeta', 'function', initializeImportMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (importModuleDynamically !== undefined &&
|
||||||
|
typeof importModuleDynamically !== 'function') {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
|
'options.importModuleDynamically', 'function', importModuleDynamically);
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrap = new ModuleWrap(src, url, context, lineOffset, columnOffset);
|
const wrap = new ModuleWrap(src, url, context, lineOffset, columnOffset);
|
||||||
@ -110,6 +114,22 @@ class SourceTextModule {
|
|||||||
linkingStatusMap.set(this, 'unlinked');
|
linkingStatusMap.set(this, 'unlinked');
|
||||||
wrapToModuleMap.set(wrap, this);
|
wrapToModuleMap.set(wrap, this);
|
||||||
|
|
||||||
|
callbackMap.set(wrap, {
|
||||||
|
initializeImportMeta,
|
||||||
|
importModuleDynamically: importModuleDynamically ? async (...args) => {
|
||||||
|
const m = await importModuleDynamically(...args);
|
||||||
|
if (isModuleNamespaceObject(m)) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
if (!m || !wrapMap.has(m))
|
||||||
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||||
|
const childLinkingStatus = linkingStatusMap.get(m);
|
||||||
|
if (childLinkingStatus === 'errored')
|
||||||
|
throw m.error;
|
||||||
|
return m.namespace;
|
||||||
|
} : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
Object.defineProperties(this, {
|
Object.defineProperties(this, {
|
||||||
url: { value: url, enumerable: true },
|
url: { value: url, enumerable: true },
|
||||||
context: { value: context, enumerable: true },
|
context: { value: context, enumerable: true },
|
||||||
@ -245,6 +265,7 @@ class SourceTextModule {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
SourceTextModule,
|
SourceTextModule,
|
||||||
initImportMetaMap,
|
wrapToModuleMap,
|
||||||
wrapToModuleMap
|
wrapMap,
|
||||||
|
linkingStatusMap,
|
||||||
};
|
};
|
||||||
|
34
lib/vm.js
34
lib/vm.js
@ -27,9 +27,12 @@ const {
|
|||||||
isContext: _isContext,
|
isContext: _isContext,
|
||||||
compileFunction: _compileFunction
|
compileFunction: _compileFunction
|
||||||
} = internalBinding('contextify');
|
} = internalBinding('contextify');
|
||||||
|
const { callbackMap } = internalBinding('module_wrap');
|
||||||
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
|
const {
|
||||||
const { isUint8Array } = require('internal/util/types');
|
ERR_INVALID_ARG_TYPE,
|
||||||
|
ERR_VM_MODULE_NOT_MODULE,
|
||||||
|
} = require('internal/errors').codes;
|
||||||
|
const { isModuleNamespaceObject, isUint8Array } = require('util').types;
|
||||||
const { validateInt32, validateUint32 } = require('internal/validators');
|
const { validateInt32, validateUint32 } = require('internal/validators');
|
||||||
const kParsingContext = Symbol('script parsing context');
|
const kParsingContext = Symbol('script parsing context');
|
||||||
|
|
||||||
@ -52,7 +55,8 @@ class Script extends ContextifyScript {
|
|||||||
columnOffset = 0,
|
columnOffset = 0,
|
||||||
cachedData,
|
cachedData,
|
||||||
produceCachedData = false,
|
produceCachedData = false,
|
||||||
[kParsingContext]: parsingContext
|
importModuleDynamically,
|
||||||
|
[kParsingContext]: parsingContext,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
if (typeof filename !== 'string') {
|
if (typeof filename !== 'string') {
|
||||||
@ -83,6 +87,28 @@ class Script extends ContextifyScript {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw e; /* node-do-not-add-exception-line */
|
throw e; /* node-do-not-add-exception-line */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (importModuleDynamically !== undefined) {
|
||||||
|
if (typeof importModuleDynamically !== 'function') {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE('options.importModuleDynamically',
|
||||||
|
'function',
|
||||||
|
importModuleDynamically);
|
||||||
|
}
|
||||||
|
const { wrapMap, linkingStatusMap } =
|
||||||
|
require('internal/vm/source_text_module');
|
||||||
|
callbackMap.set(this, { importModuleDynamically: async (...args) => {
|
||||||
|
const m = await importModuleDynamically(...args);
|
||||||
|
if (isModuleNamespaceObject(m)) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
if (!m || !wrapMap.has(m))
|
||||||
|
throw new ERR_VM_MODULE_NOT_MODULE();
|
||||||
|
const childLinkingStatus = linkingStatusMap.get(m);
|
||||||
|
if (childLinkingStatus === 'errored')
|
||||||
|
throw m.error;
|
||||||
|
return m.namespace;
|
||||||
|
} });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runInThisContext(options) {
|
runInThisContext(options) {
|
||||||
|
@ -446,6 +446,13 @@ Environment::trace_category_state() {
|
|||||||
return trace_category_state_;
|
return trace_category_state_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t Environment::get_next_module_id() {
|
||||||
|
return module_id_counter_++;
|
||||||
|
}
|
||||||
|
inline uint32_t Environment::get_next_script_id() {
|
||||||
|
return script_id_counter_++;
|
||||||
|
}
|
||||||
|
|
||||||
Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
|
Environment::ShouldNotAbortOnUncaughtScope::ShouldNotAbortOnUncaughtScope(
|
||||||
Environment* env)
|
Environment* env)
|
||||||
: env_(env) {
|
: env_(env) {
|
||||||
|
15
src/env.h
15
src/env.h
@ -47,6 +47,10 @@ struct nghttp2_rcbuf;
|
|||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
|
namespace contextify {
|
||||||
|
class ContextifyScript;
|
||||||
|
}
|
||||||
|
|
||||||
namespace fs {
|
namespace fs {
|
||||||
class FileHandleReadWrap;
|
class FileHandleReadWrap;
|
||||||
}
|
}
|
||||||
@ -674,7 +678,13 @@ class Environment {
|
|||||||
// List of id's that have been destroyed and need the destroy() cb called.
|
// List of id's that have been destroyed and need the destroy() cb called.
|
||||||
inline std::vector<double>* destroy_async_id_list();
|
inline std::vector<double>* destroy_async_id_list();
|
||||||
|
|
||||||
std::unordered_multimap<int, loader::ModuleWrap*> module_map;
|
std::unordered_multimap<int, loader::ModuleWrap*> hash_to_module_map;
|
||||||
|
std::unordered_map<uint32_t, loader::ModuleWrap*> id_to_module_map;
|
||||||
|
std::unordered_map<uint32_t, contextify::ContextifyScript*>
|
||||||
|
id_to_script_map;
|
||||||
|
|
||||||
|
inline uint32_t get_next_module_id();
|
||||||
|
inline uint32_t get_next_script_id();
|
||||||
|
|
||||||
std::unordered_map<std::string, const loader::PackageConfig>
|
std::unordered_map<std::string, const loader::PackageConfig>
|
||||||
package_json_cache;
|
package_json_cache;
|
||||||
@ -924,6 +934,9 @@ class Environment {
|
|||||||
|
|
||||||
std::shared_ptr<EnvironmentOptions> options_;
|
std::shared_ptr<EnvironmentOptions> options_;
|
||||||
|
|
||||||
|
uint32_t module_id_counter_ = 0;
|
||||||
|
uint32_t script_id_counter_ = 0;
|
||||||
|
|
||||||
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
|
AliasedBuffer<uint32_t, v8::Uint32Array> should_abort_on_uncaught_toggle_;
|
||||||
int should_not_abort_scope_counter_ = 0;
|
int should_not_abort_scope_counter_ = 0;
|
||||||
|
|
||||||
|
@ -33,7 +33,9 @@ using v8::Maybe;
|
|||||||
using v8::MaybeLocal;
|
using v8::MaybeLocal;
|
||||||
using v8::Module;
|
using v8::Module;
|
||||||
using v8::Nothing;
|
using v8::Nothing;
|
||||||
|
using v8::Number;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
|
using v8::PrimitiveArray;
|
||||||
using v8::Promise;
|
using v8::Promise;
|
||||||
using v8::ScriptCompiler;
|
using v8::ScriptCompiler;
|
||||||
using v8::ScriptOrigin;
|
using v8::ScriptOrigin;
|
||||||
@ -47,18 +49,22 @@ static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"};
|
|||||||
ModuleWrap::ModuleWrap(Environment* env,
|
ModuleWrap::ModuleWrap(Environment* env,
|
||||||
Local<Object> object,
|
Local<Object> object,
|
||||||
Local<Module> module,
|
Local<Module> module,
|
||||||
Local<String> url) : BaseObject(env, object) {
|
Local<String> url) :
|
||||||
|
BaseObject(env, object),
|
||||||
|
id_(env->get_next_module_id()) {
|
||||||
module_.Reset(env->isolate(), module);
|
module_.Reset(env->isolate(), module);
|
||||||
url_.Reset(env->isolate(), url);
|
url_.Reset(env->isolate(), url);
|
||||||
|
env->id_to_module_map.emplace(id_, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleWrap::~ModuleWrap() {
|
ModuleWrap::~ModuleWrap() {
|
||||||
HandleScope scope(env()->isolate());
|
HandleScope scope(env()->isolate());
|
||||||
Local<Module> module = module_.Get(env()->isolate());
|
Local<Module> module = module_.Get(env()->isolate());
|
||||||
auto range = env()->module_map.equal_range(module->GetIdentityHash());
|
env()->id_to_module_map.erase(id_);
|
||||||
|
auto range = env()->hash_to_module_map.equal_range(module->GetIdentityHash());
|
||||||
for (auto it = range.first; it != range.second; ++it) {
|
for (auto it = range.first; it != range.second; ++it) {
|
||||||
if (it->second == this) {
|
if (it->second == this) {
|
||||||
env()->module_map.erase(it);
|
env()->hash_to_module_map.erase(it);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,15 +72,21 @@ ModuleWrap::~ModuleWrap() {
|
|||||||
|
|
||||||
ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
|
ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
|
||||||
Local<Module> module) {
|
Local<Module> module) {
|
||||||
ModuleWrap* ret = nullptr;
|
auto range = env->hash_to_module_map.equal_range(module->GetIdentityHash());
|
||||||
auto range = env->module_map.equal_range(module->GetIdentityHash());
|
|
||||||
for (auto it = range.first; it != range.second; ++it) {
|
for (auto it = range.first; it != range.second; ++it) {
|
||||||
if (it->second->module_ == module) {
|
if (it->second->module_ == module) {
|
||||||
ret = it->second;
|
return it->second;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleWrap* ModuleWrap::GetFromID(Environment* env, uint32_t id) {
|
||||||
|
auto module_wrap_it = env->id_to_module_map.find(id);
|
||||||
|
if (module_wrap_it == env->id_to_module_map.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return module_wrap_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
||||||
@ -126,6 +138,11 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
TryCatch try_catch(isolate);
|
TryCatch try_catch(isolate);
|
||||||
Local<Module> module;
|
Local<Module> module;
|
||||||
|
|
||||||
|
Local<PrimitiveArray> host_defined_options =
|
||||||
|
PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
|
||||||
|
host_defined_options->Set(isolate, HostDefinedOptions::kType,
|
||||||
|
Number::New(isolate, ScriptType::kModule));
|
||||||
|
|
||||||
// compile
|
// compile
|
||||||
{
|
{
|
||||||
ScriptOrigin origin(url,
|
ScriptOrigin origin(url,
|
||||||
@ -136,7 +153,8 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
Local<Value>(), // source map URL
|
Local<Value>(), // source map URL
|
||||||
False(isolate), // is opaque (?)
|
False(isolate), // is opaque (?)
|
||||||
False(isolate), // is WASM
|
False(isolate), // is WASM
|
||||||
True(isolate)); // is ES6 module
|
True(isolate), // is ES Module
|
||||||
|
host_defined_options);
|
||||||
Context::Scope context_scope(context);
|
Context::Scope context_scope(context);
|
||||||
ScriptCompiler::Source source(source_text, origin);
|
ScriptCompiler::Source source(source_text, origin);
|
||||||
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
|
if (!ScriptCompiler::CompileModule(isolate, &source).ToLocal(&module)) {
|
||||||
@ -157,7 +175,10 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
|
|||||||
ModuleWrap* obj = new ModuleWrap(env, that, module, url);
|
ModuleWrap* obj = new ModuleWrap(env, that, module, url);
|
||||||
obj->context_.Reset(isolate, context);
|
obj->context_.Reset(isolate, context);
|
||||||
|
|
||||||
env->module_map.emplace(module->GetIdentityHash(), obj);
|
env->hash_to_module_map.emplace(module->GetIdentityHash(), obj);
|
||||||
|
|
||||||
|
host_defined_options->Set(isolate, HostDefinedOptions::kID,
|
||||||
|
Number::New(isolate, obj->id()));
|
||||||
|
|
||||||
that->SetIntegrityLevel(context, IntegrityLevel::kFrozen);
|
that->SetIntegrityLevel(context, IntegrityLevel::kFrozen);
|
||||||
args.GetReturnValue().Set(that);
|
args.GetReturnValue().Set(that);
|
||||||
@ -364,19 +385,14 @@ MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
|
|||||||
Environment* env = Environment::GetCurrent(context);
|
Environment* env = Environment::GetCurrent(context);
|
||||||
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
|
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
|
||||||
Isolate* isolate = env->isolate();
|
Isolate* isolate = env->isolate();
|
||||||
if (env->module_map.count(referrer->GetIdentityHash()) == 0) {
|
|
||||||
env->ThrowError("linking error, unknown module");
|
|
||||||
return MaybeLocal<Module>();
|
|
||||||
}
|
|
||||||
|
|
||||||
ModuleWrap* dependent = GetFromModule(env, referrer);
|
ModuleWrap* dependent = GetFromModule(env, referrer);
|
||||||
|
|
||||||
if (dependent == nullptr) {
|
if (dependent == nullptr) {
|
||||||
env->ThrowError("linking error, null dep");
|
env->ThrowError("linking error, null dep");
|
||||||
return MaybeLocal<Module>();
|
return MaybeLocal<Module>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Utf8Value specifier_utf8(env->isolate(), specifier);
|
Utf8Value specifier_utf8(isolate, specifier);
|
||||||
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
|
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
|
||||||
|
|
||||||
if (dependent->resolve_cache_.count(specifier_std) != 1) {
|
if (dependent->resolve_cache_.count(specifier_std) != 1) {
|
||||||
@ -402,7 +418,7 @@ MaybeLocal<Module> ModuleWrap::ResolveCallback(Local<Context> context,
|
|||||||
|
|
||||||
ModuleWrap* module;
|
ModuleWrap* module;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>());
|
ASSIGN_OR_RETURN_UNWRAP(&module, module_object, MaybeLocal<Module>());
|
||||||
return module->module_.Get(env->isolate());
|
return module->module_.Get(isolate);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -704,35 +720,56 @@ static MaybeLocal<Promise> ImportModuleDynamically(
|
|||||||
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
|
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
|
||||||
v8::EscapableHandleScope handle_scope(iso);
|
v8::EscapableHandleScope handle_scope(iso);
|
||||||
|
|
||||||
if (env->context() != context) {
|
|
||||||
auto maybe_resolver = Promise::Resolver::New(context);
|
|
||||||
Local<Promise::Resolver> resolver;
|
|
||||||
if (maybe_resolver.ToLocal(&resolver)) {
|
|
||||||
// TODO(jkrems): Turn into proper error object w/ code
|
|
||||||
Local<Value> error = v8::Exception::Error(
|
|
||||||
OneByteString(iso, "import() called outside of main context"));
|
|
||||||
if (resolver->Reject(context, error).IsJust()) {
|
|
||||||
return handle_scope.Escape(resolver.As<Promise>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MaybeLocal<Promise>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Local<Function> import_callback =
|
Local<Function> import_callback =
|
||||||
env->host_import_module_dynamically_callback();
|
env->host_import_module_dynamically_callback();
|
||||||
|
|
||||||
|
Local<PrimitiveArray> options = referrer->GetHostDefinedOptions();
|
||||||
|
if (options->Length() != HostDefinedOptions::kLength) {
|
||||||
|
Local<Promise::Resolver> resolver =
|
||||||
|
Promise::Resolver::New(context).ToLocalChecked();
|
||||||
|
resolver
|
||||||
|
->Reject(context,
|
||||||
|
v8::Exception::TypeError(FIXED_ONE_BYTE_STRING(
|
||||||
|
context->GetIsolate(), "Invalid host defined options")))
|
||||||
|
.ToChecked();
|
||||||
|
return handle_scope.Escape(resolver->GetPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Value> object;
|
||||||
|
|
||||||
|
int type = options->Get(iso, HostDefinedOptions::kType)
|
||||||
|
.As<Number>()
|
||||||
|
->Int32Value(context)
|
||||||
|
.ToChecked();
|
||||||
|
uint32_t id = options->Get(iso, HostDefinedOptions::kID)
|
||||||
|
.As<Number>()
|
||||||
|
->Uint32Value(context)
|
||||||
|
.ToChecked();
|
||||||
|
if (type == ScriptType::kScript) {
|
||||||
|
contextify::ContextifyScript* wrap = env->id_to_script_map.find(id)->second;
|
||||||
|
object = wrap->object();
|
||||||
|
} else if (type == ScriptType::kModule) {
|
||||||
|
ModuleWrap* wrap = ModuleWrap::GetFromID(env, id);
|
||||||
|
object = wrap->object();
|
||||||
|
} else {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
Local<Value> import_args[] = {
|
Local<Value> import_args[] = {
|
||||||
referrer->GetResourceName(),
|
object,
|
||||||
Local<Value>(specifier)
|
Local<Value>(specifier),
|
||||||
};
|
};
|
||||||
MaybeLocal<Value> maybe_result = import_callback->Call(context,
|
|
||||||
v8::Undefined(iso),
|
|
||||||
2,
|
|
||||||
import_args);
|
|
||||||
|
|
||||||
Local<Value> result;
|
Local<Value> result;
|
||||||
if (maybe_result.ToLocal(&result)) {
|
if (import_callback->Call(
|
||||||
|
context,
|
||||||
|
v8::Undefined(iso),
|
||||||
|
arraysize(import_args),
|
||||||
|
import_args).ToLocal(&result)) {
|
||||||
|
CHECK(result->IsPromise());
|
||||||
return handle_scope.Escape(result.As<Promise>());
|
return handle_scope.Escape(result.As<Promise>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return MaybeLocal<Promise>();
|
return MaybeLocal<Promise>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,17 @@ enum PackageMainCheck : bool {
|
|||||||
IgnoreMain = false
|
IgnoreMain = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ScriptType : int {
|
||||||
|
kScript,
|
||||||
|
kModule,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum HostDefinedOptions : int {
|
||||||
|
kType = 8,
|
||||||
|
kID = 9,
|
||||||
|
kLength = 10,
|
||||||
|
};
|
||||||
|
|
||||||
v8::Maybe<url::URL> Resolve(Environment* env,
|
v8::Maybe<url::URL> Resolve(Environment* env,
|
||||||
const std::string& specifier,
|
const std::string& specifier,
|
||||||
const url::URL& base,
|
const url::URL& base,
|
||||||
@ -38,6 +49,9 @@ class ModuleWrap : public BaseObject {
|
|||||||
tracker->TrackField("resolve_cache", resolve_cache_);
|
tracker->TrackField("resolve_cache", resolve_cache_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t id() { return id_; }
|
||||||
|
static ModuleWrap* GetFromID(node::Environment*, uint32_t id);
|
||||||
|
|
||||||
SET_MEMORY_INFO_NAME(ModuleWrap)
|
SET_MEMORY_INFO_NAME(ModuleWrap)
|
||||||
SET_SELF_SIZE(ModuleWrap)
|
SET_SELF_SIZE(ModuleWrap)
|
||||||
|
|
||||||
@ -69,12 +83,12 @@ class ModuleWrap : public BaseObject {
|
|||||||
v8::Local<v8::Module> referrer);
|
v8::Local<v8::Module> referrer);
|
||||||
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
|
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
|
||||||
|
|
||||||
|
|
||||||
Persistent<v8::Module> module_;
|
Persistent<v8::Module> module_;
|
||||||
Persistent<v8::String> url_;
|
Persistent<v8::String> url_;
|
||||||
bool linked_ = false;
|
bool linked_ = false;
|
||||||
std::unordered_map<std::string, Persistent<v8::Promise>> resolve_cache_;
|
std::unordered_map<std::string, Persistent<v8::Promise>> resolve_cache_;
|
||||||
Persistent<v8::Context> context_;
|
Persistent<v8::Context> context_;
|
||||||
|
uint32_t id_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace loader
|
} // namespace loader
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "node_contextify.h"
|
#include "node_contextify.h"
|
||||||
#include "node_context_data.h"
|
#include "node_context_data.h"
|
||||||
#include "node_errors.h"
|
#include "node_errors.h"
|
||||||
|
#include "module_wrap.h"
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
namespace contextify {
|
namespace contextify {
|
||||||
@ -49,8 +50,10 @@ using v8::Maybe;
|
|||||||
using v8::MaybeLocal;
|
using v8::MaybeLocal;
|
||||||
using v8::Name;
|
using v8::Name;
|
||||||
using v8::NamedPropertyHandlerConfiguration;
|
using v8::NamedPropertyHandlerConfiguration;
|
||||||
|
using v8::Number;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
using v8::ObjectTemplate;
|
using v8::ObjectTemplate;
|
||||||
|
using v8::PrimitiveArray;
|
||||||
using v8::PropertyAttribute;
|
using v8::PropertyAttribute;
|
||||||
using v8::PropertyCallbackInfo;
|
using v8::PropertyCallbackInfo;
|
||||||
using v8::PropertyDescriptor;
|
using v8::PropertyDescriptor;
|
||||||
@ -586,16 +589,7 @@ void ContextifyContext::IndexedPropertyDeleterCallback(
|
|||||||
args.GetReturnValue().Set(false);
|
args.GetReturnValue().Set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContextifyScript : public BaseObject {
|
void ContextifyScript::Init(Environment* env, Local<Object> target) {
|
||||||
private:
|
|
||||||
Persistent<UnboundScript> script_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
SET_NO_MEMORY_INFO()
|
|
||||||
SET_MEMORY_INFO_NAME(ContextifyScript)
|
|
||||||
SET_SELF_SIZE(ContextifyScript)
|
|
||||||
|
|
||||||
static void Init(Environment* env, Local<Object> target) {
|
|
||||||
HandleScope scope(env->isolate());
|
HandleScope scope(env->isolate());
|
||||||
Local<String> class_name =
|
Local<String> class_name =
|
||||||
FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");
|
FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript");
|
||||||
@ -612,8 +606,7 @@ class ContextifyScript : public BaseObject {
|
|||||||
env->set_script_context_constructor_template(script_tmpl);
|
env->set_script_context_constructor_template(script_tmpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
|
||||||
static void New(const FunctionCallbackInfo<Value>& args) {
|
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
Isolate* isolate = env->isolate();
|
Isolate* isolate = env->isolate();
|
||||||
Local<Context> context = env->context();
|
Local<Context> context = env->context();
|
||||||
@ -683,7 +676,23 @@ class ContextifyScript : public BaseObject {
|
|||||||
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
|
data + cached_data_buf->ByteOffset(), cached_data_buf->ByteLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptOrigin origin(filename, line_offset, column_offset);
|
Local<PrimitiveArray> host_defined_options =
|
||||||
|
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
|
||||||
|
host_defined_options->Set(isolate, loader::HostDefinedOptions::kType,
|
||||||
|
Number::New(isolate, loader::ScriptType::kScript));
|
||||||
|
host_defined_options->Set(isolate, loader::HostDefinedOptions::kID,
|
||||||
|
Number::New(isolate, contextify_script->id()));
|
||||||
|
|
||||||
|
ScriptOrigin origin(filename,
|
||||||
|
line_offset, // line offset
|
||||||
|
column_offset, // column offset
|
||||||
|
False(isolate), // is cross origin
|
||||||
|
Local<Integer>(), // script id
|
||||||
|
Local<Value>(), // source map URL
|
||||||
|
False(isolate), // is opaque (?)
|
||||||
|
False(isolate), // is WASM
|
||||||
|
False(isolate), // is ES Module
|
||||||
|
host_defined_options);
|
||||||
ScriptCompiler::Source source(code, origin, cached_data);
|
ScriptCompiler::Source source(code, origin, cached_data);
|
||||||
ScriptCompiler::CompileOptions compile_options =
|
ScriptCompiler::CompileOptions compile_options =
|
||||||
ScriptCompiler::kNoCompileOptions;
|
ScriptCompiler::kNoCompileOptions;
|
||||||
@ -737,14 +746,14 @@ class ContextifyScript : public BaseObject {
|
|||||||
contextify_script);
|
contextify_script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ContextifyScript::InstanceOf(Environment* env,
|
||||||
static bool InstanceOf(Environment* env, const Local<Value>& value) {
|
const Local<Value>& value) {
|
||||||
return !value.IsEmpty() &&
|
return !value.IsEmpty() &&
|
||||||
env->script_context_constructor_template()->HasInstance(value);
|
env->script_context_constructor_template()->HasInstance(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContextifyScript::CreateCachedData(
|
||||||
static void CreateCachedData(const FunctionCallbackInfo<Value>& args) {
|
const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
ContextifyScript* wrapped_script;
|
ContextifyScript* wrapped_script;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
|
ASSIGN_OR_RETURN_UNWRAP(&wrapped_script, args.Holder());
|
||||||
@ -763,8 +772,8 @@ class ContextifyScript : public BaseObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContextifyScript::RunInThisContext(
|
||||||
static void RunInThisContext(const FunctionCallbackInfo<Value>& args) {
|
const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
ContextifyScript* wrapped_script;
|
ContextifyScript* wrapped_script;
|
||||||
@ -791,7 +800,7 @@ class ContextifyScript : public BaseObject {
|
|||||||
TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script);
|
TRACING_CATEGORY_NODE2(vm, script), "RunInThisContext", wrapped_script);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RunInContext(const FunctionCallbackInfo<Value>& args) {
|
void ContextifyScript::RunInContext(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
ContextifyScript* wrapped_script;
|
ContextifyScript* wrapped_script;
|
||||||
@ -833,7 +842,8 @@ class ContextifyScript : public BaseObject {
|
|||||||
TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script);
|
TRACING_CATEGORY_NODE2(vm, script), "RunInContext", wrapped_script);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) {
|
void ContextifyScript::DecorateErrorStack(
|
||||||
|
Environment* env, const TryCatch& try_catch) {
|
||||||
Local<Value> exception = try_catch.Exception();
|
Local<Value> exception = try_catch.Exception();
|
||||||
|
|
||||||
if (!exception->IsObject())
|
if (!exception->IsObject())
|
||||||
@ -873,7 +883,7 @@ class ContextifyScript : public BaseObject {
|
|||||||
True(env->isolate()));
|
True(env->isolate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool EvalMachine(Environment* env,
|
bool ContextifyScript::EvalMachine(Environment* env,
|
||||||
const int64_t timeout,
|
const int64_t timeout,
|
||||||
const bool display_errors,
|
const bool display_errors,
|
||||||
const bool break_on_sigint,
|
const bool break_on_sigint,
|
||||||
@ -943,11 +953,17 @@ class ContextifyScript : public BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ContextifyScript(Environment* env, Local<Object> object)
|
ContextifyScript::ContextifyScript(Environment* env, Local<Object> object)
|
||||||
: BaseObject(env, object) {
|
: BaseObject(env, object),
|
||||||
|
id_(env->get_next_script_id()) {
|
||||||
MakeWeak();
|
MakeWeak();
|
||||||
|
env->id_to_script_map.emplace(id_, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ContextifyScript::~ContextifyScript() {
|
||||||
|
env()->id_to_script_map.erase(id_);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void ContextifyContext::CompileFunction(
|
void ContextifyContext::CompileFunction(
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "node_internals.h"
|
#include "node_internals.h"
|
||||||
#include "node_context_data.h"
|
#include "node_context_data.h"
|
||||||
|
#include "base_object-inl.h"
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
namespace contextify {
|
namespace contextify {
|
||||||
@ -102,6 +103,37 @@ class ContextifyContext {
|
|||||||
Persistent<v8::Context> context_;
|
Persistent<v8::Context> context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ContextifyScript : public BaseObject {
|
||||||
|
public:
|
||||||
|
SET_NO_MEMORY_INFO()
|
||||||
|
SET_MEMORY_INFO_NAME(ContextifyScript)
|
||||||
|
SET_SELF_SIZE(ContextifyScript)
|
||||||
|
|
||||||
|
ContextifyScript(Environment* env, v8::Local<v8::Object> object);
|
||||||
|
~ContextifyScript();
|
||||||
|
|
||||||
|
static void Init(Environment* env, v8::Local<v8::Object> target);
|
||||||
|
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static bool InstanceOf(Environment* env, const v8::Local<v8::Value>& args);
|
||||||
|
static void CreateCachedData(
|
||||||
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void RunInThisContext(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void RunInContext(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
static void DecorateErrorStack(Environment* env,
|
||||||
|
const v8::TryCatch& try_catch);
|
||||||
|
static bool EvalMachine(Environment* env,
|
||||||
|
const int64_t timeout,
|
||||||
|
const bool display_errors,
|
||||||
|
const bool break_on_sigint,
|
||||||
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
|
inline uint32_t id() { return id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
node::Persistent<v8::UnboundScript> script_;
|
||||||
|
uint32_t id_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace contextify
|
} // namespace contextify
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
|
@ -75,38 +75,3 @@ function expectFsNamespace(result) {
|
|||||||
expectMissingModuleError(import("node:fs"));
|
expectMissingModuleError(import("node:fs"));
|
||||||
expectMissingModuleError(import('http://example.com/foo.js'));
|
expectMissingModuleError(import('http://example.com/foo.js'));
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// vm.runInThisContext:
|
|
||||||
// * Supports built-ins, always
|
|
||||||
// * Supports imports if the script has a known defined origin
|
|
||||||
(function testRunInThisContext() {
|
|
||||||
// Succeeds because it's got an valid base url
|
|
||||||
expectFsNamespace(vm.runInThisContext(`import("fs")`, {
|
|
||||||
filename: __filename,
|
|
||||||
}));
|
|
||||||
expectOkNamespace(vm.runInThisContext(`import("${relativePath}")`, {
|
|
||||||
filename: __filename,
|
|
||||||
}));
|
|
||||||
// Rejects because it's got an invalid referrer URL.
|
|
||||||
// TODO(jkrems): Arguably the first two (built-in + absolute URL) could work
|
|
||||||
// with some additional effort.
|
|
||||||
expectInvalidReferrerError(vm.runInThisContext('import("fs")'));
|
|
||||||
expectInvalidReferrerError(vm.runInThisContext(`import("${targetURL}")`));
|
|
||||||
expectInvalidReferrerError(vm.runInThisContext(`import("${relativePath}")`));
|
|
||||||
})();
|
|
||||||
|
|
||||||
// vm.runInNewContext is currently completely unsupported, pending well-defined
|
|
||||||
// semantics for per-context/realm module maps in node.
|
|
||||||
(function testRunInNewContext() {
|
|
||||||
// Rejects because it's running in the wrong context
|
|
||||||
expectInvalidContextError(
|
|
||||||
vm.runInNewContext(`import("${targetURL}")`, undefined, {
|
|
||||||
filename: __filename,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rejects because it's running in the wrong context
|
|
||||||
expectInvalidContextError(vm.runInNewContext(`import("fs")`, undefined, {
|
|
||||||
filename: __filename,
|
|
||||||
}));
|
|
||||||
})();
|
|
||||||
|
@ -11,4 +11,4 @@ const list = process.moduleLoadList.slice();
|
|||||||
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
assert(list.length <= 76, list);
|
assert(list.length <= 77, list);
|
||||||
|
@ -5,11 +5,9 @@
|
|||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const { SourceTextModule, createContext } = require('vm');
|
const { Script, SourceTextModule, createContext } = require('vm');
|
||||||
|
|
||||||
const finished = common.mustCall();
|
async function testNoCallback() {
|
||||||
|
|
||||||
(async function() {
|
|
||||||
const m = new SourceTextModule('import("foo")', { context: createContext() });
|
const m = new SourceTextModule('import("foo")', { context: createContext() });
|
||||||
await m.link(common.mustNotCall());
|
await m.link(common.mustNotCall());
|
||||||
m.instantiate();
|
m.instantiate();
|
||||||
@ -19,8 +17,67 @@ const finished = common.mustCall();
|
|||||||
await result;
|
await result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
threw = true;
|
threw = true;
|
||||||
assert.strictEqual(err.message, 'import() called outside of main context');
|
assert.strictEqual(err.code, 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING');
|
||||||
}
|
}
|
||||||
assert(threw);
|
assert(threw);
|
||||||
finished();
|
}
|
||||||
}());
|
|
||||||
|
async function test() {
|
||||||
|
const foo = new SourceTextModule('export const a = 1;');
|
||||||
|
await foo.link(common.mustNotCall());
|
||||||
|
foo.instantiate();
|
||||||
|
await foo.evaluate();
|
||||||
|
|
||||||
|
{
|
||||||
|
const s = new Script('import("foo")', {
|
||||||
|
importModuleDynamically: common.mustCall((specifier, wrap) => {
|
||||||
|
assert.strictEqual(specifier, 'foo');
|
||||||
|
assert.strictEqual(wrap, s);
|
||||||
|
return foo;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = s.runInThisContext();
|
||||||
|
assert.strictEqual(foo.namespace, await result);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const m = new SourceTextModule('import("foo")', {
|
||||||
|
importModuleDynamically: common.mustCall((specifier, wrap) => {
|
||||||
|
assert.strictEqual(specifier, 'foo');
|
||||||
|
assert.strictEqual(wrap, m);
|
||||||
|
return foo;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
const { result } = await m.evaluate();
|
||||||
|
assert.strictEqual(foo.namespace, await result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testInvalid() {
|
||||||
|
const m = new SourceTextModule('import("foo")', {
|
||||||
|
importModuleDynamically: common.mustCall((specifier, wrap) => {
|
||||||
|
return 5;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
await m.link(common.mustNotCall());
|
||||||
|
m.instantiate();
|
||||||
|
const { result } = await m.evaluate();
|
||||||
|
await result.catch(common.mustCall((e) => {
|
||||||
|
assert.strictEqual(e.code, 'ERR_VM_MODULE_NOT_MODULE');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const done = common.mustCallAtLeast(3);
|
||||||
|
(async function() {
|
||||||
|
await testNoCallback();
|
||||||
|
done();
|
||||||
|
|
||||||
|
await test();
|
||||||
|
done();
|
||||||
|
|
||||||
|
await testInvalid();
|
||||||
|
done();
|
||||||
|
}()).then(common.mustCall());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user