-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
module: add import map support #50590
base: main
Are you sure you want to change the base?
Conversation
Review requested:
|
cf66b50
to
196df55
Compare
lib/internal/modules/esm/loader.js
Outdated
@@ -391,6 +396,7 @@ class ModuleLoader { | |||
conditions: this.#defaultConditions, | |||
importAttributes, | |||
parentURL, | |||
importMap: this.importMap, |
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 don't think this is transferable, meaning this will throw as soon as any custom loader is registered. Can you add a test with a loader?
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.
Yeah, this currently doesn't work. Frankly I am not sure how we do want it to work. Do we want to pass just the "raw" import map object and add handling inside the worker to re-hydrate the ImportMap
class with it? I am struggling to decide if there is a good use case to expose this to other loaders. Would it instead make sense to remove it from context
before passing context to the worker?
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.
Yup. Policies have to xfer the source. Take note though that if Workers are allowed to override the value in your design (since they can manipulate argv)
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.
Can you represent the import map value as a plain object that’s transferable? Like I would assume you should be able to implement toJSON
and fromJSON
methods to your class, and JSON is transferable. Not that I’m suggesting going as far as JSON when an object will do, but if toJSON
/fromJSON
is possible then some kind of transferable data structure is therefore also possible.
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.
Can you represent the import map value as a plain object that’s transferable?
Yep, that is what I meant to say by "Do we want to pass just the raw import map object and add handling inside the worker to re-hydrate the ImportMap class". I just am not sure this makes sense to do. Not only would it be slow but I also am not sure I can think of a use case for accessing/changing the import map in a loader.
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.
Ok, so the initial non-transferable thing was actually the parent URL instance. I resolved that. Now we have a new question of if, when using the customized loader, the import map resolve should happen inside the worker or on the main thread.
I just pushed an implementation where it happens on the main thread, which means that the processing is on the main thread both when using a custom loader and not. What are the benefits of doing the import map resolution in the worker thread.
cc @JakobJingleheimer maybe we continue this conversation here instead of slack?
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.
Ok, in slack I think @GeoffreyBooth made a good case for this needing to be on context
. I think that means I need to make it load the import map inside the worker when using custom loader hooks.
I still think I can avoid reading it twice by checking if there are customization and only loading it on the main thread when there are not.
Could you also fix the commit message to e.g. |
I actually did that at first but then the PR docs and other examples I saw seemed to suggest this was the right way. And the validation already caught |
196df55
to
5663357
Compare
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.
Looks quite good! We also need to add documentation for this. I would add a section to https://github.com/nodejs/node/blob/main/doc/api/module.md, maybe just above “Customization Hooks.” We also need to document the flag in https://github.com/nodejs/node/blob/main/doc/api/cli.md.
throw new ERR_INVALID_IMPORT_MAP('module specifier values must be non-empty strings'); | ||
} | ||
if (specifier.endsWith('/') && !mapping.endsWith('/')) { | ||
throw new ERR_INVALID_IMPORT_MAP('module specifier values for keys ending with / must also end with /'); |
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.
throw new ERR_INVALID_IMPORT_MAP('module specifier values for keys ending with / must also end with /'); | |
throw new ERR_INVALID_IMPORT_MAP('module specifier keys ending with "/" must have values that end with "/"'); |
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.
We have generic helpers like validateString, if there isn’t a validatePlainObject it might be worth creating one.
Not sure why github did not give me a comment box to directly reply to that comment, but here it is. I think that would be good idea and I can add them in this PR if they did not exist. Where would they live if I wanted to go looking for methods like this?`
test/es-module/test-importmap.mjs
Outdated
import import_map from 'internal/modules/esm/import_map'; | ||
const { ImportMap } = import_map; | ||
import binding from 'internal/test/binding'; | ||
const { primordials: { SafeMap, JSONStringify } } = binding; |
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.
In general, you don’t need to use primordials in tests.
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.
The linter told me otherwise IIRC. Happy to do whatever folks recommend, just wanted to call out that I did this because the linter told me to lol.
b60b205
to
5b6df57
Compare
So I looked to see if there was a good way to use the web platform tests, but all of these ones are html tests. For now I just went through and made sure that I covered the gaps I had seen in my original tests instead of using
|
*/ | ||
function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) { | ||
if (specifier === '') { return false; } | ||
if (specifier[0] === '/') { return true; } |
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.
Shouldn't this be path.sep, since Windows have a different path separator?
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 moved this so I could re-use it in import_map.js
, it is just as it was before I moved it. I am thinking maybe this is because imports/require statements are all using nix style, but honestly I am not certain without digging around a bit.
* Determines whether a specifier is a relative path. | ||
* @param {string} specifier - The specifier to check. | ||
*/ | ||
function isRelativeSpecifier(specifier) { |
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.
Moving to a different file should be implemented in a different commit for future observability.
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.
Happy to do that, do folks typically do that as different PRs or multiple commits in the one PR?
5b6df57
to
0afe83f
Compare
b0a4cbb
to
a59b5e5
Compare
a59b5e5
to
a32030d
Compare
Thank you @wesleytodd for pushing this again, I was hoping to do a review here when it is ready, but to comment on a couple of the recent points: For WPT there are data-driven tests in https://github.com/web-platform-tests/wpt/tree/master/import-maps/data-driven/resources which are not at all HTML based. Can we please unmark the WPT checkbox until all those data-driven tests pass? If we put import maps on |
Thanks, I was going to ping you directly when I had what I considered a "ready" state, but thanks for chiming in and if there is anything I can do to ease the review let me know.
Done.
I will look into these the next time I have to work on this. Should be this week sometime.
Yeah, this is my concern for the loaders accessing it as well. I am not sure I understand the use case we for this, and IMO it is easier to expose it later than to try and backtrack around folks doing things we didn't expect.
I think we do need to address them. I would rather not expose them until we had a clear understanding of why so we can write good tests of the use case. Is there a good way to avoid exposing them while also not putting it on context? |
Wanted to make sure I captured this here, from a slack convo with @guybedford: We want to make the |
This comment was marked as spam.
This comment was marked as spam.
Has there been any thought to making the parser API public? I think that would make sense. |
That was not on the initial plan, but I would be interested in understanding your use case. IMO the best way to do this would be to publish that stuff as a package and then either vendor it in or run changes back to the separate project later. That said, it is a lot more effort so I would want to know what folks want to use it for before making any decisions on it. As a separate note, I have not been able to work on finishing up this PR for a while now. I would like to, but work got busy and so did some of my other OSS work. If anyone wants to pair a bit to get this over the finish line I am more than happy to do that. Feel free to reach out here or in the OpenJS #nodejs-loaders channel to discuss. |
I'd like to be able to use it as part of static analysis of the module graph. |
This PR adds experimental support for import maps behind the
--experimental-import-map=<path>
flag. This is a draft as I believe there are still quite a few things to discuss and add:foo
tobar
and then have default resolution onbar
, for example? This might be against spec, but also seems really helpful.node_modules/importmap.json
)--experimental-import-map
? Right now any module specifier is allowed, but do we want that? This was done to enable http import of import maps, but that is not explicitly a goal, just a starting point for discussion.Helpful links: