mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
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: https://github.com/nodejs/node/pull/57804 Fixes: https://github.com/nodejs/node/issues/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>
This commit is contained in:
@@ -400,6 +400,35 @@ import { readFileSync } from 'node:fs';
|
||||
const buffer = readFileSync(new URL('./data.proto', import.meta.url));
|
||||
```
|
||||
|
||||
### `import.meta.main`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.0 - Early development
|
||||
|
||||
* {boolean} `true` when the current module is the entry point of the current process; `false` otherwise.
|
||||
|
||||
Equivalent to `require.main === module` in CommonJS.
|
||||
|
||||
Analogous to Python's `__name__ == "__main__"`.
|
||||
|
||||
```js
|
||||
export function foo() {
|
||||
return 'Hello, world';
|
||||
}
|
||||
|
||||
function main() {
|
||||
const message = foo();
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
if (import.meta.main) main();
|
||||
// `foo` can be imported from another module without possible side-effects from `main`
|
||||
```
|
||||
|
||||
### `import.meta.resolve(specifier)`
|
||||
|
||||
<!-- YAML
|
||||
@@ -616,6 +645,10 @@ These CommonJS variables are not available in ES modules.
|
||||
They can instead be loaded with [`module.createRequire()`][] or
|
||||
[`process.dlopen`][].
|
||||
|
||||
#### No `require.main`
|
||||
|
||||
To replace `require.main === module`, there is the [`import.meta.main`][] API.
|
||||
|
||||
#### No `require.resolve`
|
||||
|
||||
Relative resolution can be handled via `new URL('./local', import.meta.url)`.
|
||||
@@ -1181,6 +1214,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
|
||||
[`import()`]: #import-expressions
|
||||
[`import.meta.dirname`]: #importmetadirname
|
||||
[`import.meta.filename`]: #importmetafilename
|
||||
[`import.meta.main`]: #importmetamain
|
||||
[`import.meta.resolve`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve
|
||||
[`import.meta.url`]: #importmetaurl
|
||||
[`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
|
||||
|
||||
@@ -203,6 +203,20 @@ port.on('message', (message) => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'data-url': {
|
||||
const { runEntryPointWithESMLoader } = require('internal/modules/run_main');
|
||||
|
||||
RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs.
|
||||
const promise = runEntryPointWithESMLoader((cascadedLoader) => {
|
||||
return cascadedLoader.import(filename, undefined, { __proto__: null }, undefined, true);
|
||||
});
|
||||
|
||||
PromisePrototypeThen(promise, undefined, (e) => {
|
||||
workerOnGlobalUncaughtException(e, true);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// script filename
|
||||
// runMain here might be monkey-patched by users in --require.
|
||||
|
||||
@@ -51,12 +51,12 @@ function createImportMetaResolve(defaultParentURL, loader, allowParentURL) {
|
||||
/**
|
||||
* Create the `import.meta` object for a module.
|
||||
* @param {object} meta
|
||||
* @param {{url: string}} context
|
||||
* @param {{url: string, isMain?: boolean}} context
|
||||
* @param {typeof import('./loader.js').ModuleLoader} loader Reference to the current module loader
|
||||
* @returns {{dirname?: string, filename?: string, url: string, resolve?: Function}}
|
||||
*/
|
||||
function initializeImportMeta(meta, context, loader) {
|
||||
const { url } = context;
|
||||
const { url, isMain } = context;
|
||||
|
||||
// Alphabetical
|
||||
if (StringPrototypeStartsWith(url, 'file:') === true) {
|
||||
@@ -65,6 +65,8 @@ function initializeImportMeta(meta, context, loader) {
|
||||
setLazyPathHelpers(meta, url);
|
||||
}
|
||||
|
||||
meta.main = !!isMain;
|
||||
|
||||
if (!loader || loader.allowImportMetaResolve) {
|
||||
meta.resolve = createImportMetaResolve(url, loader, experimentalImportMetaResolve);
|
||||
}
|
||||
|
||||
@@ -248,10 +248,11 @@ class ModuleLoader {
|
||||
*
|
||||
* @param {string} source Source code of the module.
|
||||
* @param {string} url URL of the module.
|
||||
* @param {{ isMain?: boolean }|undefined} context - context object containing module metadata.
|
||||
* @returns {object} The module wrap object.
|
||||
*/
|
||||
createModuleWrap(source, url) {
|
||||
return compileSourceTextModule(url, source, this);
|
||||
createModuleWrap(source, url, context = kEmptyObject) {
|
||||
return compileSourceTextModule(url, source, this, context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,7 +290,8 @@ class ModuleLoader {
|
||||
* @returns {Promise<object>} The module object.
|
||||
*/
|
||||
eval(source, url, isEntryPoint = false) {
|
||||
const wrap = this.createModuleWrap(source, url);
|
||||
const context = isEntryPoint ? { isMain: true } : undefined;
|
||||
const wrap = this.createModuleWrap(source, url, context);
|
||||
return this.executeModuleJob(url, wrap, isEntryPoint);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,8 @@ translators.set('module', function moduleStrategy(url, source, isMain) {
|
||||
source = stringify(source);
|
||||
debug(`Translating StandardModule ${url}`);
|
||||
const { compileSourceTextModule } = require('internal/modules/esm/utils');
|
||||
const module = compileSourceTextModule(url, source, this);
|
||||
const context = isMain ? { isMain } : undefined;
|
||||
const module = compileSourceTextModule(url, source, this, context);
|
||||
return module;
|
||||
});
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ const {
|
||||
const {
|
||||
emitExperimentalWarning,
|
||||
getCWDURL,
|
||||
kEmptyObject,
|
||||
} = require('internal/util');
|
||||
const assert = require('internal/assert');
|
||||
const {
|
||||
@@ -188,7 +189,7 @@ function registerModule(referrer, registry) {
|
||||
*/
|
||||
function defaultInitializeImportMetaForModule(meta, wrap) {
|
||||
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
|
||||
return cascadedLoader.importMetaInitialize(meta, { url: wrap.url });
|
||||
return cascadedLoader.importMetaInitialize(meta, { url: wrap.url, isMain: wrap.isMain });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,15 +343,22 @@ async function initializeHooks() {
|
||||
* @param {string} source Source code of the module.
|
||||
* @param {typeof import('./loader.js').ModuleLoader|undefined} cascadedLoader If provided,
|
||||
* register the module for default handling.
|
||||
* @param {{ isMain?: boolean }|undefined} context - context object containing module metadata.
|
||||
* @returns {ModuleWrap}
|
||||
*/
|
||||
function compileSourceTextModule(url, source, cascadedLoader) {
|
||||
function compileSourceTextModule(url, source, cascadedLoader, context = kEmptyObject) {
|
||||
const hostDefinedOption = cascadedLoader ? source_text_module_default_hdo : undefined;
|
||||
const wrap = new ModuleWrap(url, undefined, source, 0, 0, hostDefinedOption);
|
||||
|
||||
if (!cascadedLoader) {
|
||||
return wrap;
|
||||
}
|
||||
|
||||
const { isMain } = context;
|
||||
if (isMain) {
|
||||
wrap.isMain = true;
|
||||
}
|
||||
|
||||
// Cache the source map for the module if present.
|
||||
if (wrap.sourceMapURL) {
|
||||
maybeCacheSourceMap(url, source, wrap, false, undefined, wrap.sourceMapURL);
|
||||
|
||||
@@ -7,7 +7,6 @@ const {
|
||||
AtomicsAdd,
|
||||
Float64Array,
|
||||
FunctionPrototypeBind,
|
||||
JSONStringify,
|
||||
MathMax,
|
||||
ObjectEntries,
|
||||
Promise,
|
||||
@@ -167,8 +166,8 @@ class Worker extends EventEmitter {
|
||||
doEval = 'classic';
|
||||
} else if (isURL(filename) && filename.protocol === 'data:') {
|
||||
url = null;
|
||||
doEval = 'module';
|
||||
filename = `import ${JSONStringify(`${filename}`)}`;
|
||||
doEval = 'data-url';
|
||||
filename = `${filename}`;
|
||||
} else {
|
||||
doEval = false;
|
||||
if (isURL(filename)) {
|
||||
|
||||
74
test/es-module/test-esm-import-meta-main-eval.mjs
Normal file
74
test/es-module/test-esm-import-meta-main-eval.mjs
Normal file
@@ -0,0 +1,74 @@
|
||||
import { spawnPromisified } from '../common/index.mjs';
|
||||
import * as fixtures from '../common/fixtures.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
const importMetaMainScript = `
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
assert.strictEqual(import.meta.main, true, 'import.meta.main should evaluate true in main module');
|
||||
|
||||
const { isMain: importedModuleIsMain } = await import(
|
||||
${JSON.stringify(fixtures.fileURL('es-modules/import-meta-main.mjs'))}
|
||||
);
|
||||
assert.strictEqual(importedModuleIsMain, false, 'import.meta.main should evaluate false in imported module');
|
||||
`;
|
||||
|
||||
function wrapScriptInEvalWorker(script) {
|
||||
return `
|
||||
import { Worker } from 'node:worker_threads';
|
||||
new Worker(${JSON.stringify(script)}, { eval: true });
|
||||
`;
|
||||
}
|
||||
|
||||
function convertScriptSourceToDataUrl(script) {
|
||||
return new URL(`data:text/javascript,${encodeURIComponent(script)}`);
|
||||
}
|
||||
|
||||
function wrapScriptInUrlWorker(script) {
|
||||
return `
|
||||
import { Worker } from 'node:worker_threads';
|
||||
new Worker(new URL(${JSON.stringify(convertScriptSourceToDataUrl(script))}));
|
||||
`;
|
||||
}
|
||||
|
||||
describe('import.meta.main in evaluated scripts', () => {
|
||||
it('should evaluate true in evaluated script', async () => {
|
||||
const result = await spawnPromisified(
|
||||
process.execPath,
|
||||
['--input-type=module', '--eval', importMetaMainScript],
|
||||
);
|
||||
assert.deepStrictEqual(result, {
|
||||
stderr: '',
|
||||
stdout: '',
|
||||
code: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate true in worker instantiated with module source by evaluated script', async () => {
|
||||
const result = await spawnPromisified(
|
||||
process.execPath,
|
||||
['--input-type=module', '--eval', wrapScriptInEvalWorker(importMetaMainScript)],
|
||||
);
|
||||
assert.deepStrictEqual(result, {
|
||||
stderr: '',
|
||||
stdout: '',
|
||||
code: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate true in worker instantiated with `data:` URL by evaluated script', async () => {
|
||||
const result = await spawnPromisified(
|
||||
process.execPath,
|
||||
['--input-type=module', '--eval', wrapScriptInUrlWorker(importMetaMainScript)],
|
||||
);
|
||||
assert.deepStrictEqual(result, {
|
||||
stderr: '',
|
||||
stdout: '',
|
||||
code: 0,
|
||||
signal: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
26
test/es-module/test-esm-import-meta-main.mjs
Normal file
26
test/es-module/test-esm-import-meta-main.mjs
Normal file
@@ -0,0 +1,26 @@
|
||||
import '../common/index.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
|
||||
function get_environment() {
|
||||
if (process.env.HAS_STARTED_WORKER) return 'in worker thread started by ES Module';
|
||||
return 'in ES Module';
|
||||
}
|
||||
|
||||
assert.strictEqual(
|
||||
import.meta.main,
|
||||
true,
|
||||
`\`import.meta.main\` at top-level module ${get_environment()} should evaluate \`true\``
|
||||
);
|
||||
|
||||
const { isMain: importedModuleIsMain } = await import('../fixtures/es-modules/import-meta-main.mjs');
|
||||
assert.strictEqual(
|
||||
importedModuleIsMain,
|
||||
false,
|
||||
`\`import.meta.main\` at dynamically imported module ${get_environment()} should evaluate \`false\``
|
||||
);
|
||||
|
||||
if (!process.env.HAS_STARTED_WORKER) {
|
||||
process.env.HAS_STARTED_WORKER = 1;
|
||||
new Worker(import.meta.filename);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import assert from 'assert';
|
||||
|
||||
assert.strictEqual(Object.getPrototypeOf(import.meta), null);
|
||||
|
||||
const keys = ['dirname', 'filename', 'resolve', 'url'];
|
||||
const keys = ['dirname', 'filename', 'main', 'resolve', 'url'];
|
||||
assert.deepStrictEqual(Reflect.ownKeys(import.meta), keys);
|
||||
|
||||
const descriptors = Object.getOwnPropertyDescriptors(import.meta);
|
||||
|
||||
1
test/fixtures/es-modules/import-meta-main.mjs
vendored
Normal file
1
test/fixtures/es-modules/import-meta-main.mjs
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const isMain = import.meta.main;
|
||||
Reference in New Issue
Block a user