WICG/import-maps

Allow packagemap script tag to be generated dynamically via JavaScript

bahrus opened this issue ยท 16 comments

This doesn't necessarily contradict anything in the main proposal, because I'm not exactly sure what is meant by:

Inserting a <script type="packagemap"> after initial document parsing has no effect.

But just in case...

Allowing the packagemap script tag to optionally be dynamically generated by JavaScript would open up quite a bit of flexibility. I would think that typically, this would happen in the head tag of the opening index.html. If it is done there, would this qualify as being done before the initial document parsing has been done?

Use cases would be

  1. Pointing to different builds -- customizing referenced libraries based on browser capabilities. This would allow more modern browsers to benefit from smaller downloads (and earlier real adoption, as libraries won't need to wait for the lowest common denominator browser to support the feature before removing the down level compiling). Doing this on the server would be quite difficult, as it would need to maintain a complex lookup between versions of the browser, and which JavaScript capabilities it natively supports.
  2. Different references between dev and production. This could more easily be done by the server, but complicates caching strategies.
  3. Auto generating large lists of packages based on some wild card rules (e.g. paper-*).

Agreed this seems useful.

Would this be assuming multiple package name maps support as well?

This seems problematic. What happens if a packagemap is created while modules are being loaded? Do you block further loading? or do you just block new module scripts? If the latter there could be module identifiers in both trees that will possibly load different modules.

@matthewp good points... it would be important to carefully define this - if this were restricted to inline packages maps populated synchronously (and not source-based) then there would be no need to have any blocking behaviour, and we could continue to throw for unknown maps.

Basically an online code sandbox would benefit from being able to inject the mappings it needs before injecting the sources.

Edit: in addition to the use cases described above

What happens if a packagemap is created while modules are being loaded? Do you block further loading? or do you just block new module scripts? If the latter there could be module identifiers in both trees that will possibly load different modules.

From prior experience with systems like RequireJS which do allow this: being able to remap modules dynamically has the potential to open up a hornets nest of edge cases created by race conditions that you probably don't want to invite into the system.

The cop-out solution seems most appropriate here: just not to allow it all. Demand that dynamic maps are emitted and registered before module loading starts, and have any attempts to register a package map once module loading has already started simply throw an error.

If it isn't possible to cleanly separate the time to generate the dynamic map, and for module loading to begin, I agree it does seem problematic. I'm fine with this being closed if it seems at all problematic.

Not having a way to load additional package maps ruins modularization, in asynchronous loading scenarios. I'm not talking about remapping existing aliases, but to add new ones.

Let me add a use case to further elaborate on (what I think) @dcleao is suggesting.

The problem with a static packagemap is that I need to know the entire tree when my page is loaded. This works fine if I use npm to install all my packages and can generate a tree.

But a more user-friendly way is to just import my own modules and then load 3rd party pacakges from a CDN.

<script type="packagemap">
{
  "packages": { ... },
  "scopes": { ... }
}
</script>
<script type="module">
  import { main } from './main.mjs';
  import { toClipboard } from 'https://cdn.jsdelivr.net/npm/copee/dist/copee.mjs';

  main(toClipboad);
</script>

Now imagine that copee.mjs makes imports to bare specifiers but it's packagemap is not known without reading https://cdn.jsdelivr.net/npm/copee/dist/packagemap.json.

Is the solution here to just include a reference to https://cdn.jsdelivr.net/npm/copee/dist/packagemap.json before importing the module and hopefully the whole (sub) tree is defined?

The thing is that the need for, and location of, the copee module is only known at runtime.

(edit: I meant that the need for loading copee is only known at runtime, however, a map entry that resolves to its location is known at write time. What would not be known at write time would be the additional package map entries that it has for its own internal use, and that would only be loaded when the package itself was loaded too)

The thing is that the need for, and location of, the copee module is only known at runtime.

The need not being known in advance; sure. In some scenarios that will be the case. E.g. when applying dynamic polyfills, that can depend on what features the subject browser supports and what features it does not. But shouldn't you atleast know the location of the polyfills ร  priori in such a case?

Maybe async / dynamic loaded modules are one of those scenarios where different sub-contexts for package maps would make sense? That is: you can add a package map dynamically, but it can only attach to an async 'entry point' module when it is not loaded yet, where it will only apply to module resolution for modules that are directly or indirectly loaded by that specific entry point module and no other.

Perhaps, I'm not into the technical details of how this could be achieved.
However, I'm not talking about polyfills, but of, for example, plugins of a system, which are loaded on demand, and which are so many that it would be infeasible to load/know, a priori, more than their entry points. When loaded, each may carry a package map that deals with its innards.

If you know the locations of those plugins' entry points and also know the location of their package maps as relative to those entry points... then you can also at build-time pull in the constituent package maps and assemble the complete one.

(Assuming stable versioning where file structere for each plugin is never modified, atleast.)

As mentioned in the readme, package name maps, like service workers, are an app-level thing, controlled by the app developer. Pulling in third-party "modularized" package name maps dynamically is not a use case we'll be supporting.

We can support multiple package name maps, but only if they're all loaded before any modules on the page are loaded. The page author needs to take responsibility for pulling them all in ahead of time when creating their page.

That's too bad :-( I really think that it is an essential feature...
And I don't understand why you don't recognize it, or at least discuss it. It seems that you just dismiss it... Am I missing/misunderstanding something?
Is it a way to start simple?
Is it a use case that you don't recognize as real?
Hasn't anybody else presented this use case?
I don't understand.

@rjgotten like you say, while you could build a package map at the server containing all of the package maps, up front, I don't think that that would be an efficient solution, as it would result in an arbitrarily big package map.
Additionally, that solution requires the use of a build tool and I think it would be valuable if it were possible to do this by just placing files in certain locations (and registering the entry points somehow).

The exact semantics for how you can generate import maps via JavaScript are now specified in some detail in the proto-spec. (Basically: you can do so, as long as you do so before any imports ever occur.) So, let me close this.

Nevertheless, my previous comment holds about the use cases we're interested in supporting.

SystemJS is moving ahead with dynamic import map loading because this is a necessary feature for our users.

I have put together a rough specification approach in https://github.com/guybedford/import-maps-extensions#lazy-loading-of-import-maps detailing how we ensure immutable import map extension.

Further discussion on this topic or specs here would be very welcome.