Bringing import maps to JS: `import "./importmap.json" with { type: "importmap" }`?

HTML defines import maps that map specifiers to URLs (and presumably can be extended to map specifiers to other specifiers), however as currently defined, the feature depends on HTML, which requires central management and coordination to use import maps to rewrite client-side dependencies to URLs, as even transitive dependencies need to be mapped one by one.

What if import maps could also be included via import attributes?

import "/importmap.json" with { type: "importmap" };

Perhaps type is not the right attribute, since it could be quite useful to import import maps written in JS (which can have their own dependency graph, not just better syntax), so perhaps we need a separate attribute:

// JSON syntax
import "/importmap.json" with { type: "json", is: "importmap" };
// JS syntax
import "/importmap.js" with { is: "importmap" };

Though another direction seems to be registering a separate media type for them, which would lock the syntax to JSON. I’m not aware of any more recent work in that direction however.

In terms of resolution semantics, import maps would only be able to affect specifier resolutions of modules imported after them. Conflicts would be handled in the same way as is already defined (first one wins) or via runtime error.

Not only could this be huge for making it possible to use client-side dependencies without bundlers, it could also help decouple dependency resolution from the JS runtime used. And if this is implemented before external import maps, it could even provide a better solution for that. Currently authors are stuck including import maps as a JSON island embedded in every single HTML page which is atrocious for DX, caching, etc.

If folks think this could be a good idea, I’d be happy to draft a proposal for the Jan plenary.

1 Like

You should talk to the people working on Compartments and related proposals, most recently presented in 2025-09 in the form of the module global proposal (see slides). Unfortunately I don’t know if there’s an effective way to reach the set of those people in general but you can probably ping Kris Kowal on Matrix at least.

Their focus has been on security concerns, and none of the repos are in clear or accurate state currently, unfortunately (so don’t spend your time looking at these repos - again, my understanding is that the people working on these proposals have something in mind other than what is currently reflected there). But, as I understand the point of Compartments is to allow evaluating a module in a way which provides control over its imports. In the above-linked notes there is mention that they got feedback (possibly from me, I forget) that this should ideally be integrated with import maps, which I imagine to be something like, you could create a Compartment and apply an import map such that code which does an import within the Compartment uses that import map.


Separately, given how imports work in JS I don’t think it is feasible to have one import affect the behavior of subsequent imports, because resolution of all dependent modules happens prior to evaluation of any of them. Also it’s just weird for an import to have side effects like that. But the Compartment model is probably feasible.

1 Like

It's an appealing idea to bring import maps back to the language itself, but as Kevin mentions there area some concerns here around the concept of a load-blocking import, since currently all imports resolve and load in parallel. If we wanted to allow import '/map.json' with { type: 'importmap' }; import 'foo' to work with foo being resolved through the map, that might well meet some resistance as a new blocking import behaviour.

For Node.js, there is also the problem that we don’t really want arbitrary dependencies loading in import maps, and would likely need a way to reason around that concern. Imagine importing a react plugin and it forces your react version to a specific CDN version say. Perhaps Node.js could ensure that maps are not loadable by dependencies in node_modules, but the cross-platform story isn't as clear here.

I think focusing on browsers and picking up on the HTML external import maps support story would be a more fruitful approach here as the missing feature for the web. Having to load both a map and a script isn’t a friction like copying JSON is, so external import maps support on the web is IMO that missing piece to making the HTML integration step easy for users.

Runtimes like Node.js are able to offer support for import maps in turn via their own patterns and conventions once that browser precedent is set (or even in parallel / first, again, like the other aspects here there is no resistance to import maps support in Node.js, only a question of someone doing the work).

With regards to registering the media type for import maps mentioned, I did previously put together a media type registration with IANA about 6 years ago after Google folks indicated this was not a priority for them, and got as far as completing a draft media registration that almost got through the process, but it got stalled out unfortunately due to an IANA area director change late in the process and then a lack of response from the new director at the time. If you or someone else is interested in picking this up I’m more than happy to share details on this previous work or assist in piciking it up again, as it was entirely a process-based blocker and nothing technical or standards based.

Always happy to discuss further, although not sure what the best venue for this discussion is though since it falls between standards.

1 Like

Thank you both for the very thoughtful responses!

I’ll talk to @kriskowal but looking at the slides @bakkot shared, it seems like Compartments may be solving somewhat different problems? If not, can Compartments, in some shape or form, address the problems import maps are currently addressing?

To address some of the points raised:

Note that currently in import maps first one wins. An import map cannot redefine the same specifier, for the same scope. If the conceptual model is that:

  1. import maps map specifiers to URLs/paths
  2. Import maps can only map specifiers that have not already been mapped

…then in Node.js this means import maps can only ever map specifiers that would not otherwise resolve. The only thing that would be deferred is the “Failed to resolve module specifier” error.

A React plugin wouldn’t be able to do that though as the react specifier would be already defined (see above). But it could define a different mapping for its own scope, which seems reasonable?

Hit the nail on the head; this is an instance of a broader problem… Everyone agrees that more collaboration between standards groups would create a more coherent platform, but we don’t even know how or where to do it :sweat_smile:

Compartments are solving the problem of “we want to evaluate code in the same realm but with control over imports and other host-defined behavior”. The reason the champions care about this is security, but there are other reasons to care about this, and solving that problem would also enable using import maps for your own dependencies in a very natural way (i.e., instantiate them in a Compartment which uses that import map). Unlike import maps this does not let you control the imports of every other script on the page, just the ones you are importing yourself (possibly transitively), but I think that’s a good thing (because global coordination is not feasible given how webpages are actually put together).

… Why? I don’t see how that follows from the other points, and that’s not how it works on the web (or in Deno, for that matter, which already supports import maps).

What I mean by this is not about definition ordering which was solved by the recent extension to import maps in browsers.

What I mean is that the import map won’t even be known by the environment until it is loaded and that load waiting requires using dynamic imports in bundles to load an module with the import maps’s resolution if that map is statically imported.

That is, the very value proposition is unfortunately not fitting well with the static parallel loading semantics.

Perhaps consider joining a module harmony meeting on the TC39 calendar if you’d like to discuss this topic further there as well. Very much open to suggestions for collaboration further.

1 Like

The idea was that by the time any import map imports are parsed, specifiers defined through other means (package.json, the platform itself, etc) are already defined, and thus, cannot be redefined, so essentially it is only specifiers that would otherwise throw as unresolved. IOW there is no downside to actually fetching known specifiers, because no import map import is going to change them, so there is also no reason to block.

Deno does not link to import maps via import, so this doesn’t apply.

See above. I could be missing something, but it seems to me that the only thing needed is for imports to unknown specifiers to be in a pending state until the graph has been fully built. Everything else can continue to statically load in parallel just like it always has.

Thanks, I will try!

I think it would be pretty weird if import maps which were added in this way had different semantics from the import maps which already exist, which can redefine specifiers which would otherwise be resolvable.

I agree, but since web platform import maps cannot redefine specifiers, the ship of consistency seems to have already shipped. Also, if imported import maps could redefine specifiers, it sounds like they wouldn’t be implementable anyway.

Web platform import maps can in fact define define specifiers which would otherwise be resolvable. That’s my point. Something like from "foo" is never resolvable (… I think) but from "./foo" is resolvable in the absence of an import map and can be defined as something else by an import map as long as the import map is added before that import.