mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
- This updates the comments that assume loader hooks must be async
- Differentiate the sync/async loader hook paths in naming
`#customizations` is now `#asyncLoaderHooks` to make it clear
it's from the async APIs.
- Differentiate the paths running on the loader hook thread
(affects the loading of async other loader hooks and are async)
v.s. paths on the main thread calling out to code on the loader
hook thread (do not handle loading of other async loader hooks, and
can be sync by blocking).
- `Hooks` is now `AsyncLoaderHooksOnLoaderHookWorker`
- `CustomizedModuleLoader` is now
`AsyncLoaderHooksProxiedToLoaderHookWorker` and moved into
`lib/internal/modules/esm/hooks.js` as it implements the same
interface as `AsyncLoaderHooksOnLoaderHookWorker`
- `HooksProxy` is now `AsyncLoaderHookWorker`
- Adjust the JSDoc accordingly
- Clarify the "loader worker" as the "async loader hook worker"
i.e. when there's no _async_ loader hook registered, there won't
be this worker, to avoid the misconception that this worker
is spawned unconditionally.
- The code run on the loader hook worker to process
`--experimental-loader` is moved into
`lib/internal/modules/esm/worker.js` for clarity.
- The initialization configuration `forceDefaultLoader` is split
into `shouldSpawnLoaderHookWorker` and `shouldPreloadModules`
as those can be separate.
- `--experimental-vm-modules` is now processed during pre-execution
and no longer part of the initialization of the built-in ESM
loader, as it only exposes the vm APIs of ESM, and is unrelated
to built-in ESM loading.
PR-URL: https://github.com/nodejs/node/pull/60278
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
171 lines
6.2 KiB
JavaScript
171 lines
6.2 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
StringPrototypeEndsWith,
|
|
globalThis,
|
|
} = primordials;
|
|
|
|
const { getNearestParentPackageJSONType } = internalBinding('modules');
|
|
const { getOptionValue } = require('internal/options');
|
|
const path = require('path');
|
|
const { pathToFileURL, URL } = require('internal/url');
|
|
const { kEmptyObject, getCWDURL } = require('internal/util');
|
|
const {
|
|
hasUncaughtExceptionCaptureCallback,
|
|
} = require('internal/process/execution');
|
|
const {
|
|
triggerUncaughtException,
|
|
} = internalBinding('errors');
|
|
const {
|
|
privateSymbols: {
|
|
entry_point_promise_private_symbol,
|
|
},
|
|
} = internalBinding('util');
|
|
/**
|
|
* Get the absolute path to the main entry point.
|
|
* @param {string} main - Entry point path
|
|
* @returns {string|undefined}
|
|
*/
|
|
function resolveMainPath(main) {
|
|
/** @type {string} */
|
|
let mainPath;
|
|
// Extension searching for the main entry point is supported for backward compatibility.
|
|
// Module._findPath is monkey-patchable here.
|
|
const { Module } = require('internal/modules/cjs/loader');
|
|
mainPath = Module._findPath(path.resolve(main), null, true);
|
|
if (!mainPath) { return; }
|
|
|
|
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
|
|
if (!preserveSymlinksMain) {
|
|
const { toRealPath } = require('internal/modules/helpers');
|
|
mainPath = toRealPath(mainPath);
|
|
}
|
|
|
|
return mainPath;
|
|
}
|
|
|
|
/**
|
|
* Determine whether the main entry point should be loaded through the ESM Loader.
|
|
* @param {string} mainPath - Absolute path to the main entry point
|
|
* @returns {boolean}
|
|
*/
|
|
function shouldUseESMLoader(mainPath) {
|
|
/**
|
|
* @type {string[]} userLoaders A list of custom loaders registered by the user
|
|
* (or an empty list when none have been registered).
|
|
*/
|
|
const userLoaders = getOptionValue('--experimental-loader');
|
|
/**
|
|
* @type {string[]} userImports A list of preloaded modules registered by the user
|
|
* (or an empty list when none have been registered).
|
|
*/
|
|
const userImports = getOptionValue('--import');
|
|
if (userLoaders.length > 0 || userImports.length > 0) { return true; }
|
|
|
|
// Determine the module format of the entry point.
|
|
if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; }
|
|
if (mainPath && StringPrototypeEndsWith(mainPath, '.wasm')) { return true; }
|
|
if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; }
|
|
|
|
if (getOptionValue('--experimental-strip-types')) {
|
|
if (!mainPath || StringPrototypeEndsWith(mainPath, '.cts')) { return false; }
|
|
// This will likely change in the future to start with commonjs loader by default
|
|
if (mainPath && StringPrototypeEndsWith(mainPath, '.mts')) { return true; }
|
|
}
|
|
|
|
const type = getNearestParentPackageJSONType(mainPath);
|
|
|
|
// No package.json or no `type` field.
|
|
if (type === undefined || type === 'none') {
|
|
return false;
|
|
}
|
|
|
|
return type === 'module';
|
|
}
|
|
|
|
/**
|
|
* @param {function(ModuleLoader):ModuleWrap|undefined} callback
|
|
*/
|
|
async function asyncRunEntryPointWithESMLoader(callback) {
|
|
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
|
|
try {
|
|
const userImports = getOptionValue('--import');
|
|
if (userImports.length > 0) {
|
|
const parentURL = getCWDURL().href;
|
|
for (let i = 0; i < userImports.length; i++) {
|
|
await cascadedLoader.import(userImports[i], parentURL, kEmptyObject);
|
|
}
|
|
} else {
|
|
cascadedLoader.waitForAsyncLoaderHookInitialization();
|
|
}
|
|
await callback(cascadedLoader);
|
|
} catch (err) {
|
|
if (hasUncaughtExceptionCaptureCallback()) {
|
|
process._fatalException(err);
|
|
return;
|
|
}
|
|
triggerUncaughtException(
|
|
err,
|
|
true, /* fromPromise */
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This initializes the ESM loader and runs --import (if any) before executing the
|
|
* callback to run the entry point.
|
|
* If the callback intends to evaluate a ESM module as entry point, it should return
|
|
* the corresponding ModuleWrap so that stalled TLA can be checked a process exit.
|
|
* @param {function(ModuleLoader):ModuleWrap|undefined} callback
|
|
* @returns {Promise}
|
|
*/
|
|
function runEntryPointWithESMLoader(callback) {
|
|
const promise = asyncRunEntryPointWithESMLoader(callback);
|
|
// Register the promise - if by the time the event loop finishes running, this is
|
|
// still unsettled, we'll search the graph from the entry point module and print
|
|
// the location of any unsettled top-level await found.
|
|
globalThis[entry_point_promise_private_symbol] = promise;
|
|
return promise;
|
|
}
|
|
|
|
/**
|
|
* Parse the CLI main entry point string and run it.
|
|
* For backwards compatibility, we have to run a bunch of monkey-patchable code that belongs to the CJS loader (exposed
|
|
* by `require('module')`) even when the entry point is ESM.
|
|
* Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
|
|
* Because of module detection, this function will attempt to run ambiguous (no explicit extension, no
|
|
* `package.json` type field) entry points as CommonJS first; under certain conditions, it will retry running as ESM.
|
|
* @param {string} main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
|
|
*/
|
|
function executeUserEntryPoint(main = process.argv[1]) {
|
|
let useESMLoader;
|
|
let resolvedMain;
|
|
if (getOptionValue('--entry-url')) {
|
|
useESMLoader = true;
|
|
} else {
|
|
resolvedMain = resolveMainPath(main);
|
|
useESMLoader = shouldUseESMLoader(resolvedMain);
|
|
}
|
|
// Unless we know we should use the ESM loader to handle the entry point per the checks in `shouldUseESMLoader`, first
|
|
// try to run the entry point via the CommonJS loader; and if that fails under certain conditions, retry as ESM.
|
|
if (!useESMLoader) {
|
|
const cjsLoader = require('internal/modules/cjs/loader');
|
|
const { wrapModuleLoad } = cjsLoader;
|
|
wrapModuleLoad(main, null, true);
|
|
} else {
|
|
const mainPath = resolvedMain || main;
|
|
const mainURL = getOptionValue('--entry-url') ? new URL(mainPath, getCWDURL()) : pathToFileURL(mainPath);
|
|
|
|
runEntryPointWithESMLoader((cascadedLoader) => {
|
|
// Note that if the graph contains unsettled TLA, this may never resolve
|
|
// even after the event loop stops running.
|
|
return cascadedLoader.import(mainURL, undefined, { __proto__: null }, undefined, true);
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
executeUserEntryPoint,
|
|
runEntryPointWithESMLoader,
|
|
};
|