Skip to content

Commit

Permalink
esm: refactor DefaultModuleLoader
Browse files Browse the repository at this point in the history
Fixes #48515
Fixes #48439
  • Loading branch information
izaakschroeder committed Jun 30, 2023
1 parent 951da52 commit 76f775e
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 147 deletions.
69 changes: 59 additions & 10 deletions lib/internal/modules/esm/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
// [2] `validate...()`s throw the wrong error


class Hooks {
#chains = {
function getDefaultChains() {
return {
/**
* Prior to ESM loading. These are called once before any modules are started.
* @private
Expand Down Expand Up @@ -115,27 +115,46 @@ class Hooks {
},
],
};
}

class Hooks {
#chains;

// Cache URLs we've already validated to avoid repeated validation
#validatedUrls = new SafeSet();
#validatedUrls;

constructor(chains = getDefaultChains(), validatedUrls = new SafeSet()) {
this.#chains = chains;
this.#validatedUrls = validatedUrls;
}

/**
* Import and register custom/user-defined module loader hook(s).
* @param {string} urlOrSpecifier
* @param {string} parentURL
*/
async register(urlOrSpecifier, parentURL) {
const moduleLoader = require('internal/process/esm_loader').esmLoader;

const keyedExports = await moduleLoader.import(
const esmLoader = require('internal/process/esm_loader').esmLoader;
const keyedExports = await esmLoader.import(
urlOrSpecifier,
parentURL,
kEmptyObject,
);

this.addCustomLoader(urlOrSpecifier, keyedExports);
}

allowImportMetaResolve() {
return false;
}

getChains() {
return this.#chains;
}

getValidatedUrls() {
return this.#validatedUrls;
}

/**
* Collect custom/user-defined module loader hook(s).
* After all hooks have been collected, the global preload hook(s) must be initialized.
Expand Down Expand Up @@ -221,15 +240,16 @@ class Hooks {
parentURL,
importAssertions = { __proto__: null },
) {
const chain = this.#chains.resolve;
throwIfInvalidParentURL(parentURL);

const chain = this.#chains.resolve;
const context = {
conditions: getDefaultConditions(),
importAssertions,
parentURL,
};
const meta = {
hooks: this,
chainFinished: null,
context,
hookErrIdentifier: '',
Expand Down Expand Up @@ -346,6 +366,7 @@ class Hooks {
async load(url, context = {}) {
const chain = this.#chains.load;
const meta = {
hooks: this,
chainFinished: null,
context,
hookErrIdentifier: '',
Expand Down Expand Up @@ -528,7 +549,17 @@ class HooksProxy {
debug('wait for signal from worker');
AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0);
const response = this.#worker.receiveMessageSync();
if (response.message.status === 'exit') { return; }
if (response.message.status === 'exit') {
// TODO: I do not understand why this is necessary.
// node \
// --no-warnings --experimental-loader 'data:text/javascript,process.exit(42)'
// ./test/fixtures/empty.js
// Does not trigger `this.#worker.on('exit', process.exit);`.
// I think it is because `makeSyncRequest` keeps waiting to see another
// message and blocks the thread from ANY other activity including the exit.
process.exit(response.message.body);
return;
}
const { preloadScripts } = this.#unwrapMessage(response);
this.#executePreloadScripts(preloadScripts);
}
Expand Down Expand Up @@ -749,7 +780,25 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
ObjectAssign(meta.context, context);
}

const output = await hook(arg0, meta.context, nextNextHook);
const esmLoader = require('internal/process/esm_loader').esmLoader;

const chains = meta.hooks.getChains();
const load = chain === chains.load ? chains.load.slice(0, generatedHookIndex) : chains.load;
const resolve = chain === chains.resolve ? chains.resolve.slice(0, generatedHookIndex) : chains.resolve;
let output;
if (load.length > 0 && resolve.length > 0) {
const nextChains = {
load,
resolve,
globalPreload: chains.globalPreload,
};
const delegate = new Hooks(nextChains, meta.hooks.getValidatedUrls());
output = await esmLoader.withDelegate(delegate, () => {
return hook(arg0, meta.context, nextNextHook);
});
} else {
output = await hook(arg0, meta.context, nextNextHook);
}

validateOutput(outputErrIdentifier, output);

Expand Down
12 changes: 10 additions & 2 deletions lib/internal/modules/esm/initialize_import_meta.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
'use strict';

const { Symbol } = primordials;

const { getOptionValue } = require('internal/options');
const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve');
const kResolveSync = Symbol('sync');

const importAssertions = {
[kResolveSync]: true,
};

/**
* Generate a function to be used as import.meta.resolve for a particular module.
Expand All @@ -14,7 +21,7 @@ function createImportMetaResolve(defaultParentUrl, loader) {
let url;

try {
({ url } = loader.resolve(specifier, parentUrl));
({ url } = loader.resolve(specifier, parentUrl, importAssertions));
} catch (error) {
if (error?.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
({ url } = error);
Expand All @@ -38,7 +45,7 @@ function initializeImportMeta(meta, context, loader) {
const { url } = context;

// Alphabetical
if (experimentalImportMetaResolve && loader.loaderType !== 'internal') {
if (experimentalImportMetaResolve && loader.allowImportMetaResolve()) {
meta.resolve = createImportMetaResolve(url, loader);
}

Expand All @@ -49,4 +56,5 @@ function initializeImportMeta(meta, context, loader) {

module.exports = {
initializeImportMeta,
kResolveSync,
};
Loading

0 comments on commit 76f775e

Please sign in to comment.