Skip to content

Commit adef9af

Browse files
Lordfirespeedaduh95
authored andcommitted
esm: implement import.meta.main
Boolean value to check if an ES Module is the entrypoint of the current process. Implements: #57226 Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: #57804 Fixes: #57226 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
1 parent beba631 commit adef9af

File tree

11 files changed

+173
-12
lines changed

11 files changed

+173
-12
lines changed

doc/api/esm.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,35 @@ import { readFileSync } from 'node:fs';
396396
const buffer = readFileSync(new URL('./data.proto', import.meta.url));
397397
```
398398
399+
### `import.meta.main`
400+
401+
<!-- YAML
402+
added:
403+
- REPLACEME
404+
-->
405+
406+
> Stability: 1.0 - Early development
407+
408+
* {boolean} `true` when the current module is the entry point of the current process; `false` otherwise.
409+
410+
Equivalent to `require.main === module` in CommonJS.
411+
412+
Analogous to Python's `__name__ == "__main__"`.
413+
414+
```js
415+
export function foo() {
416+
return 'Hello, world';
417+
}
418+
419+
function main() {
420+
const message = foo();
421+
console.log(message);
422+
}
423+
424+
if (import.meta.main) main();
425+
// `foo` can be imported from another module without possible side-effects from `main`
426+
```
427+
399428
### `import.meta.resolve(specifier)`
400429
401430
<!-- YAML
@@ -612,6 +641,10 @@ These CommonJS variables are not available in ES modules.
612641
They can instead be loaded with [`module.createRequire()`][] or
613642
[`process.dlopen`][].
614643
644+
#### No `require.main`
645+
646+
To replace `require.main === module`, there is the [`import.meta.main`][] API.
647+
615648
#### No `require.resolve`
616649
617650
Relative resolution can be handled via `new URL('./local', import.meta.url)`.
@@ -1177,6 +1210,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
11771210
[`import()`]: #import-expressions
11781211
[`import.meta.dirname`]: #importmetadirname
11791212
[`import.meta.filename`]: #importmetafilename
1213+
[`import.meta.main`]: #importmetamain
11801214
[`import.meta.resolve`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve
11811215
[`import.meta.url`]: #importmetaurl
11821216
[`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

lib/internal/main/worker_thread.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@ port.on('message', (message) => {
203203
break;
204204
}
205205

206+
case 'data-url': {
207+
const { runEntryPointWithESMLoader } = require('internal/modules/run_main');
208+
209+
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
210+
const promise = runEntryPointWithESMLoader((cascadedLoader) => {
211+
return cascadedLoader.import(filename, undefined, { __proto__: null }, undefined, true);
212+
});
213+
214+
PromisePrototypeThen(promise, undefined, (e) => {
215+
workerOnGlobalUncaughtException(e, true);
216+
});
217+
break;
218+
}
219+
206220
default: {
207221
// script filename
208222
// runMain here might be monkey-patched by users in --require.

lib/internal/modules/esm/initialize_import_meta.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ function createImportMetaResolve(defaultParentURL, loader, allowParentURL) {
5151
/**
5252
* Create the `import.meta` object for a module.
5353
* @param {object} meta
54-
* @param {{url: string}} context
54+
* @param {{url: string, isMain?: boolean}} context
5555
* @param {typeof import('./loader.js').ModuleLoader} loader Reference to the current module loader
5656
* @returns {{dirname?: string, filename?: string, url: string, resolve?: Function}}
5757
*/
5858
function initializeImportMeta(meta, context, loader) {
59-
const { url } = context;
59+
const { url, isMain } = context;
6060

6161
// Alphabetical
6262
if (StringPrototypeStartsWith(url, 'file:') === true) {
@@ -65,6 +65,8 @@ function initializeImportMeta(meta, context, loader) {
6565
setLazyPathHelpers(meta, url);
6666
}
6767

68+
meta.main = !!isMain;
69+
6870
if (!loader || loader.allowImportMetaResolve) {
6971
meta.resolve = createImportMetaResolve(url, loader, experimentalImportMetaResolve);
7072
}

lib/internal/modules/esm/loader.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,11 @@ class ModuleLoader {
248248
*
249249
* @param {string} source Source code of the module.
250250
* @param {string} url URL of the module.
251+
* @param {{ isMain?: boolean }|undefined} context - context object containing module metadata.
251252
* @returns {object} The module wrap object.
252253
*/
253-
createModuleWrap(source, url) {
254-
return compileSourceTextModule(url, source, this);
254+
createModuleWrap(source, url, context = kEmptyObject) {
255+
return compileSourceTextModule(url, source, this, context);
255256
}
256257

257258
/**
@@ -289,7 +290,8 @@ class ModuleLoader {
289290
* @returns {Promise<object>} The module object.
290291
*/
291292
eval(source, url, isEntryPoint = false) {
292-
const wrap = this.createModuleWrap(source, url);
293+
const context = isEntryPoint ? { isMain: true } : undefined;
294+
const wrap = this.createModuleWrap(source, url, context);
293295
return this.executeModuleJob(url, wrap, isEntryPoint);
294296
}
295297

lib/internal/modules/esm/translators.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ translators.set('module', function moduleStrategy(url, source, isMain) {
103103
source = stringify(source);
104104
debug(`Translating StandardModule ${url}`);
105105
const { compileSourceTextModule } = require('internal/modules/esm/utils');
106-
const module = compileSourceTextModule(url, source, this);
106+
const context = isMain ? { isMain } : undefined;
107+
const module = compileSourceTextModule(url, source, this, context);
107108
return module;
108109
});
109110

lib/internal/modules/esm/utils.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const {
4242
const {
4343
emitExperimentalWarning,
4444
getCWDURL,
45+
kEmptyObject,
4546
} = require('internal/util');
4647
const assert = require('internal/assert');
4748
const {
@@ -188,7 +189,7 @@ function registerModule(referrer, registry) {
188189
*/
189190
function defaultInitializeImportMetaForModule(meta, wrap) {
190191
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
191-
return cascadedLoader.importMetaInitialize(meta, { url: wrap.url });
192+
return cascadedLoader.importMetaInitialize(meta, { url: wrap.url, isMain: wrap.isMain });
192193
}
193194

194195
/**
@@ -342,15 +343,22 @@ async function initializeHooks() {
342343
* @param {string} source Source code of the module.
343344
* @param {typeof import('./loader.js').ModuleLoader|undefined} cascadedLoader If provided,
344345
* register the module for default handling.
346+
* @param {{ isMain?: boolean }|undefined} context - context object containing module metadata.
345347
* @returns {ModuleWrap}
346348
*/
347-
function compileSourceTextModule(url, source, cascadedLoader) {
349+
function compileSourceTextModule(url, source, cascadedLoader, context = kEmptyObject) {
348350
const hostDefinedOption = cascadedLoader ? source_text_module_default_hdo : undefined;
349351
const wrap = new ModuleWrap(url, undefined, source, 0, 0, hostDefinedOption);
350352

351353
if (!cascadedLoader) {
352354
return wrap;
353355
}
356+
357+
const { isMain } = context;
358+
if (isMain) {
359+
wrap.isMain = true;
360+
}
361+
354362
// Cache the source map for the module if present.
355363
if (wrap.sourceMapURL) {
356364
maybeCacheSourceMap(url, source, wrap, false, undefined, wrap.sourceMapURL);

lib/internal/worker.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const {
77
AtomicsAdd,
88
Float64Array,
99
FunctionPrototypeBind,
10-
JSONStringify,
1110
MathMax,
1211
ObjectEntries,
1312
Promise,
@@ -167,8 +166,8 @@ class Worker extends EventEmitter {
167166
doEval = 'classic';
168167
} else if (isURL(filename) && filename.protocol === 'data:') {
169168
url = null;
170-
doEval = 'module';
171-
filename = `import ${JSONStringify(`${filename}`)}`;
169+
doEval = 'data-url';
170+
filename = `${filename}`;
172171
} else {
173172
doEval = false;
174173
if (isURL(filename)) {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { spawnPromisified } from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.js';
3+
import assert from 'node:assert/strict';
4+
import { describe, it } from 'node:test';
5+
6+
const importMetaMainScript = `
7+
import assert from 'node:assert/strict';
8+
9+
assert.strictEqual(import.meta.main, true, 'import.meta.main should evaluate true in main module');
10+
11+
const { isMain: importedModuleIsMain } = await import(
12+
${JSON.stringify(fixtures.fileURL('es-modules/import-meta-main.mjs'))}
13+
);
14+
assert.strictEqual(importedModuleIsMain, false, 'import.meta.main should evaluate false in imported module');
15+
`;
16+
17+
function wrapScriptInEvalWorker(script) {
18+
return `
19+
import { Worker } from 'node:worker_threads';
20+
new Worker(${JSON.stringify(script)}, { eval: true });
21+
`;
22+
}
23+
24+
function convertScriptSourceToDataUrl(script) {
25+
return new URL(`data:text/javascript,${encodeURIComponent(script)}`);
26+
}
27+
28+
function wrapScriptInUrlWorker(script) {
29+
return `
30+
import { Worker } from 'node:worker_threads';
31+
new Worker(new URL(${JSON.stringify(convertScriptSourceToDataUrl(script))}));
32+
`;
33+
}
34+
35+
describe('import.meta.main in evaluated scripts', () => {
36+
it('should evaluate true in evaluated script', async () => {
37+
const result = await spawnPromisified(
38+
process.execPath,
39+
['--input-type=module', '--eval', importMetaMainScript],
40+
);
41+
assert.deepStrictEqual(result, {
42+
stderr: '',
43+
stdout: '',
44+
code: 0,
45+
signal: null,
46+
});
47+
});
48+
49+
it('should evaluate true in worker instantiated with module source by evaluated script', async () => {
50+
const result = await spawnPromisified(
51+
process.execPath,
52+
['--input-type=module', '--eval', wrapScriptInEvalWorker(importMetaMainScript)],
53+
);
54+
assert.deepStrictEqual(result, {
55+
stderr: '',
56+
stdout: '',
57+
code: 0,
58+
signal: null,
59+
});
60+
});
61+
62+
it('should evaluate true in worker instantiated with `data:` URL by evaluated script', async () => {
63+
const result = await spawnPromisified(
64+
process.execPath,
65+
['--input-type=module', '--eval', wrapScriptInUrlWorker(importMetaMainScript)],
66+
);
67+
assert.deepStrictEqual(result, {
68+
stderr: '',
69+
stdout: '',
70+
code: 0,
71+
signal: null,
72+
});
73+
});
74+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import '../common/index.mjs';
2+
import assert from 'node:assert/strict';
3+
import { Worker } from 'node:worker_threads';
4+
5+
function get_environment() {
6+
if (process.env.HAS_STARTED_WORKER) return 'in worker thread started by ES Module';
7+
return 'in ES Module';
8+
}
9+
10+
assert.strictEqual(
11+
import.meta.main,
12+
true,
13+
`\`import.meta.main\` at top-level module ${get_environment()} should evaluate \`true\``
14+
);
15+
16+
const { isMain: importedModuleIsMain } = await import('../fixtures/es-modules/import-meta-main.mjs');
17+
assert.strictEqual(
18+
importedModuleIsMain,
19+
false,
20+
`\`import.meta.main\` at dynamically imported module ${get_environment()} should evaluate \`false\``
21+
);
22+
23+
if (!process.env.HAS_STARTED_WORKER) {
24+
process.env.HAS_STARTED_WORKER = 1;
25+
new Worker(import.meta.filename);
26+
}

test/es-module/test-esm-import-meta.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import assert from 'assert';
33

44
assert.strictEqual(Object.getPrototypeOf(import.meta), null);
55

6-
const keys = ['dirname', 'filename', 'resolve', 'url'];
6+
const keys = ['dirname', 'filename', 'main', 'resolve', 'url'];
77
assert.deepStrictEqual(Reflect.ownKeys(import.meta), keys);
88

99
const descriptors = Object.getOwnPropertyDescriptors(import.meta);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const isMain = import.meta.main;

0 commit comments

Comments
 (0)