From 3daa0a6c040799da20cf09de9a0475df43566e98 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:11:15 -0400 Subject: [PATCH] esm: add import.meta.dirname and import.meta.filename PR-URL: https://github.com/nodejs/node/pull/48740 Reviewed-By: Matteo Collina Reviewed-By: Jiawen Geng Reviewed-By: Benjamin Gruenbaum Reviewed-By: Stephen Belanger Reviewed-By: James M Snell Reviewed-By: Geoffrey Booth --- benchmark/fixtures/esm-dir-file.mjs | 3 ++ benchmark/fixtures/load-esm-dir-file.js | 5 +++ benchmark/misc/startup.js | 1 + doc/api/esm.md | 35 ++++++++++++++++++- .../modules/esm/initialize_import_meta.js | 13 ++++++- test/es-module/test-esm-import-meta.mjs | 16 ++++++++- .../test_cannot_run_js/entry_point.c | 7 ++++ 7 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 benchmark/fixtures/esm-dir-file.mjs create mode 100644 benchmark/fixtures/load-esm-dir-file.js create mode 100644 test/js-native-api/test_cannot_run_js/entry_point.c diff --git a/benchmark/fixtures/esm-dir-file.mjs b/benchmark/fixtures/esm-dir-file.mjs new file mode 100644 index 00000000000000..d5dafc5c46697e --- /dev/null +++ b/benchmark/fixtures/esm-dir-file.mjs @@ -0,0 +1,3 @@ +import assert from 'assert'; +assert.ok(import.meta.dirname); +assert.ok(import.meta.filename); diff --git a/benchmark/fixtures/load-esm-dir-file.js b/benchmark/fixtures/load-esm-dir-file.js new file mode 100644 index 00000000000000..a4115dd39e0489 --- /dev/null +++ b/benchmark/fixtures/load-esm-dir-file.js @@ -0,0 +1,5 @@ +(async function () { + for (let i = 0; i < 1000; i += 1) { + await import(`./esm-dir-file.mjs?i=${i}`); + } +}()); diff --git a/benchmark/misc/startup.js b/benchmark/misc/startup.js index 07c0701d128899..f55be1a7902e4f 100644 --- a/benchmark/misc/startup.js +++ b/benchmark/misc/startup.js @@ -9,6 +9,7 @@ const bench = common.createBenchmark(main, { script: [ 'benchmark/fixtures/require-builtins', 'test/fixtures/semicolon', + 'benchmark/fixtures/load-esm-dir-file', ], mode: ['process', 'worker'], count: [30], diff --git a/doc/api/esm.md b/doc/api/esm.md index 42b0a7bc21204b..278513ae78b435 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -332,6 +332,35 @@ modules it can be used to load ES modules. The `import.meta` meta property is an `Object` that contains the following properties. +### `import.meta.dirname` + + + +> Stability: 1.2 - Release candidate + +* {string} The directory name of the current module. This is the same as the + [`path.dirname()`][] of the [`import.meta.filename`][]. + +> **Caveat**: only present on `file:` modules. + +### `import.meta.filename` + + + +> Stability: 1.2 - Release candidate + +* {string} The full absolute path and filename of the current module, with +* symlinks resolved. +* This is the same as the [`url.fileURLToPath()`][] of the +* [`import.meta.url`][]. + +> **Caveat** only local modules support this property. Modules not using the +> `file:` protocol will not provide it. + ### `import.meta.url` * {string} The absolute `file:` URL of the module. @@ -526,7 +555,7 @@ If needed, a `require` function can be constructed within an ES module using These CommonJS variables are not available in ES modules. `__filename` and `__dirname` use cases can be replicated via -[`import.meta.url`][]. +[`import.meta.filename`][] and [`import.meta.dirname`][]. #### No Addon Loading @@ -1107,13 +1136,17 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. [`data:` URLs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs [`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export [`import()`]: #import-expressions +[`import.meta.dirname`]: #importmetadirname +[`import.meta.filename`]: #importmetafilename [`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 [`module.createRequire()`]: module.md#modulecreaterequirefilename [`module.syncBuiltinESMExports()`]: module.md#modulesyncbuiltinesmexports [`package.json`]: packages.md#nodejs-packagejson-field-definitions +[`path.dirname()`]: path.md#pathdirnamepath [`process.dlopen`]: process.md#processdlopenmodule-filename-flags +[`url.fileURLToPath()`]: url.md#urlfileurltopathurl [cjs-module-lexer]: https://github.com/nodejs/cjs-module-lexer/tree/1.2.2 [commonjs-extension-resolution-loader]: https://github.com/nodejs/loaders-test/tree/main/commonjs-extension-resolution-loader [custom https loader]: module.md#import-from-https diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index f55f60a5b7647a..818c99479cd068 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -1,6 +1,9 @@ 'use strict'; +const { StringPrototypeStartsWith } = primordials; const { getOptionValue } = require('internal/options'); +const { fileURLToPath } = require('internal/url'); +const { dirname } = require('path'); const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve'); /** @@ -45,12 +48,20 @@ function createImportMetaResolve(defaultParentURL, loader, allowParentURL) { * @param {object} meta * @param {{url: string}} context * @param {typeof import('./loader.js').ModuleLoader} loader Reference to the current module loader - * @returns {{url: string, resolve?: Function}} + * @returns {{dirname?: string, filename?: string, url: string, resolve?: Function}} */ function initializeImportMeta(meta, context, loader) { const { url } = context; // Alphabetical + if (StringPrototypeStartsWith(url, 'file:') === true) { + // These only make sense for locally loaded modules, + // i.e. network modules are not supported. + const filePath = fileURLToPath(url); + meta.dirname = dirname(filePath); + meta.filename = filePath; + } + if (!loader || loader.allowImportMetaResolve) { meta.resolve = createImportMetaResolve(url, loader, experimentalImportMetaResolve); } diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index 4c5aa902d4a970..50d16a3438a851 100644 --- a/test/es-module/test-esm-import-meta.mjs +++ b/test/es-module/test-esm-import-meta.mjs @@ -3,7 +3,7 @@ import assert from 'assert'; assert.strictEqual(Object.getPrototypeOf(import.meta), null); -const keys = ['resolve', 'url']; +const keys = ['dirname', 'filename', 'resolve', 'url']; assert.deepStrictEqual(Reflect.ownKeys(import.meta), keys); const descriptors = Object.getOwnPropertyDescriptors(import.meta); @@ -18,3 +18,17 @@ for (const descriptor of Object.values(descriptors)) { const urlReg = /^file:\/\/\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; assert(import.meta.url.match(urlReg)); + +// Match *nix paths: `/some/path/test/es-module` +// Match Windows paths: `d:\\some\\path\\test\\es-module` +const dirReg = /^(\/|\w:\\).*(\/|\\)test(\/|\\)es-module$/; +assert.match(import.meta.dirname, dirReg); + +// Match *nix paths: `/some/path/test/es-module/test-esm-import-meta.mjs` +// Match Windows paths: `d:\\some\\path\\test\\es-module\\test-esm-import-meta.js` +const fileReg = /^(\/|\w:\\).*(\/|\\)test(\/|\\)es-module(\/|\\)test-esm-import-meta\.mjs$/; +assert.match(import.meta.filename, fileReg); + +// Verify that `data:` imports do not behave like `file:` imports. +import dataDirname from 'data:text/javascript,export default "dirname" in import.meta'; +assert.strictEqual(dataDirname, false); diff --git a/test/js-native-api/test_cannot_run_js/entry_point.c b/test/js-native-api/test_cannot_run_js/entry_point.c new file mode 100644 index 00000000000000..6b7b50a38c9535 --- /dev/null +++ b/test/js-native-api/test_cannot_run_js/entry_point.c @@ -0,0 +1,7 @@ +#include + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports); +EXTERN_C_END + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)