-
Notifications
You must be signed in to change notification settings - Fork 30k
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: implement import.meta.resolve and make nodejs: scheme public #31032
Conversation
//cc @nodejs/modules-active-members |
Discussion issue: nodejs/modules#458 |
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.
I'm not a fan of exposing this in this way, at least not without reconciling with whatwg first. whatwg has expressed interest in a similar design, and I'd like us to match them up if/where possible.
@devsnek as mentioned in the issue comment, this PR is to start exactly those discussions, and is blocked on them. |
@guybedford oh i see, I started writing my review before you posted that comment :) Glad to see we're on the same page 👍 |
Can we prevent that? It feels constraining when |
|
There would be nothing wrong with a custom resolver hook supporting resolving to files that don't exist and instead throw during |
If you're fine actually touching the file, it feels like you could just
It feels like it encourages people to assume that just because they could resolve it, there's some sort of guarantee that the returned string can be loaded. |
If we want to move the existence check from the resolver to the instantiation phase, we can do that, but that should be considered a separate concern to this PR. Also someone would need to justify and create that PR before stability. |
Right now we don't leak when exactly the exception happens to |
@jkrems it is definitely not an option to |
Right, but that’s not actually possible to derive from the resolution result. As shown above, file permissions are enough for a resolved path not to be valid for import. And this doesn’t touch things like race conditions or loaders generating files on-the-fly (or hiding them even though they exist on disk). What is a use case where you need to know that you could require something but where you can’t try to actually require it? |
That there are more reasons why an import might not succeed (including file permissions, transitive imports failing, exceptions) doesn't change that "the specifier doesn't resolve to something" is an important bit to know prior to attempting to evaluate it. One example in particular, this bit helps you catch a programmer error on the importing side using eslint-plugin-import, which is used in eslint-config-airbnb. |
I know (and actively use) that plugin. But if that plugin wanted to start using My primary problem with leaking it is quite simple: It means that (*) Yes, node's default resolver accesses the file system. But one can implement the ES resolver using a "small" in-memory data structure that doesn't require knowledge about every single file on disk (an import map). If file existence is required to implement resolution, import maps are no longer sufficient to model import URL resolution in node. To me that's a big property to give up on for a pretty fuzzy check of "could this URL actually be fetched". |
For illustration of why I wouldn't want |
How would your alternative handle different kinds of exceptional cases, like in that snippet? How would the API return a success or multiple kinds of error values on the same channel, and why would that be an improvement? |
I assume the only actual error case you'd likely be interested in would be that the file doesn't exist. And that can be checked by stating it. Without running the entire loader hooks chain (including source hooks and/or transforms), we won't be able to return any of this info longer term if loader hooks are registered. So there would be a I'm happy to support an |
If i have to manually stat it, then “there’s a filesystem” isn’t abstracted from users of the resolver. One of the big benefits is to abstract that away. I shouldn’t need any fs methods at all to resolve a specifier, just the abstraction. |
78ceef4
to
0133b76
Compare
I think I acknowledged that when I suggested that there could be a second function ( Especially because |
i think this argument may be a bit too isolated on the specific semantics of resolve. taking a step back, we have a pipeline somewhat like this:
The URL given to Any behaviour needed to go from The constraints of this model dictate that it must be assumed that |
I think that's fair. There's definitely exceptional circumstances where resolution cannot determine what the final |
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.
RSLGTM assuming this is
- green CI
- behind a flag
What else is needed for us to land this? |
I think this is ready to land unless Guy protests. |
14bfcf4
to
9db74ae
Compare
PR-URL: #31032 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Myles Borins <[email protected]>
Landed in 0f96dc2. |
PR-URL: #31032 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Myles Borins <[email protected]>
The signatures of |
PR-URL: nodejs#31032 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Myles Borins <[email protected]>
Backport-PR-URL: #32610 PR-URL: #31032 Reviewed-By: Jan Krems <[email protected]> Reviewed-By: Myles Borins <[email protected]>
This PR implements
import.meta.resolve
as an async hook into the module resolver.This fills the gap of enabling
require.resolve
workflows that aren't currently possible in ES modules, such as locating the exact path of a dependency or local resolution following all the correct resolver resolution rules.This is a feature that will require alignment with browsers, so I'm opening this now to be able to start those discussions. Marking as blocked on consensus discussions.
Usage examples:
await import.meta.resolve('./dep.ext')
->file:///path/to/dep.ext
, relative to current module.await import.meta.resolve('./dep.ext', 'file:///different/path/parent')
->file:///different/path/dep.ext
, relative to provided parent.await import.meta.resolve('dependency')
->file:///path/to/node_modules/dependency/main.js
, as resolved from current module.await import.meta.resolve('dependency', 'file:///different/path/parent')
->file:///different/path/node_modules/dependency/main.js
, as resolved from provided module.await import.meta.resolve('./does-not-exist')
-> Throws not found as the default Node.js resolver checks for existence.await import.meta.resolve('fs')
->nodejs:fs
If the Node.js resolver has been hooked by a loader, the corresponding resolve function will be called in
import.meta.resolve
.This API also makes the
nodejs:fs
internal builtin module scheme public, including the associated changes for this feature.Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes