From 22d1c702b75a21f71b59cea7b30548dff29079e7 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Tue, 11 Jul 2023 17:38:32 -0400 Subject: [PATCH 01/12] esm: add import.meta.dirname and import.meta.filename --- doc/api/esm.md | 36 +++++++++++++++---- .../modules/esm/initialize_import_meta.js | 28 ++++++++++++++- test/es-module/test-esm-import-meta.mjs | 12 ++++++- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index bfd40556e5fcab..d0f427f4d3d701 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -309,6 +309,32 @@ 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` + + + +* {string} The directory name of the current module. This is the same as the + [`path.dirname()`][] of the [`import.meta.filename`][]. + +> **Caveat** only local modules support this property. Modules not using the +> `file:` protocol will not provide it. + +### `import.meta.filename` + + + +* {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. @@ -498,13 +524,6 @@ In most cases, the ES module `import` can be used to load CommonJS modules. If needed, a `require` function can be constructed within an ES module using [`module.createRequire()`][]. -#### No `__filename` or `__dirname` - -These CommonJS variables are not available in ES modules. - -`__filename` and `__dirname` use cases can be replicated via -[`import.meta.url`][]. - #### No Addon Loading [Addons][] are not currently supported with ES module imports. @@ -1065,13 +1084,16 @@ 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.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..9e7196e4495306 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('url'); +const { dirname } = require('path'); const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve'); /** @@ -45,12 +48,16 @@ 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 + const moduleMeta = getModuleMetaPathProperties(url); + meta.dirname = moduleMeta.dirname; + meta.filename = moduleMeta.filename; + if (!loader || loader.allowImportMetaResolve) { meta.resolve = createImportMetaResolve(url, loader, experimentalImportMetaResolve); } @@ -60,6 +67,25 @@ function initializeImportMeta(meta, context, loader) { return meta; } +/** + * Produce path-based `dirname` and `filename` properties for modules loaded from the filesystem. + * @param {string} url + * @returns {{dirname?: string, filename?: string}} + */ +function getModuleMetaPathProperties(url) { + if (StringPrototypeStartsWith(url, 'file://') === false) { + // These only make sense for locally loaded modules, + // i.e. network modules are not supported. + return { dirname: undefined, filename: undefined }; + } + + const filePath = fileURLToPath(url); + return { + dirname: dirname(filePath), + filename: filePath, + }; +} + module.exports = { initializeImportMeta, }; diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index 4c5aa902d4a970..59e801fca7bcd7 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,13 @@ for (const descriptor of Object.values(descriptors)) { const urlReg = /^file:\/\/\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; assert(import.meta.url.match(urlReg)); + +const dirReg = /^\/.*\/test\/es-module$/; +assert.match(import.meta.dirname, dirReg); + +const fileReg = /^\/.*\/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 import.meta.dirname'; +assert.strictEqual(dataDirname, undefined); From dac01b4575c3c4f6ef39f0a12ad4aa4402e3d7fb Mon Sep 17 00:00:00 2001 From: James Sumners Date: Thu, 24 Aug 2023 09:11:46 -0400 Subject: [PATCH 02/12] Add benchmark --- benchmark/fixtures/esm-dir-file.mjs | 3 +++ benchmark/fixtures/load-esm-dir-file.js | 5 +++++ benchmark/misc/startup.js | 1 + 3 files changed, 9 insertions(+) create mode 100644 benchmark/fixtures/esm-dir-file.mjs create mode 100644 benchmark/fixtures/load-esm-dir-file.js 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], From 0b6e16f1ae3bb8a1c5afeb1496095b8d807ff4c7 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Fri, 29 Sep 2023 12:47:35 -0400 Subject: [PATCH 03/12] Address linter gripe --- lib/internal/modules/esm/initialize_import_meta.js | 2 +- test/js-native-api/test_cannot_run_js/entry_point.c | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/js-native-api/test_cannot_run_js/entry_point.c diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index 9e7196e4495306..62f564ad96319d 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -2,7 +2,7 @@ const { StringPrototypeStartsWith } = primordials; const { getOptionValue } = require('internal/options'); -const { fileURLToPath } = require('url'); +const { fileURLToPath } = require('internal/url'); const { dirname } = require('path'); const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve'); 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) From 37f048622b5afc8b76159404b9f99c801bc97a35 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:38:21 -0400 Subject: [PATCH 04/12] Apply suggestions from code review Co-authored-by: Geoffrey Booth --- doc/api/esm.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/api/esm.md b/doc/api/esm.md index d0f427f4d3d701..0a793fa0946134 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -315,6 +315,7 @@ properties. added: REPLACEME --> +> 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`][]. @@ -327,6 +328,7 @@ added: REPLACEME added: REPLACEME --> +> 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 From 39def2a276cee1cea3d6a43476e5d16e681687c9 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 2 Oct 2023 14:28:01 -0700 Subject: [PATCH 05/12] Update doc/api/esm.md --- doc/api/esm.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/esm.md b/doc/api/esm.md index 0a793fa0946134..7a4e7fe1abd34a 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -316,6 +316,7 @@ added: REPLACEME --> > 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`][]. From 3c74b9fc9c68139c721ef0e675b7c1efb118154d Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 2 Oct 2023 14:28:11 -0700 Subject: [PATCH 06/12] Update doc/api/esm.md --- doc/api/esm.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/esm.md b/doc/api/esm.md index 7a4e7fe1abd34a..d8af080633642c 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -330,6 +330,7 @@ added: REPLACEME --> > 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 From 2643e7be2afcef59534ee01a8a5c2a1d533a470c Mon Sep 17 00:00:00 2001 From: James Sumners Date: Mon, 23 Oct 2023 10:07:22 -0400 Subject: [PATCH 07/12] Re-add doc --- doc/api/esm.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/api/esm.md b/doc/api/esm.md index d8af080633642c..2409fb230ab3ef 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -528,6 +528,13 @@ In most cases, the ES module `import` can be used to load CommonJS modules. If needed, a `require` function can be constructed within an ES module using [`module.createRequire()`][]. +#### No `__filename` or `__dirname` + +These CommonJS variables are not available in ES modules. + +`__filename` and `__dirname` use cases can be replicated via +[`import.meta.filename`][] and [`import.meta.dirname`][]. + #### No Addon Loading [Addons][] are not currently supported with ES module imports. @@ -1088,6 +1095,7 @@ 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 From 983e142a869b3469f020847ccbfd688ac3e16993 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Tue, 24 Oct 2023 12:37:18 -0400 Subject: [PATCH 08/12] Address feedback --- .../modules/esm/initialize_import_meta.js | 29 +++++-------------- test/es-module/test-esm-import-meta.mjs | 4 +-- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index 62f564ad96319d..818c99479cd068 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -54,9 +54,13 @@ function initializeImportMeta(meta, context, loader) { const { url } = context; // Alphabetical - const moduleMeta = getModuleMetaPathProperties(url); - meta.dirname = moduleMeta.dirname; - meta.filename = moduleMeta.filename; + 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); @@ -67,25 +71,6 @@ function initializeImportMeta(meta, context, loader) { return meta; } -/** - * Produce path-based `dirname` and `filename` properties for modules loaded from the filesystem. - * @param {string} url - * @returns {{dirname?: string, filename?: string}} - */ -function getModuleMetaPathProperties(url) { - if (StringPrototypeStartsWith(url, 'file://') === false) { - // These only make sense for locally loaded modules, - // i.e. network modules are not supported. - return { dirname: undefined, filename: undefined }; - } - - const filePath = fileURLToPath(url); - return { - dirname: dirname(filePath), - filename: filePath, - }; -} - module.exports = { initializeImportMeta, }; diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index 59e801fca7bcd7..667f7659950924 100644 --- a/test/es-module/test-esm-import-meta.mjs +++ b/test/es-module/test-esm-import-meta.mjs @@ -26,5 +26,5 @@ const fileReg = /^\/.*\/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 import.meta.dirname'; -assert.strictEqual(dataDirname, undefined); +import dataDirname from 'data:text/javascript,export default "dirname" in import.meta'; +assert.strictEqual(dataDirname, false); From bba3fd68e429c3f8f1ed47d34c5e42f4bb83e307 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:43:15 -0400 Subject: [PATCH 09/12] Update doc/api/esm.md Co-authored-by: Geoffrey Booth --- doc/api/esm.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 2409fb230ab3ef..87555a49bb9ecf 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -320,8 +320,7 @@ added: REPLACEME * {string} The directory name of the current module. This is the same as the [`path.dirname()`][] of the [`import.meta.filename`][]. -> **Caveat** only local modules support this property. Modules not using the -> `file:` protocol will not provide it. +> **Caveat** only file-based modules support this property. ### `import.meta.filename` From 88180cbb6644ee045120d106d86d6247919e6f13 Mon Sep 17 00:00:00 2001 From: James Sumners <321201+jsumners@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:56:22 -0400 Subject: [PATCH 10/12] Update esm.md Co-authored-by: Antoine du Hamel --- doc/api/esm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 87555a49bb9ecf..d32e0abadb99ae 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -320,7 +320,7 @@ added: REPLACEME * {string} The directory name of the current module. This is the same as the [`path.dirname()`][] of the [`import.meta.filename`][]. -> **Caveat** only file-based modules support this property. +> **Caveat**: only present on `file:` modules. ### `import.meta.filename` From 603f050f0418bbf79572c5103cf09b235ab91cb8 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Wed, 25 Oct 2023 08:12:01 -0400 Subject: [PATCH 11/12] Update regular expressions --- test/es-module/test-esm-import-meta.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index 667f7659950924..cae13a3d312e81 100644 --- a/test/es-module/test-esm-import-meta.mjs +++ b/test/es-module/test-esm-import-meta.mjs @@ -19,10 +19,14 @@ for (const descriptor of Object.values(descriptors)) { const urlReg = /^file:\/\/\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; assert(import.meta.url.match(urlReg)); -const dirReg = /^\/.*\/test\/es-module$/; +// Match *nix paths: `/some/path/test/es-module` +// Match Windows paths: `d:\\some\\path\\test\\es-module` +const dirReg = /^(\/|\w:\\).*(\/|\\)test(\/|\\{2})es-module$/; assert.match(import.meta.dirname, dirReg); -const fileReg = /^\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; +// 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(\/|\\{2})es-module(\/|\\{2})test-esm-import-meta\.mjs$/; assert.match(import.meta.filename, fileReg); // Verify that `data:` imports do not behave like `file:` imports. From a77a8a8d88877126f654a2cf2d53e2dba7d25aa5 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Tue, 31 Oct 2023 09:10:36 -0400 Subject: [PATCH 12/12] Tweak Windows regexes --- test/es-module/test-esm-import-meta.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index cae13a3d312e81..50d16a3438a851 100644 --- a/test/es-module/test-esm-import-meta.mjs +++ b/test/es-module/test-esm-import-meta.mjs @@ -21,12 +21,12 @@ 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(\/|\\{2})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(\/|\\{2})es-module(\/|\\{2})test-esm-import-meta\.mjs$/; +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.