-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Proposal: import.meta.resolve (was: import.meta.resolveURL) #3871
Comments
Bikeshed: I'm not sure if we want to continue to use "resolve" for this, since the URL Standard doesn't really have that concept. The alternative would be "parseURL". |
I'm strongly in favor of this, although I realize there is indeed a bikeshedding question here. /cc @whatwg/modules for thoughts, especially implementer interest. Spec-wise, it seems like you'd create one function object per module script... no other real choices I can see. Seems fine. Might be a bit fun figuring out whether to use any IDL formalisms or just JS style spec. The bikeshedding is indeed probably the biggest issue here. I think "resolve" is the typical user-exposed name for this operation in a module context; see e.g. Node.js or Webpack. (I thought this went back to the CommonJS standard or AMD as well, but I can't find any evidence.) I guess that's more about module identifiers though. I think we should just run with that, in fact: we should just use the name "resolve", and let this it work on all module resolution. This doesn't matter now, but with package name maps it would then allow nice things like |
@domenic Will there be a way to know the package name of the current module/package, something like |
This looks nice, but conflicts with relative path URLs without leading
returns something like
might tries to resolve Two functions might be needed (one for parseURL and one for #resolve-a-module-specifier). |
@dcleao I expect No. For example,
In this case, it's not clear what |
I've also been following this discussion on the nodejs modules side and am strongly in favour of this. This would simplify a lot of workflows for people using our new APIs (like Worker, mentioned above). I think it could also bring similar benefits to people writing code on the client. |
@hiroshige-g you're right about the potential "conflict". However, I am not sure there are any consumers who really insist on typing |
I have concerns with this being Synchronous if there is intent for this to be shared with Node in any way. The current design of ESM loaders in Node is with asynchronous resolution. If this API was asynchronous it seems like it wouldn't introduce a bottleneck if a microtask was added to resolve when compared to the cost of fetching/loading a Module. That said, I think this API should return a Promise and be asynchronous. Per In addition, without it being more than just sugar for |
Yeah, we wouldn't make this async on the web, so Node may not be able to match, it's true. Node may want a different name for its async resolution algorithm. |
@domenic I'd prefer to use |
On the web URL and module specifier resolution is synchronous. |
@domenic that doesn't really explain why matching would be problematic? Also, is that always going to be the case? Wouldn't there be possible long lookups if loading a package name map, or having an overly large package name map? |
I don't agree that module specifier resolution should be synchronous on the web, as I don't think package name maps should block execution, but rather utilize an asynchronous resolver to avoid this. |
Matching would be problematic for the same reason making And yes, on the web it would be a bad performance degradation to make resolution async, so new things like package name maps will not be introduced to do that. |
I'd suggest we wait on this then until there is a clear solution that doesn't create divergence or performance degradation. |
As @bmeck points out, there is already divergence, so I don't think we should wait on that. We can avoid performance degradation easily by making it sync. So I think the solution is pretty clear. |
I don't understand this comment.
Resolution could stay sync under the hood, I'm just saying exposing the results to JS userland as Promises would allow unification rather than divergence. In addition as @guybedford was pointing out in that alternate thread, blocking Modules seems like degradation already by forcing it to be sync... |
I don't see how making it sync increases performance if package name maps are forced to be prevent Module loading. Perhaps you have a writeup? Currently, it seems like the inverse of what you are stating would be more beneficial. |
Yeah, as I mentioned in WICG/import-maps#48, we are working on porting the writeup/explorations we did in the Blink prototype to the public space. Stay tuned there for updates. We don't want to expose things that are synchronously available as promises. If someone needs to treat a sync result as a promise for e.g. Node.js compatibility, they can use |
Forcing async also makes some use cases difficult (or at least less ergonomic) without top-level await. I would probably just use |
Thanks @hiroshige-g. |
Is there any chance of allowing I'd like to be able to follow a chain of relative imports in a reliable way (respecting import maps and all that), and it doesn't seem possible to retrieve the I'm happy to be redirected to any prior discussion of this topic, of course. |
For For |
@domenic Ahh, thanks for clarifying that distinction. Are you envisioning a world where we have both By the way, @guybedford directed me to his PR adding support for |
Ergonomics, simplicity, and perhaps a tiny bit of performance. In particular it's not ideal if using
Agreed; that was my understanding as well. The only thing I can think of is if we wanted to support adding new import maps after the initial ones load, but that is fraught with danger (as it changes how the same specifier resolves over time). |
Hey, regarding sync vs. async
This seems to be the case for:
So mostly - I'm confused :] I personally kind of hate async APIs but since this API doesn't take a function and if it throws will always be the last developer sees in their stack trace - I'm not too concerned. Any time I'd use this I'm already likely before an (I have absolutely no opinion about the name and my opinion about whether should resolve be sync is obviously not made up + doesn't really matter here as I hold no position in whatwg or Node's (active) modules team) |
Resolution of module specifiers does not follow redirects.
Resolution of module specifiers does not take into account network-layer concerns like HSTS.
As discussed above, we think this is unlikely, because it would mean that a module specifier could change its resolution over the lifetime of the program. All import maps need to be present before any module scripts start executing (and thus, before any module resolution happens).
Resolution of module specifiers doesn't require any information about the current page's capabilities. It only requires the referring module script's base URL (currently) and its import map (in the future). |
@domenic thank you for clarifying I think I understood what In retrospect I'm not sure why I assumed that since the initial post and proposal just says "returns the WhatWG URL normalization applied for url relative to import.meta.url, and returning the fully normalized URL string.". Now I'm just confused about why Node would want this asynchronously if |
Well, it is a bit confusing. The original post, and some of the discussion here, seems to be discussing sugar for |
I am quite worried about a sync Consider the case of a large application where the import map is very large. The import map blocking all scripts from executing on the page then becomes the critical performance bottleneck and concern for the entire application performance. In these applications deferring parts of the import map until they are needed is an incredibly useful optimization, and this is enabled by allowing import maps to be deferred after initial bootstrap executions. When the import map is deferred, the problem is how to handle resolution when there are "inflight" import maps. It seems like there are a few options here:
I'm not sure this is the best place to dive into these deep topics now, but I'm also not sure where that place is - since no one has yet created an open standards forum for discussion and consensus-building around these topics. For now, for the reasons above I would be very weary shipping a synchronous resolver. I've chatted briefly with @yoavweiss and @domfarolino about these optimization concepts before, and I hope we can make a decision here that doesn't restrict performance optimization of the web in future. |
I'm not really comfortable with any form of deferred import maps, since that breaks the idempotency guarantees of module specifier resolution. Once modules have executed, the import maps need to be locked. |
@domenic this has nothing to do with what you are or are not comfortable with! This is about the web and what is best for future performance. If you want to drag this argument into the weeds of how to define an immutable import map extension process, I'd be glad to do that - but let's not ship anything based on "disallowing discussion" please. |
I don't see any need to make this personal, based on my turn of phrase. Let me be clearer: I would be opposed to shipping such a thing in Chrome, or incorporating such a thing into the HTML Standard. I am confident it is possible to have performant web applications without changing the meaning of module specifiers over time. I'll step back from this discussion as it seems like it's gotten unproductive. |
I'm not sure I understand, so you'd expect Even assuming you could load import maps dynamically - wouldn't it be better for What about a sync If I understand #3871 (comment) correctly |
Thanks @benjamingr for engaging in the discussion on these questions.
Import maps, as specified today, will always block all module scripts from executing until the entire import map has completed loading. At that point, no further import maps can ever be loaded into the page.
It depends - imagine you have a core app and third-party user plugins. The core app loaded with a boot time import map. The third-party user plugins are currently being installed with an in-flight import map. Now if your core app wanted to determine where a third-party user plugin asset is located (the major use case of A sync resolve can certainly work with the above workflow with the extra userland wait step like with the API you suggest. To be completely honest I'm not 100% what is best here - it helps a lot to discuss it! @domenic I just think there's a lot of depth to the concerns here, and am frustrated yes that it isn't possible to openly discuss it anywhere. Suggestions welcome re the right forum. |
Thanks I think I understand the difference in perspectives now. Honestly from an ergonomics standpoint I would prefer .resolve to by sync but that's just my Node.js PoV and I haven't really written many apps that are like the ones you describe nor have I considered that use case in depth. I definitely see a challenge in dependencies wanting to use import maps themselves with sync resolution. So IIUC the use case we are concerned with is:
Wouldn't I have a place where I would have to "import" said second map? How would the resolve call even know how to wait? (If it waits, what happens if I add a map while it's waiting? What if it resolves and then I load a module?) I would expect an async resolve to get a dependency list or similarly be explicit about what it's waiting for. Otherwise even if it's async it would be very easy to .resolve before an import map is "in flight" wouldn't it? (Keep in mind I am very likely the least knowledgeable participant on this topic here - and if you run out of patience feel free to disengage, I doubt I will be able to contribute much philosophy and don't have a lot of context, I am just asking a bunch of questions and learning a bunch) |
The model of dynamic import maps extension can be made well-defined by considering the following:
In this way you can progressively load an application import map and modules based on the user interactions with it. The dynamic resolver hook here is key - the resolver is a very natural home for enabling this import map injection in a well-defined way - as we need to be able to have all module resolutions to the new slices be able to trigger and wait on this hook for the whole import graph, while we load the full dependency information. If we make |
Ok, I've slept it over and I think I (generally) understand - thanks. That mental model (of the large immutable map we load chunks of vs. a mutable map) sounds pretty cool. I honestly don't know if having a synchronous resolve (and in that model should it ever be implemented throw an If "dynamic" import maps are ever supported - I would expect Otherwise in the above example if the import map is "as large as the whole npm registry" I would definitely need a way to "unload" parts of the import map or otherwise manage what parts are loaded. I think I understand now why we'd want an async
|
This brings up the critical point actually - it is actually crucial in a dynamic import maps model that
This is done in the spec through the dynamic import and import host hooks, and having the resolver itself form the asynchronous blocking point for import map readiness, before resolutions are completed. Thus, in the HTML spec, the resolver being async allows for optimized lazy loading with deferred import maps. And this is my concerns with exposing a sync resolver call, as that might exactly block this future work. |
If I don't explicitly have a dependency between an Or are you saying what import map each specifier comes from is established ahead of time (in some "parent import map" and the |
One way that If a user attempts to load a bare specifier that does not have a map, it would throw an error. Having a global event / hook that can handle this case before the error can allow dynamic injection of just in time resolution information. This does contrast with allowing arbitrary URL mappings in import maps though, a feature of import maps I am against actually, for related reasons. @benjamingr I'm glad we could engage in some of these discussions here given that there haven't been other avenues to date. Instead of continuing this related tangent further, perhaps we can aim to bring more of these discussions over to the WICG discord in future if that seems the right home for this stuff. |
Nit: can we / should we rename this issue to match the (agreed upon?) |
These have proven over time to be much less interesting than other future extensions we might spend time on. In particular: * Give up on fallback support. Closes #76, closes #79, closes #83, closes #84. Also closes #79 by removing all the potential complexity there; we can instead discuss on whatwg/html#3871 and whatwg/html#5572. * Give up on import: URLs. Closes #71, closes #149. * Give up on built-in module remapping.
Motivation
With
import.meta.url
support in Module Scripts, common asset loading workflows look something like the following:With an
import.meta.resolveURL
function, these workflows can be simplified to:much more clearly indicating the intent of these common loading scenarios.
The added benefit of the above is that we bring static analyzability back to these workflows, in that build tools can now statically determine where assets and workers are being resolved and handle rewriting of these kinds of expressions. In this way assets and workers can much more easily be updated to point to optimized resources during builds than requiring build tools to try and analyze custom URL manipulations, which may vary more than the initial examples above and lead to more unreliable results. This would, for example, be useful for us in Rollup to be able to provide these optimizations more easily to users.
Proposal
The proposal is a function of the following form:
Which returns the WhatWG URL normalization applied for
url
relative toimport.meta.url
, and returning the fully normalized URL string.I'd be happy to assist with any spec work here as well.
Feedback welcome!
The text was updated successfully, but these errors were encountered: