mirror of
https://github.com/nodejs/node.git
synced 2025-05-02 14:56:19 +00:00

PR-URL: https://github.com/nodejs/node/pull/53725 Refs: https://github.com/nodejs/loaders/issues/217 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruy Adorno <ruy@vlt.sh>
185 lines
6.8 KiB
JavaScript
185 lines
6.8 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
StringPrototypeEndsWith,
|
|
globalThis,
|
|
} = primordials;
|
|
|
|
const { getNearestParentPackageJSONType } = internalBinding('modules');
|
|
const { getOptionValue } = require('internal/options');
|
|
const path = require('path');
|
|
const { pathToFileURL } = 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
|
|
*/
|
|
function resolveMainPath(main) {
|
|
const defaultType = getOptionValue('--experimental-default-type');
|
|
/** @type {string} */
|
|
let mainPath;
|
|
if (defaultType === 'module') {
|
|
if (getOptionValue('--preserve-symlinks-main')) { return; }
|
|
mainPath = path.resolve(main);
|
|
} else {
|
|
// Extension searching for the main entry point is supported only in legacy mode.
|
|
// 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');
|
|
try {
|
|
mainPath = toRealPath(mainPath);
|
|
} catch (err) {
|
|
if (defaultType === 'module' && err?.code === 'ENOENT') {
|
|
const { decorateErrorWithCommonJSHints } = require('internal/modules/esm/resolve');
|
|
const { getCWDURL } = require('internal/util');
|
|
decorateErrorWithCommonJSHints(err, mainPath, getCWDURL());
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
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
|
|
*/
|
|
function shouldUseESMLoader(mainPath) {
|
|
if (getOptionValue('--experimental-default-type') === 'module') { return true; }
|
|
|
|
/**
|
|
* @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, '.cjs')) { return false; }
|
|
|
|
if (getOptionValue('--experimental-strip-types')) {
|
|
// This ensures that --experimental-default-type=commonjs and .mts files are treated as commonjs
|
|
if (getOptionValue('--experimental-default-type') === 'commonjs') { return false; }
|
|
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.forceLoadHooks();
|
|
}
|
|
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.
|
|
* This monkey-patchable code is bypassed under `--experimental-default-type=module`.
|
|
* 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]) {
|
|
const resolvedMain = resolveMainPath(main);
|
|
const useESMLoader = shouldUseESMLoader(resolvedMain);
|
|
let mainURL;
|
|
// 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;
|
|
if (mainURL === undefined) {
|
|
mainURL = pathToFileURL(mainPath).href;
|
|
}
|
|
|
|
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 }, true);
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
executeUserEntryPoint,
|
|
runEntryPointWithESMLoader,
|
|
};
|