From 0946c5a572d687b631c3e08c0a76e07535104e9e Mon Sep 17 00:00:00 2001 From: James Sumners Date: Tue, 11 Jul 2023 17:38:32 -0400 Subject: [PATCH] esm: add __dirname and __filename --- doc/api/esm.md | 36 +++++++++++++++---- .../modules/esm/initialize_import_meta.js | 28 ++++++++++++++- test/es-module/test-esm-import-meta.mjs | 8 ++++- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 68ecad93b8ad3b..635897b3ccbd59 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -308,6 +308,31 @@ 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. Network modules will +> not provide it. + +### `import.meta.__filename` + + + +* {string} The file name of the current module. This is the current module + file's absolute path with symlinks resolved. This is the same as the + [`url.fileURLToPath()`][] of the [`import.meta.url`][]. + +> **Caveat** only local modules support this property. Network modules will +> not provide it. + ### `import.meta.url` * {string} The absolute `file:` URL of the module. @@ -483,13 +508,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. @@ -1606,6 +1624,8 @@ 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`]: #importmeta__dirname +[`import.meta.__filename`]: #importmeta__filename [`import.meta.resolve`]: #importmetaresolvespecifier-parent [`import.meta.url`]: #importmetaurl [`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import @@ -1613,10 +1633,12 @@ for ESM specifiers is [commonjs-extension-resolution-loader][]. [`module.register()`]: module.md#moduleregister [`module.syncBuiltinESMExports()`]: module.md#modulesyncbuiltinesmexports [`package.json`]: packages.md#nodejs-packagejson-field-definitions +[`path.dirname()`]: path.md#dirname [`port.ref()`]: https://nodejs.org/dist/latest-v17.x/docs/api/worker_threads.html#portref [`port.unref()`]: https://nodejs.org/dist/latest-v17.x/docs/api/worker_threads.html#portunref [`process.dlopen`]: process.md#processdlopenmodule-filename-flags [`string`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String +[`url.fileURLToPath()`]: url.md#fileurltopath [`util.TextDecoder`]: util.md#class-utiltextdecoder [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 diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index c548f71bef837a..49d170d9a0b88e 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -1,6 +1,8 @@ 'use strict'; const { getOptionValue } = require('internal/options'); +const { fileURLToPath } = require('url'); +const { dirname } = require('path'); const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve'); /** @@ -32,12 +34,16 @@ function createImportMetaResolve(defaultParentUrl, loader) { * @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 = resolveModuleMeta(url); + meta.__dirname = moduleMeta.__dirname; + meta.__filename = moduleMeta.__filename; + if (experimentalImportMetaResolve && loader.loaderType !== 'internal') { meta.resolve = createImportMetaResolve(url, loader); } @@ -47,6 +53,26 @@ function initializeImportMeta(meta, context, loader) { return meta; } +/** + * Inspect the module URL and convert it to `__dirname` and `__filename` + * references if it does not point to a network module. + * @param {string} url + * @returns {{__dirname?: string, __filename?: string}} + */ +function resolveModuleMeta(url) { + if (url.slice(0, 7) !== 'file://') { + // 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 0151177b21c302..e3b797e2304db9 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 = ['url']; +const keys = ['__dirname', '__filename', 'url']; assert.deepStrictEqual(Reflect.ownKeys(import.meta), keys); const descriptors = Object.getOwnPropertyDescriptors(import.meta); @@ -18,3 +18,9 @@ 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(import.meta.__dirname.match(dirReg)); + +const fileReg = /^\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; +assert(import.meta.__filename.match(fileReg));