-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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: source hooks #30986
esm: source hooks #30986
Conversation
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.
Some changes, but I'd be hesitant to land these changes if we want to do a more serious overhaul.
I talked with @GeoffreyBooth and am fine with landing this as long as we make the potential for change much more visible as we have discussed other hooks and I synced up with him about how these hooks are different from the design docs hooks we have previously discussed. We should sync up about the design of these hooks being explicitly written out, but this is fine as long as we go with an iterative approach and do not commit to these as final hooks. |
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.
dismissing except for nit, seems ok as we are being more explicit as these are likely to change and merging this PR does not cement the hooks.
agreed to iterate elsewhere afterwards/document instability
Do you think you could set up a benchmark? It would be good to keep a handle on how our loader performance is, and this seems like it might add a lot of overhead (not that I'm saying we shouldn't land it, but rather we should just be aware). |
That would be great, but I’ve never done benchmarks in the Node codebase. Is there something you can point me to to show me how? Can that be its own PR? Also more broadly how would that work? Like I would assume that the benchmarked case is “no custom loaders,” so is what we’re benchmarking just how much slower that case becomes as more hooks are added? Or is your thinking to benchmark “no loaders” vs “with custom loader A” or B or C etc.? |
debab7b
to
129a757
Compare
@GeoffreyBooth benchmarks are here: https://github.com/nodejs/node/tree/master/test/benchmark. From what I can tell they run a specified test a bunch of times. The case I have in mind at the moment is "resolve big tree without hooks", specifically I'm eyeing the added await and two branches in each translator path, which is why I thought it would be good to add the benchmark now. Adding it later also works too. |
Thanks. I’m not sure when I’ll have time for that, so if you don’t mind I’d prefer to do that as a follow-up PR (or anyone else is welcome to). |
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.
Amazing work moving this forward!
Would be nice to use the direct hook functions like with resolve, instead of having the custom wrappers, see the review comments.
I would still prefer to only have a getSource
hook and not also add a transform
hook, since the ability to transform is enabled by the hook composition model, and I don't like the idea of endorsing the concept of transform hooks in the API explicitly, as opposed to simply enabling them.
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.
Thinking about this a little further, I think this is a useful opportunity to also move the format
return to the getSource
hook.
The reason for this is that format
being returned by the resolver was always due to us not having a source hook, and having the source hook return the format is much more useful in the loader workflows (like HTTP loaders).
I think it's important to get that in as part of this.
As discussed further with @GeoffreyBooth I'd be happy to have a |
So the current flow works like this:
So a Later on we probably want the “determine format” logic to be possibly set or overridden by the source returned in |
Combining them would cause problems once adding multiple hooks is supported. |
Yes, with the
Great question - the primary reason is a human / branding one. Including a |
I've extracted a |
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.
Looking good.
'ExperimentalWarning'); | ||
format = legacyExtensionFormatMap[ext]; | ||
} | ||
return { format: format || null }; |
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.
Do we need to make this an object, or can we just permit a string return?
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 guess it depends on if we might extend this in the future to return more data? Perhaps data or functions to pass between hooks?
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.
For getFormat
in particular I feel that this should be an object while we sort out how things like bodies can be ferried between to avoid double parsing/fetching.
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 we perhaps still support the direct string as a sugar case though?
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 the biggest fan of polymorphic return types since that means each stage has to do branching marshaling to/from strings. If we keep it the same as the value returned by getDefaultFormat
that seems better than branching to me. I could be swayed otherwise but would rather start throwing on invalid returns like the documentation example has/had rather than trying to marshal multiple types. I think an alternative of moving getFormat
to be combined with getSource
would remove the need for this data ferrying as well and might be more palpable to myself.
5734d54
to
75ab14a
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.
About the defaultGetFormat
/ defaultGetSource
passed to the hook, I think these will eventually need to be async functions. Right now only a single loader hook is supported but when they can be stacked the defaultFn argument will not be the node.js function but will be an async function provided by another loader hook.
@guybedford I'm worried that if I appreciate your not wanting to encourage unnecessary transform hooks but can this be solved by documentation warnings? In addition I have not seen firm plans for an API to add loader hooks. At one point I was hoping for an API to enable |
When running Why are loaders required to be ESM? As it been considered to also support CJS-exported loaders? |
maybe look into my implamentation https://github.com/direktspeed/esm-loader it works without hooks and is highly configurable via parsers |
7001cb5
to
efa3987
Compare
I pushed some commits last night but this is still WIP. A few TODOs:
|
2aded31
to
7279d14
Compare
I updated the docs to move the loader examples into their own section, where I think they make much more sense rather than haphazardly spread across multiple hooks’ sections. I think that’s everything, @guybedford please let me know if there’s anything else. |
7279d14
to
b1b2df4
Compare
Once CI completes it looks like we will be good to land this. Further final reviews welcome. |
PR-URL: #30986 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Landed in 2551a21. |
PR-URL: #30986 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
Notable changes: * deps: * upgrade to libuv 1.34.1 (cjihrig) #31332 * upgrade npm to 6.13.6 (Ruy Adorno) #31304 * doc: * add GeoffreyBooth to collaborators (Geoffrey Booth) #31306 * module * add API for interacting with source maps (bcoe) #31132 * loader getSource, getFormat, transform hooks (Geoffrey Booth) #30986 * logical conditional exports ordering (Guy Bedford) #31008 * unflag conditional exports (Guy Bedford) #31001 * process: * allow monitoring uncaughtException (Gerhard Stoebich) #31257 PR-URL: #31382
Notable changes: * deps: * upgrade to libuv 1.34.1 (cjihrig) #31332 * upgrade npm to 6.13.6 (Ruy Adorno) #31304 * module * add API for interacting with source maps (bcoe) #31132 * loader getSource, getFormat, transform hooks (Geoffrey Booth) #30986 * logical conditional exports ordering (Guy Bedford) #31008 * unflag conditional exports (Guy Bedford) #31001 * process: * allow monitoring uncaughtException (Gerhard Stoebich) #31257 * Added new collaborators: * [GeoffreyBooth](https://github.com/GeoffreyBooth) - Geoffrey Booth. #31306 PR-URL: #31382
Notable changes: * deps: * upgrade to libuv 1.34.1 (cjihrig) #31332 * upgrade npm to 6.13.6 (Ruy Adorno) #31304 * module * add API for interacting with source maps (bcoe) #31132 * loader getSource, getFormat, transform hooks (Geoffrey Booth) #30986 * logical conditional exports ordering (Guy Bedford) #31008 * unflag conditional exports (Guy Bedford) #31001 * process: * allow monitoring uncaughtException (Gerhard Stoebich) #31257 * Added new collaborators: * [GeoffreyBooth](https://github.com/GeoffreyBooth) - Geoffrey Booth. #31306 PR-URL: #31382
PR-URL: #30986 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
PR-URL: #30986 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
This PR adds additional hooks to the ESM
--loader
implementation:getSource
allows a user to override the function that Node’s ESM loader uses to retrieve the source code for a specifier (e.g. loading a JavaScript file from disk). A loader that overrides this can do things like load from cache or memory (like tink or yarn pnp) or from other sources likehttps://
URLs. An example of the former is in the tests, and the latter is in the docs.transformSource
allows a user to mutate the source code string or buffer after it’s been retrieved viagetSource
but before Node does anything else with it. This allows for things like a transpiler loader (TypeScript, CoffeeScript, JSX, Babel, etc.). This is separate, and intentionally smaller in scope, than a potentialtranslate
hook which could override one of the existing translators (such as formodule
,commonjs
,wasm
etc.; seelib/internal/modules/esm/translators.js
).See discussion in nodejs/modules#351. cc @nodejs/modules-active-members
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes