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: add import.meta.dirname and import.meta.filename #48740

Merged
merged 12 commits into from Oct 31, 2023
3 changes: 3 additions & 0 deletions benchmark/fixtures/esm-dir-file.mjs
@@ -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
@@ -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
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
Expand Up @@ -309,6 +309,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
-->

jsumners marked this conversation as resolved.
Show resolved Hide resolved
> Stability: 1.2 - Release candidate
GeoffreyBooth marked this conversation as resolved.
Show resolved Hide resolved

* {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`
GeoffreyBooth marked this conversation as resolved.
Show resolved Hide resolved

<!-- YAML
added: REPLACEME
-->

jsumners marked this conversation as resolved.
Show resolved Hide resolved
> Stability: 1.2 - Release candidate
GeoffreyBooth marked this conversation as resolved.
Show resolved Hide resolved

* {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 @@ -503,7 +532,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 @@ -1065,13 +1094,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
@@ -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
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(\/|\\{2})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$/;
assert.match(import.meta.filename, fileReg);
Copy link
Member

Choose a reason for hiding this comment

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

Do we need any separate tests for Windows paths? What about file: URLs that are loading network paths, like file://server/folder/file.js?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we need any separate tests for Windows paths?

Is this done for the CJS implementation? What would we actually be testing? That path.dirname works with Windows paths?

What about file: URLs that are loading network paths, like file://server/folder/file.js?

That looks like a mounted file system to me.


// 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);
7 changes: 7 additions & 0 deletions test/js-native-api/test_cannot_run_js/entry_point.c
@@ -0,0 +1,7 @@
#include <node_api.h>

EXTERN_C_START
napi_value Init(napi_env env, napi_value exports);
EXTERN_C_END

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)