node/lib/internal/process/esm_loader.js
Maël Nison 12f6da731c
esm: leverage loaders when resolving subsequent loaders
PR-URL: https://github.com/nodejs/node/pull/43772
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
2022-12-17 04:23:11 +00:00

106 lines
2.9 KiB
JavaScript

'use strict';
const {
ArrayIsArray,
ArrayPrototypePushApply,
} = primordials;
const { ESMLoader } = require('internal/modules/esm/loader');
const {
hasUncaughtExceptionCaptureCallback,
} = require('internal/process/execution');
const { pathToFileURL } = require('internal/url');
const { kEmptyObject } = require('internal/util');
const esmLoader = new ESMLoader();
exports.esmLoader = esmLoader;
// Module.runMain() causes loadESM() to re-run (which it should do); however, this should NOT cause
// ESM to be re-initialised; doing so causes duplicate custom loaders to be added to the public
// esmLoader.
let isESMInitialized = false;
/**
* Causes side-effects: user-defined loader hooks are added to esmLoader.
* @returns {void}
*/
async function initializeLoader() {
if (isESMInitialized) { return; }
const { getOptionValue } = require('internal/options');
const customLoaders = getOptionValue('--experimental-loader');
const preloadModules = getOptionValue('--import');
let cwd;
try {
cwd = process.cwd() + '/';
} catch {
cwd = '/';
}
const internalEsmLoader = new ESMLoader();
const allLoaders = [];
const parentURL = pathToFileURL(cwd).href;
for (let i = 0; i < customLoaders.length; i++) {
const customLoader = customLoaders[i];
// Importation must be handled by internal loader to avoid polluting user-land
const keyedExportsSublist = await internalEsmLoader.import(
[customLoader],
parentURL,
kEmptyObject,
);
internalEsmLoader.addCustomLoaders(keyedExportsSublist);
ArrayPrototypePushApply(allLoaders, keyedExportsSublist);
}
// Hooks must then be added to external/public loader
// (so they're triggered in userland)
esmLoader.addCustomLoaders(allLoaders);
esmLoader.preload();
// Preload after loaders are added so they can be used
if (preloadModules?.length) {
await loadModulesInIsolation(parentURL, preloadModules, allLoaders);
}
isESMInitialized = true;
}
function loadModulesInIsolation(parentURL, specifiers, loaders = []) {
if (!ArrayIsArray(specifiers) || specifiers.length === 0) { return; }
// A separate loader instance is necessary to avoid cross-contamination
// between internal Node.js and userland. For example, a module with internal
// state (such as a counter) should be independent.
const internalEsmLoader = new ESMLoader();
internalEsmLoader.addCustomLoaders(loaders);
internalEsmLoader.preload();
// Importation must be handled by internal loader to avoid poluting userland
return internalEsmLoader.import(
specifiers,
parentURL,
kEmptyObject,
);
}
exports.loadESM = async function loadESM(callback) {
try {
await initializeLoader();
await callback(esmLoader);
} catch (err) {
if (hasUncaughtExceptionCaptureCallback()) {
process._fatalException(err);
return;
}
internalBinding('errors').triggerUncaughtException(
err,
true /* fromPromise */
);
}
};