-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
esm: add import.meta.node.resolveURL
#49246
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -377,7 +377,34 @@ behind the `--experimental-import-meta-resolve` flag: | |||||||||||||||||||
* `parent` {string|URL} An optional absolute parent module URL to resolve from. | ||||||||||||||||||||
|
||||||||||||||||||||
> **Caveat** This feature is not available within custom loaders (it would | ||||||||||||||||||||
> create a deadlock). | ||||||||||||||||||||
> create a deadlock). Use [`import.meta.node.resolveURL`][] instead. | ||||||||||||||||||||
|
||||||||||||||||||||
### `import.meta.node.resolveURL(specifier[, parentURL])` | ||||||||||||||||||||
|
||||||||||||||||||||
<!-- | ||||||||||||||||||||
added: REPLACEME | ||||||||||||||||||||
--> | ||||||||||||||||||||
|
||||||||||||||||||||
> Stability: 1 – Experimental | ||||||||||||||||||||
|
||||||||||||||||||||
* `specifier` {string|URL} The module specifier to resolve relative to the | ||||||||||||||||||||
`parentURL`. | ||||||||||||||||||||
* `parentURL` {string|URL} The URL to resolve the specifier. **Default:** `import.meta.url`. | ||||||||||||||||||||
* Returns: {Promise} Fulfills with a {URL} representing the absolute URL of the | ||||||||||||||||||||
resolved specifier. | ||||||||||||||||||||
Comment on lines
+393
to
+394
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it would be confusing that this is async while There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps name it |
||||||||||||||||||||
|
||||||||||||||||||||
> **Caveat**: This is a Node.js specific API, consider using | ||||||||||||||||||||
> [`import.meta.resolve`](#importmetaresolvespecifier) for a more portable code | ||||||||||||||||||||
> if you don't need the second argument and do not intend your module to run in | ||||||||||||||||||||
> a custom loader. | ||||||||||||||||||||
|
||||||||||||||||||||
```js | ||||||||||||||||||||
import { readFile } from 'node:fs/promises'; | ||||||||||||||||||||
|
||||||||||||||||||||
const data = await readFile(await import.meta.node.resolveURL('./data.txt'), 'utf-8'); | ||||||||||||||||||||
// This is equivalent to (but also available in custom loaders): | ||||||||||||||||||||
const data2 = await readFile(new URL(import.meta.resolve('./data.txt')), 'utf-8'); | ||||||||||||||||||||
Comment on lines
+404
to
+406
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment makes it sound like the
Suggested change
|
||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
## Interoperability with CommonJS | ||||||||||||||||||||
|
||||||||||||||||||||
|
@@ -514,7 +541,7 @@ They can instead be loaded with [`module.createRequire()`][] or | |||||||||||||||||||
Relative resolution can be handled via `new URL('./local', import.meta.url)`. | ||||||||||||||||||||
|
||||||||||||||||||||
For a complete `require.resolve` replacement, there is the | ||||||||||||||||||||
[import.meta.resolve][] API. | ||||||||||||||||||||
[`import.meta.node.resolveURL`][] API. | ||||||||||||||||||||
|
||||||||||||||||||||
Alternatively `module.createRequire()` can be used. | ||||||||||||||||||||
|
||||||||||||||||||||
|
@@ -1710,6 +1737,7 @@ 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.node.resolveURL`]: #importmetanoderesolveurlspecifier-parenturl | ||||||||||||||||||||
[`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 | ||||||||||||||||||||
|
@@ -1728,7 +1756,6 @@ for ESM specifiers is [commonjs-extension-resolution-loader][]. | |||||||||||||||||||
[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]: #https-loader | ||||||||||||||||||||
[import.meta.resolve]: #importmetaresolvespecifier | ||||||||||||||||||||
[load hook]: #loadurl-context-nextload | ||||||||||||||||||||
[percent-encoded]: url.md#percent-encoding-in-urls | ||||||||||||||||||||
[special scheme]: https://url.spec.whatwg.org/#special-scheme | ||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
'use strict'; | ||
|
||
const { URL } = require('internal/url'); | ||
const { getOptionValue } = require('internal/options'); | ||
const { kEmptyObject } = require('internal/util'); | ||
const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve'); | ||
|
||
/** | ||
|
@@ -15,6 +17,7 @@ function createImportMetaResolve(defaultParentURL, loader, allowParentURL) { | |
* @param {string} specifier | ||
* @param {URL['href']} [parentURL] When `--experimental-import-meta-resolve` is specified, a | ||
* second argument can be provided. | ||
* @return {URL['href']} | ||
*/ | ||
return function resolve(specifier, parentURL = defaultParentURL) { | ||
let url; | ||
|
@@ -56,6 +59,30 @@ function initializeImportMeta(meta, context, loader) { | |
} | ||
|
||
meta.url = url; | ||
meta.node = { | ||
/** | ||
* @param {string | URL} specifier | ||
* @param {URL['href'] | URL} [parentURL] | ||
* @return {Promise<URL>} | ||
*/ | ||
async resolveURL(specifier, parentURL = url) { | ||
try { | ||
const { url: resolved } = await loader.resolve(specifier, parentURL, kEmptyObject); | ||
return new URL(resolved); | ||
} catch (error) { | ||
switch (error?.code) { | ||
case 'ERR_UNSUPPORTED_DIR_IMPORT': | ||
case 'ERR_MODULE_NOT_FOUND': { | ||
const { url: resolved } = error; | ||
if (resolved) { | ||
return new URL(resolved); | ||
Comment on lines
+74
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does |
||
} | ||
} | ||
} | ||
throw error; | ||
} | ||
}, | ||
}; | ||
|
||
return meta; | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,105 @@ | ||||
// Flags: --experimental-import-meta-resolve | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is unnecessary?
Suggested change
|
||||
import { spawnPromisified } from '../common/index.mjs'; | ||||
import * as fixtures from '../common/fixtures.mjs'; | ||||
import assert from 'node:assert'; | ||||
import { spawn } from 'node:child_process'; | ||||
import { execPath } from 'node:process'; | ||||
|
||||
assert.deepStrictEqual(await import.meta.node.resolveURL('./test-esm-import-meta.mjs'), | ||||
new URL('./test-esm-import-meta.mjs', import.meta.url)); | ||||
assert.deepStrictEqual(await import.meta.node.resolveURL(new URL(import.meta.url)), | ||||
new URL(import.meta.url)); | ||||
{ | ||||
// Testing with specifiers that does not lead to actual modules: | ||||
const notFound = await import.meta.node.resolveURL('./notfound.mjs'); | ||||
assert.deepStrictEqual(notFound, new URL('./notfound.mjs', import.meta.url)); | ||||
const noExtension = await import.meta.node.resolveURL('./asset'); | ||||
assert.deepStrictEqual(noExtension, new URL('./asset', import.meta.url)); | ||||
await assert.rejects(import.meta.node.resolveURL('does-not-exist'), { code: 'ERR_MODULE_NOT_FOUND' }); | ||||
} | ||||
assert.strictEqual( | ||||
`${await import.meta.node.resolveURL('../fixtures/empty-with-bom.txt')}`, | ||||
import.meta.resolve('../fixtures/empty-with-bom.txt')); | ||||
assert.deepStrictEqual( | ||||
await import.meta.node.resolveURL('../fixtures/empty-with-bom.txt'), | ||||
fixtures.fileURL('empty-with-bom.txt')); | ||||
assert.deepStrictEqual( | ||||
await import.meta.node.resolveURL('./empty-with-bom.txt', fixtures.fileURL('./')), | ||||
fixtures.fileURL('empty-with-bom.txt')); | ||||
assert.deepStrictEqual( | ||||
await import.meta.node.resolveURL('./empty-with-bom.txt', fixtures.fileURL('./').href), | ||||
fixtures.fileURL('empty-with-bom.txt')); | ||||
await [[], {}, Symbol(), 0, 1, 1n, 1.1, () => {}, true, false].map((arg) => | ||||
assert.rejects(import.meta.node.resolveURL('../fixtures/', arg), { | ||||
code: 'ERR_INVALID_ARG_TYPE', | ||||
}) | ||||
); | ||||
assert.deepStrictEqual(await import.meta.node.resolveURL('http://some-absolute/url'), new URL('http://some-absolute/url')); | ||||
assert.deepStrictEqual(await import.meta.node.resolveURL('some://weird/protocol'), new URL('some://weird/protocol')); | ||||
assert.deepStrictEqual(await import.meta.node.resolveURL('baz/', fixtures.fileURL('./')), | ||||
fixtures.fileURL('node_modules/baz/')); | ||||
|
||||
await Promise.all([ | ||||
|
||||
async () => { | ||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||||
'--input-type=module', | ||||
'--eval', 'console.log(typeof import.meta.node.resolveURL)', | ||||
]); | ||||
assert.strictEqual(stderr, ''); | ||||
assert.strictEqual(stdout, 'function\n'); | ||||
assert.strictEqual(code, 0); | ||||
}, | ||||
|
||||
async () => { | ||||
const cp = spawn(execPath, [ | ||||
'--input-type=module', | ||||
]); | ||||
cp.stdin.end('console.log(typeof import.meta.node.resolveURL)'); | ||||
assert.match((await cp.stdout.toArray()).toString(), /^function\r?\n$/); | ||||
}, | ||||
|
||||
async () => { | ||||
// Should return a Promise. | ||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||||
'--input-type=module', | ||||
'--eval', 'import "data:text/javascript,console.log(import.meta.node.resolveURL(%22node:os%22))"', | ||||
]); | ||||
assert.strictEqual(stderr, ''); | ||||
assert.strictEqual(stdout, 'Promise { <pending> }\n'); | ||||
assert.strictEqual(code, 0); | ||||
}, | ||||
|
||||
async () => { | ||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||||
'--input-type=module', | ||||
'--eval', 'import "data:text/javascript,console.log(`%24{await import.meta.node.resolveURL(%22node:os%22)}`)"', | ||||
]); | ||||
assert.strictEqual(stderr, ''); | ||||
assert.strictEqual(stdout, 'node:os\n'); | ||||
assert.strictEqual(code, 0); | ||||
}, | ||||
|
||||
async () => { | ||||
// Should be available in custom loaders. | ||||
const { code, stderr, stdout } = await spawnPromisified(execPath, [ | ||||
'--no-warnings', | ||||
'--experimental-loader', | ||||
'data:text/javascript,console.log(`%24{await import.meta.node.resolveURL(%22node:os%22)}`)', | ||||
'--eval', | ||||
'setTimeout(()=>{}, 99)', | ||||
]); | ||||
assert.strictEqual(stderr, ''); | ||||
assert.strictEqual(stdout, 'node:os\n'); | ||||
assert.strictEqual(code, 0); | ||||
}, | ||||
|
||||
async () => { | ||||
const cp = spawn(execPath, [ | ||||
'--input-type=module', | ||||
]); | ||||
cp.stdin.end('import "data:text/javascript,console.log(`%24{await import.meta.node.resolveURL(%22node:os%22)}`)"'); | ||||
assert.match((await cp.stdout.toArray()).toString(), /^node:os\r?\n$/); | ||||
}, | ||||
|
||||
].map((fn) => fn())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.