Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

esm: backport dirname and filename to v20 #50502

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions benchmark/fixtures/esm-dir-file.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import assert from 'assert';
assert.ok(import.meta.dirname);
assert.ok(import.meta.filename);
5 changes: 5 additions & 0 deletions benchmark/fixtures/load-esm-dir-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(async function () {
for (let i = 0; i < 1000; i += 1) {
await import(`./esm-dir-file.mjs?i=${i}`);
}
}());
1 change: 1 addition & 0 deletions benchmark/misc/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
35 changes: 34 additions & 1 deletion doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,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`

<!-- YAML
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`][].

> **Caveat**: only present on `file:` modules.

### `import.meta.filename`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.2 - Release candidate
Comment on lines +313 to +330
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not clear if I should be changing these YAML blocks, and what I should be altering them to if so.

Copy link

@Mifrill Mifrill Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsumners as I understand, the REPLACEME is placeholder for a version that should be a release version, like this:

<!-- YAML
added: 1.2.0
-->

Hence, this readme file should be updated on each release with the accorded version.


* {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.
Expand Down Expand Up @@ -502,7 +531,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

Expand Down Expand Up @@ -1063,13 +1092,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
Expand Down
13 changes: 12 additions & 1 deletion lib/internal/modules/esm/initialize_import_meta.js
Original file line number Diff line number Diff line change
@@ -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');

/**
Expand Down Expand Up @@ -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);
}
Expand Down
16 changes: 15 additions & 1 deletion test/es-module/test-esm-import-meta.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);