-
Notifications
You must be signed in to change notification settings - Fork 72
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
How do we install import maps in worker/worklet contexts? #2
Comments
Could the packagemap script have a property on it, |
@matthewp I'd certainly like to see a |
Yeah, that seems pretty reasonable. I think I like the idea of a general property, with some name or another. |
Could we by default just have workers inherit the package map from the parent window? That seems to cover the 99% scenario. Isolation doesn't seem to align with the worker boundary, as much as it does concepts of Realms. I understand the issues for worklets and service workers but I'm' not sure they should be dominating use cases here. |
Hmm, I don't understand. Workers don't share the parent realm so if you're saying isolation is aligned with realms then it is also aligned with the worker boundary. |
I was referring to the proposal to create isolated realms, but it's a somewhat misleading tangent. The question really is what is the use case for having a different package map for the worker, that couldn't be shared with the page? |
Note if we first shipped with the default of having the worker share the package map, then a future proposal could always provide package map isolation. The isolation picture and use cases will be clearer then than they are now to spec something good as necessary, and realms are possibly part of that picture too. |
In the end I am very uncomfortable with sharing module-related features cross-realm. In general cross-realm sharing of state of this sort is largely unprecedented, and I'd be especially unhappy about doing it for modules, which right now are purely tied to realms. I am pretty certain that v0 will not have such a strange new feature as cross-realm sharing, even if we eventually want to introduce an easy way to do so. |
@domenic then we must guarantee that when it comes to implementations of the Realms proposal, that they do not share the package map with the main page by default. |
Also, note that what I'm suggesting here is a default behaviour - there is nothing to say this cannot be overridden. As someone who has worked on build tools in JS for a while, assuming all workers are instantiated with a packagemap argument will be a tooling nightmare - the third-party worker story is really being neglected from all directions currently which is incredibly worrying already. |
I see this is already catered to in the Realms spec (although interesting the Realms spec then effectively hinges on a full blown module API for modules support - tc39/proposal-shadowrealm#103). Passing the package map can work - we'd likely have build workflows that inject it into the new Worker instantiations, while npm packages leave it out. It's just annoying to have non-portable ties on this boundary effectively requiring a build step for third-party libraries with worker instantiations. |
Yes, in general the realms proposal has a large variety of blocking issues on how it interacts with how browsers work with realms. I understand that breaking the current model for how realms work can be convenient, especially for tools or similar. I don't think that means we should do so, especially by default. |
Then make it a boolean option - |
That's an idea. I'd prefer we have a more holistic story for how workers relate to other realms, instead of creating a one-off feature in package name maps. For example it's not clear why a package name map would be easily inherited but not a module map, or URL, or content security policy, or... |
Related: w3ctag/design-principles#111 |
Note that any kind of inheritance would also be "racy" for shared and service workers and therefore not an acceptable solution for them. This feature doesn't seem needed for worklets as they can't fetch anything. |
My understanding is that worklets can use static import statements. (And per spec dynamic ones, but IIUC the sole worklet implementer so far removed support for that JS language feature, but hasn't updated the spec.) |
I see, I doubt the fetching model for that is well-tested. All those module fetches would come with |
If worklets should be able to access built-in modules, we may want to work through how import maps could work, so that these modules can be polyfilled/virtualized. |
As per CSP3 dedicated workers and worklets now inherit their page's CSP so this indicates some precedence.
|
As a web developers, it's trying experiencing inconsistent old outdated capabilities in workers. Article after article highlights the importance of this feature set for keeping a responsive web app & doing computation, but support for Workers has been awful & this ticket is another intimidating document implying that working with workers is going to remain really hard for the forseeable future, & not receiving the benefit of the es2015 modules progress that the happier paths have gotten. We still don't have module 1.0 support in workers on Chrome. Now, I'm finding that when we do get support, it's going to be for modules that won't be able to use import-maps? It's frustrating that,
Modules feels like it was shipped prematurely, and I'd like to avoid going 1.0 with import-maps in a similar fashion, before there's a plan for how it can be used in practice across the platform. |
Thanks for the call to action @rektide. FWIW es-module-shims supports import maps in web workers in Chrome today using In terms of spec work, I think it's just the constraints of prioritization with a serial process with limited people working on this. And I'm sure a spec PR for a worker approach wouldn't be ignored. |
I have to say, it definitely discourages me from working on module stuff, if making incremental progress gets that kind of negative reception. If the demand is that every piece of the puzzle be perfect and full before any specs are accepted or any browsers ship modules, then I don't want to work on modules. Fortunately I think we have a lot of web developers who gain value from what we have today, and appreciate it, instead of saying that it's "premature" to ship anything but a big-bang modules + dynamic import() + import.meta + modulepreload + workers + import maps + import maps in workers + more all at once. So I plan to ignore such sentiments. |
With service workers, the import map should be updatable between versions of the service worker. There isn't a JS call directly linked to service worker updates, so something like a header, or something inline would work better. |
@jakearchibald What makes this case different from changing |
@jakearchibald thanks for clarifying that makes sense. Do you feel this service worker update concern is necessary to work through first before progress can be made on setting an Specifically I really would like to see progress on |
Thinking about the out-of-band configuration case further, another option might be to define an navigator.serviceWorker.register('/latest-worker.js', {
importMapSrc: '/latest-worker-map.json'
}); The import map can then be updated along with the JS just fine, and this fully mirrors the For this reason specifying both The benefit of this over the header might be that the source and import map can be fetched in parallel for the service worker and for service worker updates, rather than as separate requests. Since import maps directly inform further loading, it seems a fairly useful property to have as well. |
Those are initiated via script whereas most service worker updates are not. |
I think that's ok.
Yeah, that's kinda neat. However, I worry that it complicates the security story. Right now, if someone gets to run script on your origin, they can extend the life of that attack by installing a service worker. However, that script needs to be same-origin, and in the same path as the scope (unless a special header is used). Service worker script requests also have special headers, so servers can pick up on unexpected service worker requests.
The header solution doesn't have these issues, and it seems like it would also plug other feature gaps? #2 (comment) |
Yeah, I'm coming around more to the header-based solution. Especially since it looks like we need something similar for speculation rules, based on some recent web developer feedback. |
That's certainly understandable, thanks for pointing this out. Separately import maps with a
Thanks @jakearchibald I'm fine with a header solution for service workers for an out-of-band mechanism, I just think we also need an in-band mechanism in addition, and I just hope this service worker use case doesn't preclude progress on an in-band approach. |
I agree with @guybedford that it would be good to have some approach which can work in-band; it might be hard to configure the header approach for worker import maps, and these are essential for functioning, whereas detailed prefetch/speculation hints work fine if omitted. If we go with a header-based approach, it would be great if we had some clear ideas about the deployment workflows beforehand (web bundles may help!). I agree with Guy's comment that the ideal approach would be to inherit the import map by default. |
Agreed, particularly as blob URLs don't have headers. Also agree that inheriting from the parent should be easy.
I don't see why it would. |
According to the import-map-processing section:
and given the fact that
I prefer the first approach, and my idea is to add an option "importMap" to WorkerOptions, as defined below dictionary WorkerOptions {
WorkerType type = "classic";
RequestCredentials credentials = "same-origin"; // credentials is only used if type is "module"
DOMString name = "";
ImportMapOptions importMap = null; // importMap is only used if type is "module"
};
dictionary ImportMapOptions {
USVString src = ""; // a http/https url, a data url, or a blob url
Blob srcObject = null; // option `importMap.src` takes precedence over `importMap.srcObject` if both specified
}; See also IDL of the Notes:
Examples: You can dynamically generate import maps, let importMapObject = {
imports: {
a: './a.js'
}
};
let worker = new Worker('path/to/worker.js', {
type: 'module',
importMap: {
srcObject: new Blob([JSON.stringify(importMapObject)], {type: 'application/importmap+json'}),
},
}); load an importmap resource manually, let importMapObject = await fetch('path/to/importmap.json').then((res) => {
if (!res.ok) {
throw new Error('Error loading importmap, received status: ' + res.status);
}
let type = res.headers.get('content-type');
if (!/^application\/(importmap\+)?json/.test(type)) {
console.warn('Resource interpreted as importmap but transferred with MIME type ' + type);
}
return res.blob();
});
let worker = new Worker('path/to/worker.js', {
type: 'module',
importMap: {
srcObject: importMapObject,
},
}); or use embed data url as importmap source until someday let worker = new Worker('path/to/worker.js', {
type: 'module',
importMap: {
src: "data:application/importmap+json;base64,...",
},
}); About multiple import maps If you do have multiple import maps to be used, then you may load multiple import maps from multiple sources, parse them into plain JSON objects, and merge them as single Blob, just do it by your self. |
I don't think it needs to be that complicated. Just: const worker = new Worker(src, {
type: 'module',
importMap: {…}
}); Then, if folks want to fetch the import map from somewhere external, they can use For shared/service workers, where the worker creation may have been triggered by another environment, the import map is specified in a header. |
I'm a bit late to the discussion and I already see there are a few folks from the Deno community here suggesting solutions and I wanted to give my 2 cents. The Deno team certainly recognizes that this is a requested feature that could help to solve many common workloads. I also recognize that our restrictions are in some places wildly different than in browsers - we don't need to consider import maps loaded via
I agree with Jake here - but would really love to see a complementary option to specify a URL. In Deno's case a call to That said, I think it's more important to ship a first iteration of the support to get further discussion going and the Deno team is willing to dedicate resources to ship the first implementation on the agreed proposal in a one-month timeframe. |
(Does this discussion allow me to join? Sorry if I overlooked some rules.) I want it to be as easy as possible.
It seems to me that you can just set a rule like "We can use either one, but |
This is a huge missing feature. I just went through a bunch of trouble to get all my import map stuff working properly to work around some oddities in how some libraries reference dependencies in their import statements, and then when moving to web workers it just doesn't work at all and I need a whole different solution and in fact probably just can't use modules at all. |
I'm working on a module which uses a pluggable terrain loader in a worker as well as buildless ESM. I worked around the existing behavior using local links, but quickly realized, when importing it into app space, that I wouldn't be able to import any dependencies because they use named modules. This was just unacceptable, so I took the performance hit to proxy an iframe as a worker so I could inject an importmap into it. So far this is working well (though much more taxing than a worker would be). This is what I did to make that work: https://github.com/environment-safe/esm-worker/blob/master/src/index.mjs#L10-L59 (abandon all hope ye who enter). Feel free to crib the code or use the module. Here's hoping it doesn't take another 5 years to land this as a native feature. 🤞 |
Please just apply the import map to anything which can import a module (it's called an "import map" for a reason guys!!). It's beyond ridiculous in my opinion that we are in 2024 and we're still discussing this. My worklets and workers doesn't function because of this stupidity. A terrible developer experience! Anything but what I suggested IS a terrible developer experience and there are no GOOD arguments against it really. 6 years discussing this... It's a shame. |
I don't think the discussions is what's delaying it here. I think it is because no browser vendor has the capacity to work on it. |
To Domenic's original proposal here, just as a module has a But boy oh boy am I happy to take whatever I can get here! Fine if this is implicit; I really just want anything, badly. Is anyone successfully working around by deploying a https://github.com/joeldenning/import-map-service-worker serviceworker or some other translator? |
What about relative import map paths, would they always resolve against Simple example of relative paths: <script type="importmap">{"imports": {
"esm-worker": "../src/index.js",
"calculator": "./calculator.js"
}}</script> |
We just ran in to this with Construct - we added support for import maps, but our engine supports running in both DOM and worker contexts, and import maps only work in the DOM context. For us it's a configurable option so users can work around by using DOM context, but there are some nice performance benefits to running everything in a web worker, so it's a shame this isn't supported by import maps yet. FWIW, I saw that there's a proposal for dynamic import maps - see also #92. If browsers support dynamically loading import maps rather than only one specified up-front, then it seems this potentially opens the way to an API, e.g. await self.addImportMap(...);
await import("./main.js"); ...and then main.js loads as a module in the worker with the import map specified. Perhaps not ideal and a few roundtrips to the network, but it would get the job done until other questions around how to specify an import map up-front are resolved, and even once they are, presumably the ability to have dynamic import maps in workers would still be useful for the same reasons it's useful in the DOM context. |
It's unclear how to apply import maps to a worker. There are essentially three categories of approach I can think of:
new Worker(url, { type: "module", importMap: ... })
self.setImportMap(...)
orimport "map.json" assert { type: "importmap" }
.Import-Map: file.json
or maybe evenImport-Map: { ... a bunch of JSON inlined into the header ... }
.The worker-creator specified import map is a bit strange:
Basically, there is no clear translation of
<script type="importmap">
into a worker setting.Also, as pointed out below, anything where the creator controls the import map works poorly for service worker updates.
The worker itself specifying seems basically unworkable, for reasons discussed below.
And the header-based mechanism is hard to develop against and deploy.
Original post, for posterity, including references to the old "package name map" name
We have a sketch of an idea for how to supply a package name map to a worker:
This is interesting to contrast with the mechanism for window contexts proposed currently (and discussed in #1):
Basically, there is no clear translation of
<script type="packagemap">
into a worker setting.If we went with this, presumably we'd do the same for SharedWorker. Service workers would probably use an option to
navigator.serviceWorker.register()
, and have an impact similar to the other options?For worklets, I guess you'd do something like
CSS.paintWorklet.setModuleMap({ ... })
. Only callable once, of course.In all cases, it would be nice to make it easy to inherit a package name map. With the current tentative proposal you could do
but this is fairly verbose and a bit wasteful (since the browser has already done all the parsing and processing of the map, and you're making it do that over again). At the same time, inheriting by default seems conceptually weird, since workers are a separate realm.
The text was updated successfully, but these errors were encountered: