Repository navigation
Expand file tree
/
Copy pathrun_main.js
More file actions
214 lines (198 loc) Β· 8.75 KB
/
Copy pathrun_main.js
File metadata and controls
214 lines (198 loc) Β· 8.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
'use strict';
const {
StringPrototypeEndsWith,
globalThis,
} = primordials;
const { getOptionValue } = require('internal/options');
// Through loaderMethods rather than the `modules` binding directly: a mounted
// VFS overrides these, and a worker entry point can live inside one.
const { loaderMethods } = require('internal/modules/helpers');
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) {
mainPath = loaderMethods.realpathSync(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('--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 = loaderMethods.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();
}
// Any --import above may have registered a VFS provider, so mount now (no-op
// if prepareExecution already mounted in the no-import case).
require('internal/process/pre_execution').finishVfsMounts();
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`
* @returns {Promise|undefined} A promise when the entry point is still being evaluated
* asynchronously because its graph contains top-level await, otherwise `undefined`.
*/
function executeUserEntryPoint(main = process.argv[1]) {
if (getOptionValue('[vfs_load_set]')) {
// The entry is a directory mount whose reserved root only exists after
// finishVfsMounts() runs (inside runEntryPointWithESMLoader, once --import
// preloads have had a chance to registerProvider()). Load it through the CJS
// main loader so the reserved directory resolves to its index the same way
// any require() of a directory does, sidestepping ESM directory-import.
runEntryPointWithESMLoader(() => {
const { getVfsLoadRoot } = require('internal/process/pre_execution');
const { wrapModuleLoad } = require('internal/modules/cjs/loader');
return wrapModuleLoad(getVfsLoadRoot(), null, true);
});
return;
}
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);
// When no async loader hooks or --import are registered, load the entry point
// directly. The graph is loaded and linked synchronously, and evaluated
// synchronously too unless it contains top-level await.
if (getOptionValue('--experimental-loader').length === 0 &&
getOptionValue('--import').length === 0) {
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
const job = cascadedLoader.importForEntryPoint(mainURL.href);
if (job === undefined) {
// Evaluated synchronously; no promise was created at any point.
return;
}
// Top-level await: evaluate through the usual asynchronous entry point path,
// reusing the job that was already resolved, linked and instantiated above.
return runEntryPointWithESMLoader(async () => {
// Begin evaluation inside a microtask, as `import()` does by awaiting the
// module job before running it, so that a top-level await in the entry point
// resumes in the same microtask drain instead of unwinding to the main stack
// and letting the `process.nextTick` queue run first.
await null;
// Await rather than returning the job's promise: resolving a promise *with* a
// thenable performs a `then` lookup on it, which user code can observe.
await job.run(true);
});
}
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,
};