Import Maps Get a Second Chance
Import maps promised to solve real performance problems with JavaScript modules — eliminating cache invalidation cascades and cleaning up how developers reference module URLs. But two hard limitations made them practically unusable in real-world pages, especially at Shopify's scale. The constraints: no module could load before the import map, and only one import map was allowed per document. Both had to hold, or modules would throw errors.
For Shopify, this wasn't just about missing out on a nice optimization. It was a correctness failure. After migrating the Online Store Editor to use modules, developers started reporting that modules in themes and App blocks using import maps weren't loading at all. The root cause traced back to a well-known issue: any module loaded before an import map causes the map to throw when it eventually parses.
Similar problems existed for theme authors dealing with App blocks, where different parties' code could conflict. And it wasn't just Shopify's ecosystem — browser extensions injecting modules early could break import maps on any page.
Why the Restrictions Existed
The rules stem from JavaScript's module system guaranteeing that module resolution doesn't change over a document's lifetime. If a module resolves to a URL, that resolution must stay stable. Import maps could violate that guarantee: a map loaded after a module could alter that module's resolution. Similarly, two conflicting import maps could resolve the same bare specifier to different URLs, depending on when modules loaded relative to each map.
The original implementers chose to ban these scenarios outright, throwing errors when they occurred. That decision made sense for shipping import maps, but in practice it meant:
- No module could load before the import map — impossible to guarantee on pages with mixed authorship, like themes with App blocks or user extensions.
- Only one import map could exist — so theme and App developers using import maps would collide with each other and with Shopify's own code.
The single-map restriction created another problem for large applications. Shopify's Admin, for example, is a long-running JavaScript-heavy app. A single import map covering all modules it might load would be enormous, and loading that map upfront — before any module execution, blocking further parsing — would introduce performance costs that could easily negate the caching benefits import maps provide.
A Reconciliation Mechanism
These limitations were known for years, with issues filed in the WICG import-maps repository and discussions going back to the early days of the proposal. The core gap: nobody had designed how multiple import maps would reconcile, either with each other or with modules already loaded.
Working with Jake Archibald, Shopify dove into past discussions and developed a proposal. The mechanism has three parts:
- Each document maintains a single global import map.
- Each document tracks a resolved module set — all module names already resolved in that document.
- A merge algorithm combines every newly encountered import map into the global one, with specific rules about what gets dropped.
The merge algorithm examines the resolved module set and removes any rule in a new map that would have changed the resolution of an already-loaded module. It then merges the new map into the global one, discarding any rules that conflict with existing ones.
In practice: if a new import map tries to redefine something a past-loaded module already resolved, the new rule is ignored. If a new import map conflicts with an existing rule — perhaps added by a past map — the new map loses.
The proposal went through review with Domenic Denicola and Hiroshige Hayashizaki, then moved to the HTML spec working group for broader discussion. Guy Bedford, who authored the es-module-shims polyfill and reported many of the original issues, contributed as well. The spec change landed, Shopify implemented it in Chromium, and WebKit followed after a positive standards position review.
Supporting Older Browsers
Shipping the feature in Chrome and Safari was a necessary step, but Shopify couldn't leave users on older browsers behind. The import map issues weren't merely missing optimizations — they were broken functionality. Theme and App developers on unsupported browsers would still run into the errors if they adopted modules and import maps.
Bedford extended es-module-shims to support multiple import maps, and Shopify began injecting the polyfill into storefronts when a page included an import map and was accessed by a browser without native support. That rollout surfaced edge cases where theme or App developers had already included the polyfill themselves — those conflicts were fixed upstream in the polyfill.
The end result: multiple import maps now work correctly with modules loading before or between them. The feature ships in Chrome starting from version 133 and Safari from 18.4; Firefox has a standards position pending.
For Shopify, this solves a practical deployment problem. Themes, App blocks, and Shopify's own scripts can all use import maps without the coordination burden of a single-map, strict-ordering requirement. Developers get the ergonomics of bare specifiers and the caching performance of immutable, hash-based URLs, without concerning themselves with what else is on the page that might load a module.
What This Means for Developers
Import maps and JavaScript modules can now be used without worrying about inclusion order, and there's no longer a need to preload every module a document might use into the initial map. Shopify's internal teams are already relying on this approach in production, with the flagship Horizon theme built on top of it.
For environments outside Shopify that lack native import map support, you may need to conditionally load the es-modules-shim polyfill — version 2.4.0 or higher. On Shopify, this is handled automatically.
The result is improved correctness and performance on the web, alongside a simpler development workflow for teams building theme-level JavaScript.



