From 84fd829b45757131363bcccd3c80f68d36ad0a0d Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 21 Apr 2020 16:43:58 -0500 Subject: [PATCH] vm: add importModuleDynamically option to compileFunction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/nodejs/node/issues/31860 PR-URL: https://github.com/nodejs/node/pull/32985 Reviewed-By: Anna Henningsen Reviewed-By: Michaƫl Zasso --- doc/api/vm.md | 16 ++++++++++- lib/internal/modules/cjs/loader.js | 40 ++++++++------------------- lib/vm.js | 17 ++++++++++++ test/parallel/test-vm-module-basic.js | 19 ++++++++++++- tools/doc/type-parser.js | 1 + 5 files changed, 63 insertions(+), 30 deletions(-) diff --git a/doc/api/vm.md b/doc/api/vm.md index ec731ffd36c6a0..e959ebc66ff24e 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -88,7 +88,7 @@ changes: This option is part of the experimental modules API, and should not be considered stable. * `specifier` {string} specifier passed to `import()` - * `module` {vm.Module} + * `script` {vm.Script} * Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is recommended in order to take advantage of error tracking, and to avoid issues with namespaces that contain `then` function exports. @@ -773,6 +773,10 @@ const vm = require('vm'); ## `vm.compileFunction(code[, params[, options]])` * `code` {string} The body of the function to compile. @@ -795,6 +799,16 @@ added: v10.10.0 * `contextExtensions` {Object[]} An array containing a collection of context extensions (objects wrapping the current scope) to be applied while compiling. **Default:** `[]`. + * `importModuleDynamically` {Function} Called during evaluation of this module + when `import()` is called. If this option is not specified, calls to + `import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. + This option is part of the experimental modules API, and should not be + considered stable. + * `specifier` {string} specifier passed to `import()` + * `function` {Function} + * Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is + recommended in order to take advantage of error tracking, and to avoid + issues with namespaces that contain `then` function exports. * Returns: {Function} Compiles the given code into the provided context (if no context is diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 6ead20937edfa2..ba91ac2232f190 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -73,7 +73,6 @@ const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); const manifest = getOptionValue('--experimental-policy') ? require('internal/process/policy').manifest : null; -const { compileFunction } = internalBinding('contextify'); // Whether any user-provided CJS modules had been loaded (executed). // Used for internal assertions. @@ -1045,40 +1044,25 @@ function wrapSafe(filename, content, cjsModuleInstance) { }, }); } - let compiled; try { - compiled = compileFunction( - content, + return vm.compileFunction(content, [ + 'exports', + 'require', + 'module', + '__filename', + '__dirname', + ], { filename, - 0, - 0, - undefined, - false, - undefined, - [], - [ - 'exports', - 'require', - 'module', - '__filename', - '__dirname', - ] - ); + importModuleDynamically(specifier) { + const loader = asyncESM.ESMLoader; + return loader.import(specifier, normalizeReferrerURL(filename)); + }, + }); } catch (err) { if (process.mainModule === cjsModuleInstance) enrichCJSError(err); throw err; } - - const { callbackMap } = internalBinding('module_wrap'); - callbackMap.set(compiled.cacheKey, { - importModuleDynamically: async (specifier) => { - const loader = asyncESM.ESMLoader; - return loader.import(specifier, normalizeReferrerURL(filename)); - } - }); - - return compiled.function; } // Run the file contents in the correct scope or sandbox. Expose diff --git a/lib/vm.js b/lib/vm.js index c2d8908703b396..cffca355720dae 100644 --- a/lib/vm.js +++ b/lib/vm.js @@ -313,6 +313,7 @@ function compileFunction(code, params, options = {}) { produceCachedData = false, parsingContext = undefined, contextExtensions = [], + importModuleDynamically, } = options; validateString(filename, 'options.filename'); @@ -360,6 +361,22 @@ function compileFunction(code, params, options = {}) { result.function.cachedData = result.cachedData; } + if (importModuleDynamically !== undefined) { + if (typeof importModuleDynamically !== 'function') { + throw new ERR_INVALID_ARG_TYPE('options.importModuleDynamically', + 'function', + importModuleDynamically); + } + const { importModuleDynamicallyWrap } = + require('internal/vm/module'); + const { callbackMap } = internalBinding('module_wrap'); + const wrapped = importModuleDynamicallyWrap(importModuleDynamically); + const func = result.function; + callbackMap.set(result.cacheKey, { + importModuleDynamically: (s, _k) => wrapped(s, func), + }); + } + return result.function; } diff --git a/test/parallel/test-vm-module-basic.js b/test/parallel/test-vm-module-basic.js index 86e13f8b12bfa8..155524f7e7a176 100644 --- a/test/parallel/test-vm-module-basic.js +++ b/test/parallel/test-vm-module-basic.js @@ -8,7 +8,8 @@ const { Module, SourceTextModule, SyntheticModule, - createContext + createContext, + compileFunction, } = require('vm'); const util = require('util'); @@ -147,3 +148,19 @@ const util = require('util'); name: 'TypeError' }); } + +// Test compileFunction importModuleDynamically +{ + const module = new SyntheticModule([], () => {}); + module.link(() => {}); + const f = compileFunction('return import("x")', [], { + importModuleDynamically(specifier, referrer) { + assert.strictEqual(specifier, 'x'); + assert.strictEqual(referrer, f); + return module; + }, + }); + f().then((ns) => { + assert.strictEqual(ns, module.namespace); + }); +} diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js index 02b59d37ffd278..b244564d8f8ebf 100644 --- a/tools/doc/type-parser.js +++ b/tools/doc/type-parser.js @@ -148,6 +148,7 @@ const customTypesMap = { 'URLSearchParams': 'url.html#url_class_urlsearchparams', 'vm.Module': 'vm.html#vm_class_vm_module', + 'vm.Script': 'vm.html#vm_class_vm_script', 'vm.SourceTextModule': 'vm.html#vm_class_vm_sourcetextmodule', 'MessagePort': 'worker_threads.html#worker_threads_class_messageport',