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/52093 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Richard Lau <rlau@redhat.com>
207 lines
7.5 KiB
JavaScript
207 lines
7.5 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
StringPrototypeEndsWith,
|
|
globalThis,
|
|
} = primordials;
|
|
|
|
const { getNearestParentPackageJSONType } = internalBinding('modules');
|
|
const { getOptionValue } = require('internal/options');
|
|
const { checkPackageJSONIntegrity } = require('internal/modules/package_json_reader');
|
|
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; }
|
|
|
|
const response = getNearestParentPackageJSONType(mainPath);
|
|
|
|
// No package.json or no `type` field.
|
|
if (response === undefined || response[0] === 'none') {
|
|
return false;
|
|
}
|
|
|
|
// TODO(@anonrig): Do not return filePath and rawContent if experimental-policy is not used.
|
|
const {
|
|
0: type,
|
|
1: filePath,
|
|
2: rawContent,
|
|
} = response;
|
|
|
|
checkPackageJSONIntegrity(filePath, rawContent);
|
|
|
|
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'`.
|
|
* When `--experimental-detect-module` is passed, 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);
|
|
|
|
// 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.
|
|
let retryAsESM = false;
|
|
if (!useESMLoader) {
|
|
const cjsLoader = require('internal/modules/cjs/loader');
|
|
const { Module } = cjsLoader;
|
|
if (getOptionValue('--experimental-detect-module')) {
|
|
try {
|
|
// Module._load is the monkey-patchable CJS module loader.
|
|
Module._load(main, null, true);
|
|
} catch (error) {
|
|
const source = cjsLoader.entryPointSource;
|
|
const { shouldRetryAsESM } = require('internal/modules/helpers');
|
|
retryAsESM = shouldRetryAsESM(error.message, source);
|
|
// In case the entry point is a large file, such as a bundle,
|
|
// ensure no further references can prevent it being garbage-collected.
|
|
cjsLoader.entryPointSource = undefined;
|
|
if (!retryAsESM) {
|
|
const { enrichCJSError } = require('internal/modules/esm/translators');
|
|
enrichCJSError(error, source, resolvedMain);
|
|
throw error;
|
|
}
|
|
}
|
|
} else { // `--experimental-detect-module` is not passed
|
|
Module._load(main, null, true);
|
|
}
|
|
}
|
|
|
|
if (useESMLoader || retryAsESM) {
|
|
const mainPath = resolvedMain || main;
|
|
const 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,
|
|
};
|