Skip to content

Commit

Permalink
fixup! fixup! vm: support using the default loader to handle dynamic …
Browse files Browse the repository at this point in the history
…import()
  • Loading branch information
joyeecheung committed Dec 21, 2023
1 parent f99b2d0 commit 7478354
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 30 deletions.
8 changes: 5 additions & 3 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -1684,8 +1684,8 @@ main context:
1. The module being resolved would be relative to the `filename` option passed
to `vm.Script` or `vm.compileFunction()`. The resolution can work with a
`filename` that's either an absolute path or a URL string. If `filename` is
a string that's neither an absolute path or a URL string, or if it's
undefined, the resolution will be relative to the current working directory
a string that's neither an absolute path or a URL, or if it's undefined,
the resolution will be relative to the current working directory
of the process. In the case of `vm.createContext()`, the resolution is always
relative to the current working directory since this option is only used when
there isn't a referrer script or module.
Expand All @@ -1695,7 +1695,9 @@ main context:
3. For any given `filename` that resolves to a specific path, once the process
manages to load a particular module from that path, the result may be cached,
and subsequent load of the same module from the same path would return the
same thing. There is currently no way to bypass the caching behavior.
same thing. If the `filename` is a URL string, the cache would not be hit
if it has different search parameters. For non-URL strings as `filename`s
there is currently no way to bypass the caching behavior.
### When `importModuleDynamically` is a function
Expand Down
10 changes: 9 additions & 1 deletion lib/internal/modules/esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ function initializeImportMetaObject(symbol, meta) {
const getCascadedLoader = getLazy(
() => require('internal/process/esm_loader').esmLoader,
);

/**
* Proxy the dynamic import to the default loader.
* @param {string} specifier - The module specifier string.
* @param {Record<string, string>} attributes - The import attributes object.
* @param {string|null|undefined} referrerName - name of the referrer.
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
*/
function defaultImportModuleDynamically(specifier, attributes, referrerName) {
const parentURL = normalizeReferrerURL(referrerName);
return getCascadedLoader().import(specifier, parentURL, attributes);
Expand All @@ -215,7 +223,7 @@ function defaultImportModuleDynamically(specifier, attributes, referrerName) {
* @param {symbol} referrerSymbol - Referrer symbol of the registered script, function, module, or contextified object.
* @param {string} specifier - The module specifier string.
* @param {Record<string, string>} attributes - The import attributes object.
* @param {string|null} referrerName - name of the referrer.
* @param {string|null|undefined} referrerName - name of the referrer.
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
* @throws {ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING} - If the callback function is missing.
*/
Expand Down
32 changes: 18 additions & 14 deletions lib/internal/modules/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ const { validateString } = require('internal/validators');
const fs = require('fs'); // Import all of `fs` so that it can be monkey-patched.
const internalFS = require('internal/fs/utils');
const path = require('path');
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
const { pathToFileURL, fileURLToPath } = require('internal/url');
const assert = require('internal/assert');

const { getOptionValue } = require('internal/options');
const { setOwnProperty } = require('internal/util');
const { inspect } = require('internal/util/inspect');

const {
privateSymbols: {
Expand Down Expand Up @@ -290,29 +292,31 @@ function addBuiltinLibsToObject(object, dummyModuleName) {

/**
* Normalize the referrer name as a URL.
* If it's an absolute path or a file:// it's normalized as a file:// URL.
* Otherwise it's returned as undefined;
* @param {string | null | undefined | false | any } referrerName
* If it's a string containing an absolute path or a URL it's normalized as
* a URL string.
* Otherwise it's returned as undefined.
* @param {string | null | undefined} referrerName
* @returns {string | undefined}
*/
function normalizeReferrerURL(referrerName) {
if (typeof referrerName !== 'string') {
if (referrerName === null || referrerName === undefined) {
return undefined;
}

if (StringPrototypeStartsWith(referrerName, 'file://')) {
return referrerName;
}
if (typeof referrerName === 'string') {
if (path.isAbsolute(referrerName)) {
return pathToFileURL(referrerName).href;
}

if (path.isAbsolute(referrerName)) {
return pathToFileURL(referrerName).href;
}
if (StringPrototypeStartsWith(referrerName, 'file://') ||
URLCanParse(referrerName)) {
return referrerName;
}

if (URLCanParse(referrerName)) {
return new URL(referrerName).href;
return undefined;
}

return undefined;
assert.fail('Unreachable code reached by ' + inspect(referrerName));
}

module.exports = {
Expand Down
10 changes: 6 additions & 4 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ function prepareWorkerThreadExecution() {
}

function prepareShadowRealmExecution() {
const { registerRealm } = require('internal/modules/esm/utils');
// Patch the process object with legacy properties and normalizations.
// Do not expand argv1 as it is not available in ShadowRealm.
patchProcessObject(false);
Expand All @@ -86,9 +85,12 @@ function prepareShadowRealmExecution() {
} = internalBinding('symbols');

// For ShadowRealm.prototype.importValue(), the referrer name is
// always null which would be coerced to an undefined parentURL.
// when we use vm_dynamic_import_default_internal
// to proxy the request to the default handler.
// always null, so the native ImportModuleDynamically() callback would
// always fallback to look up the host-defined option from the
// global object using host_defined_option_symbol. Using
// vm_dynamic_import_default_internal as the host-defined option
// instructs the JS-land importModuleDynamicallyCallback() to
// proxy the request to defaultImportModuleDynamically().
globalThis[host_defined_option_symbol] =
vm_dynamic_import_default_internal;
}
Expand Down
11 changes: 3 additions & 8 deletions test/es-module/test-vm-main-context-default-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const tmpdir = require('../common/tmpdir');
const fixtures = require('../common/fixtures');
const url = require('url');
const fs = require('fs');
const { inspect } = require('util');
const {
compileFunction,
Script,
Expand All @@ -29,7 +28,6 @@ async function testNotFoundErrors(options) {
// Use try-catch for better async stack traces in the logs.
await assert.rejects(script.runInThisContext(), { code: 'ERR_MODULE_NOT_FOUND' });

result = 'uncaught-fallback';
const imported = compileFunction('return import("./message.mjs")', [], options)();
// Use try-catch for better async stack traces in the logs.
await assert.rejects(imported, { code: 'ERR_MODULE_NOT_FOUND' });
Expand Down Expand Up @@ -69,9 +67,7 @@ async function main() {
{
// Importing with absolute path as filename.
tmpdir.refresh();
// Note that we must use different file names for different tests otherwise
// we would get cached results.
const filename = tmpdir.resolve('index1.js');
const filename = tmpdir.resolve('index.js');
const options = {
filename,
importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER
Expand All @@ -83,9 +79,8 @@ async function main() {
{
// Importing with file:// URL as filename.
tmpdir.refresh();
// Note that we must use different file names for different tests otherwise
// we would get cached results.
const filename = url.pathToFileURL(tmpdir.resolve('index2.js')).href;
// We use a search parameter to bypass caching.
const filename = url.pathToFileURL(tmpdir.resolve('index.js')).href + '?t=1';
const options = {
filename,
importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER
Expand Down

0 comments on commit 7478354

Please sign in to comment.