Problem/Motivation
Importmaps allow a browser to resolve naked ES imports.
e.g.
import React from 'react';
Ordinarily a browser doesn't understand how to resolve that, but with an import map it can.
However, you can only have one <script type="importmap"> element on a page, so therefore it makes sense to have an API in core to support this.
Adding this API will allow us to do more interesting things with JavaScript. E.g. consider two modules that rely on a JavaScript library. If both of them bundle the same library into their code, there will be two instances of this in the page.
A concrete need for this is for the React module and the Gutenberg and Decoupled layout builder modules. Both of them need React loaded into the page.
Without importmaps, both of them will need to hard-code a reference to the URL of the bundled react libraries provided by the React module.
With importmaps, both of them can configure their bundler (Vite/Webpack) to mark React as external - this will result in the bundler leaving import React from 'react' in the bundled code.
Then the React module could specify an importmap via this new API and Drupal would take care of emitting an <script type="importmap"> into the page.
Proposed resolution
Add an API to core for importmaps. Base this off the Importmaps contrib module.
At present the importmaps module uses hook_page_top to unconditionally add the <script type="importmap"> tag to the page. If we moved this to core, we could integrate with libraries.yml and only add it based on e.g. libraries dependencies.
Remaining tasks
Agree we want to do this.
Adapt the code in importmaps (e.g. YML discovery, plugin manager).
Replace the hook_page_top with an implementation that is built into the asset renderer pipeline and can detect importmap entries to add based on metadata such as libraries.yml. For example the YML file could be extended to list libraries that should trigger the entry being added to an importmap. The asset renderer could collate attached libraries and filter out the importmap plugin definitions accordingly. If any were found, it would emit the tag, if not it would not.
User interface changes
API changes
New plugin manager and YML plugin discovery for importmaps
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #34 | 3398525-nr-bot.txt | 90 bytes | needs-review-queue-bot |
Issue fork drupal-3398525
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
andy-blumI love this idea, and think an addition to the libraries.yml would be the perfect place to declare es module usage. For example from the react module's current usage of the importmaps module:
react.libraries.yml
react.importmaps.yml
To kick off some discussion here, re-imagining this as a single .libraries.yml file could become one of several options, each with their own benefits/drawbacks.
Option 1: Adding importpath info to a JS asset
Pros:
Cons:
Option 2: Adding importpath info to an individual library
Pros:
Cons:
Option 3: Adding importpath info as a separate non-library top level item
Pros:
Cons:
Comment #3
larowlanOption 4
ie another top level entry that isn't css/js
Because we really don't want the file attached as a regular js script. We want the browser to load it on demand.
Then another module that needs react-dom
Comment #4
larowlanJust realised there was no code in the importmaps contrib project, pushed that up
Comment #5
andy-blumI think your option 4 is the same as my option 2, though?
Comment #6
larowlanAh, I saw you still had js: in option 2 so thought it was different. If you're saying the same think I think we should remove the js entry in it to make it clear that that the file needs to be loaded by the browser from the importmap instead of by a script tag/Drupals standard asset rendering
Do you have a preferred option? I think option 2 with the above caveat gets us closest to the intent here
Comment #7
longwaveSymfony's AssetMapper component provides support for importmaps, it doesn't look like we can fully leverage this component directly, but we could learn from it and perhaps use some of their code.
https://symfony.com/doc/current/frontend/asset_mapper.html
Comment #8
prudloff commentedI started a POC contrib module that implements this: https://www.drupal.org/project/importmap
It uses data attributes to avoid breaking the
libraries.ymlYAML schema. But if core implemented this, it could of course be with new properties in the YAML.Comment #9
larowlanHmm, thanks, but feels odd to create a competing module one week after I made one at drupal.org/project/importmaps - we should be collaborating (I mentioned it in comment 4)
Comment #11
nod_in classic drupal fashion we're jumping the gun to implementation before getting the scope cleared up :)
I'm leaning towards option 2 with the "importmap" key to match what it's called in html.
That's why I'm very satisfied we have peast in core now, we can actually look into the js files and extract stuff. Ideally I'd like to dynamically build the dependencies from what's declared in the js file itself to avoid having to double-declare dependencies.
Closed #3331393: Provide management/mechanism for JS/ES6 importmap since this one has more activity and a better issue summary.
Comment #12
prudloff commentedSorry, I did not see your module. I was initially following the duplicate issue and not this one.
I created an issue about merging the two modules: #3399978: Merge with importmap module
We had a concrete need for this and thought it would be better to publish it as a contrib module, to get ideas rolling.
But I agree that we need to think about the best way to declare imports before adding this to core.
My initial thought was that it would make sense to have this in the library definitions since these are in fact libraries. But I agree that we would almost never need to load a JS file both as an import and with a script tag, so I guess it can make sense to a have a separate top-level key (or even a separate YAML file) for this.
We also might want to support scopes.
Comment #13
prudloff commentedI don't think this would work with dynamic imports.
But I am not entirely sure we have to care about dependencies here.
Having a module in the import map has no effect if it is not imported by some other JS module. So we could always include every declared module in the import map and don't bother managing dependencies.
Comment #14
catchIt is only tangentially related but there is also #3366561: Support preloading of fonts in the libraries API where the main barrier is figuring out the definition format.
Comment #15
catchRegardless of the format, anything marked for import needs to be completely excluded from Drupal's asset rendering (and aggregation if it's enabled). Drupal just should not be loading it at all; instead just providing the markup so it can be loaded on demand via the browser.
A top-level entry in libraries would not hurt here, because it then wouldn't be possible to mix imported vs. not scripts in a single library definition.
Can we be certain that we won't get a mix of libraries relying on importmaps vs. libraries relying on library dependencies? That seems like it would not be good.
An issue with taking these files out of the asset rendering system is that they won't get the asset query string or any version information etc. appended unless we handle that too - so maybe we would need to re-implement/re-use at least that bit for cache busting.
Comment #16
larowlanComment #17
larowlanMade a start on this, pausing till next week.
Have added some fake stuff to core.libraries for testing sake and am seeing the console.log
This doesn't work with big pipe and we'll likely have similar issues with Ajax.
In my testing, there is no API to dynamically modify the importmap as new files are loaded.
Trying to remove the import map and add a new one (e.g. in an AjaxCommand) results in
Multiple import maps are not allowed.Trying to rewrite the contents of the existing script tag results in the browser not detecting any new additions.
So I think we're probably going to need to build the importmap based on all known importmaps and output that.
Otherwise if bigpipe or an ajax response add new JS that requires the importmap we're out of luck
I'll work on that and test coverage next week
Comment #18
larowlanGot confirmation that there is no API for dynamic imports - https://github.com/WICG/import-maps/issues/92
So will continue with loading them all upfront
Comment #19
nod_If we hit some limits of the import map api, An alternative solution could be reaching into the js source and rewrite import paths at process/bundle time.
I'm worried that the import map will end up being massive for something like Drupal. Maybe not, we'll see when we get there I guess.
Do we need to worry about security if we have an import map with everything? I know we don't have access rules on js assets by design so it shouldn't be an issue but you never know.
Comment #20
prudloff commentedSymfony has a FAQ page that points to this example: https://ux.symfony.com/
It is a production website with ~100 files in the import map.
Comment #21
christianadamski commentedAnybody still working on this? Maybe during Barcelona?
Comment #22
larowlanDiscussed #18 with Lauri. Import map support is a must have for future CKEditor loading changes and will likely be needed by Experience Builder to support modules adding functionality.
On that basis we felt it would be appropriate to simplify this and move away from defining these in libraries.yml as they don't need any of the features like dependencies, weights etc. We have to load all of the importmaps on the first page load - #18 forces our hand.
I'll work to refactor the existing branch and update the issue summary.
Comment #24
larowlanComment #26
quietone commentedThe change record branch and version info needs to be updated as well as the text which refers to 10.3. Should the change record introduce the new api?
Comment #27
brianperryReally excited about this feature!
Would probably outside of the scope of this issue if there is any follow up, but after reviewing the in-progress implementation it got me thinking about how this would apply to single directory components.
My best guess is that you'd currently have to:
* In the component.yml for the SDC itself, override the library to add the module attribute for the js asset:
* Add an importmap.yml file at the root of the module or theme that defines the single directory component. In that file you'd need to reference the library by the name auto-generated by sdc (core/components.[THEME_OR_MODULE_NAME]--[COMPONENT_NAME_WITH_DASHES])
That would at least make adding js from a component to the import map possible on the current feature branch, which is great. But it feels like there could be some syntactic sugar in the sdc module to make this easier. One of the great things (imho) about SDCs is that a developer can write a js file and not worry about defining a library. Would love to see that concept extend to import maps.
It seems like the `hook_importmap_alter` added here would make it pretty straightforward to implement this in the sdc module once it is determined what could be used to communicate that js should be included in an import map / handled as a module.
Thoughts?
Comment #28
larowlanI'm not sure where SDC fits in with regards to exposing importmap entries - I guess if you had one component that had an ES module that you also needed to import in other places you might want to do that. But really only if another module or theme needed to import it. In all other cases it would be fine to let your bundler do code-splitting and have the consuming code import it from the chunks split during bundling.
The importmap functionality is really only when you have unknown consumers. E.g. React module doesn't know what themes/modules will need to load react. Experience builder doesn't know what contrib modules will want to ship React powered things for field widgets.
But I think it would be worth exploring in a follow-up for sure.
Comment #29
larowlanUpdated the change record and addressed the feedback. I think the only thing remaining from reviews are follow ups as follows:
* How can SDC opt in
* Is there a nicer way for a .libraries.yml file to declare itself a module - than the current attributes approach
Comment #30
mortona2k commentedThere's been some discussion on asset bundling, importmap, and SDC in the Frontend Builder Initiative slack channel.
@brianperry can you elaborate a little more on the use case (In slack if not here)? I hit some things that might be relevant, but I'm not totally sure.
One example is a SDC that imports a js module from npm, like swiper. If a module uses it as a library, and my theme component bundles it, is there some way put the bundled version in the importmap and override the module using the library? I'm not sure if this even makes sense though, or if it just makes more sense to set up front end bundling in the root and override the module to use your library.
There's some overlap in manifest.json files for bundled assets and importmaps. I'm not sure if there's any benefit to using a global importmap vs individually bundled or even globally bundled assets.
Another issue is referencing images or icons in the theme directory. When I bundle in the theme dir, the paths are relative to the theme root and drupal uses the Vite module to translate them to the correct path. Since we don't have libraries defined for images, there's some extra config needed to juggle the paths that I haven't figured out yet. I'm not sure if images in the importmap are possible or would just cause more problems.
Comment #31
brianperry> The importmap functionality is really only when you have unknown consumers. E.g. React module doesn't know what themes/modules will need to load react.
I get that this is the primary use case driving this issue, but I don't know that I agree with this statement broadly. Import maps also simplify the process of using esmodule imports in unbundled code. It would be possible for example to create a small utility in a vanilla js file that is exported as a module, which could then be imported in other js module files where needed. You don't necessarily need import maps to do that, but Drupal's library system / asset handling can make it tricky to negotiate module paths without something like an import map.
In practice most complex projects will use a bundler, but this pure esm approach seems like something that could grow over time.
Symfony's Asset Mapper component uses import maps extensively. In fact, they seem to recommend asset mapper as a default
Elsewhere in the FE ecosystem the Islands Architecture popularized by Astro is built on top of esm imports. Their `client:*` directives result in a custom element that loads the necessary javascript via a dynamic import.
While this issue sparked questions related to SDCs since I have been working with them lately, I'm also following this because I've been experimenting with a Drupal Islands Architecture implementation. Based on experiments thus far, I think a Drupal import map implementation would be needed to make this viable. The current POC builds its own import map. It isn't really packaged up for easy consumption at the moment, but I'll try to follow up here when that changes.
> @brianperry can you elaborate a little more on the use case (In slack if not here)? I hit some things that might be relevant, but I'm not totally sure.
@mortona2k I'll say hi in the slack channel, but I'm mostly thinking about the un-bundled use case above.
Comment #32
larowlanYeah it would allow us to move away from everything being global
But in practice I think there are concerns about the resource waterfall impact on performance
Comment #33
christianadamski commentedJust in case this is in any way helpful:
Geolocation module v4 moved to almost complete ES6 modules. A lot of them. And for lack of importmap support its all handled by dynamic imports. And they spread out over 10 sub-modules.
So, this is already out there and I would be more than happy to move away from keeping track of all the relative paths and to an importmap mechanism provided by core.
Comment #34
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #35
effulgentsia commentedA couple months ago, Multiple import maps was merged into the HTML spec. Chrome will support this in v133. TBD when other browsers will catch up.
Comment #36
wim leersComment #37
larowlanWith experience builder I went with a much simpler approach which is to support #attached of type 'import_maps' and then have an attachments processor that consolidated all them and made a single
<script type="importmap">Might be worth considering here too
See https://git.drupalcode.org/project/experience_builder/-/merge_requests/687
Comment #38
catch@larowlan how would #37 work with big pipe and AJAX?
Comment #39
larowlanGood point. Experience builder is only using it in an IFrame so sidesteps that issue
@effulgentsia pointed out that support for multiple importmao scripts is coming to browsers which would also help, but that's a way off yet
So probably worth ignoring the XB approach for now
Comment #40
effulgentsia commentedI don't think it's that far off. It's in Chrome 133 and Safari HEAD (Tech Preview). https://bugzilla.mozilla.org/show_bug.cgi?id=1916277 is the issue for Firefox, they're just choosing to block it on landing import map integrity verification first.
Comment #41
catchWe won't be able to require multiple import maps until Firefox ESR supports it, but I think if we're only adding the API for contrib to use initially, we could assume multiple import map support and just not use it in core until it's available in ESR.
Comment #42
larowlanWas was marked as fixed today https://github.com/WICG/import-maps/issues/92
Comment #43
larowlanThis blocks #3527914: Use UMD installation method for CKEditor5 which is critical, so marking this as such too
Comment #44
xjmComment #45
prudloff commentedNot sure this was discussed yet: browser cache invalidation can be a challenge.
For files in the importmap, we can add a GET parameter (generated with AssetQueryString) to invalidate browser cache when the file changes:
However if this file then does another import with a relative path:
We have no way to invalidate browser cache when this other file changes.
Comment #46
catchFirefox still doesn't support multiple import maps:
https://caniuse.com/mdn-html_elements_script_type_importmap_multiple_imp...
It looks like it's being tracked in https://bugzilla.mozilla.org/show_bug.cgi?id=1916277
However per #41 I still think we should add support for multiple import maps, and delay usage of it until Firefox ESR has support.
Comment #47
effulgentsia commentedThere's a comment in that Mozilla issue that mentions https://github.com/guybedford/es-module-shims, a polyfill for multiple import maps (and other import map features) for browsers that don't support it natively.
Comment #48
longwaveFirefox still doesn't have support for multiple import maps, but the end of the year is getting closer and we will need this for any CKEditor release in 2026 for #3527914: Use UMD installation method for CKEditor5
Given that most other browsers do support multiple import maps I think we should go ahead and use the polyfill here.
Comment #49
longwaveRebased against 11.x, the performance test changes were unmergeable and will need redoing, otherwise the only big change was the conversion of
system_page_top()to OOP.Comment #51
vijaycs85Comment #52
vijaycs85The
core/tests/Drupal/Tests/Core/Test/TestDiscoveryTest.phpis failing with below error:This is triggered by the 11.x change to enable the PHPUnit errors. mostly it means we need to make sure all the re-rolling MRs with new tests would face this issue.
Comment #53
prudloff commentedSomething else that was not discussed I think: importmaps might break with strict CSPs that don't allow unsafe-inline.
The import needs to be allowed with its hash or a nonce.
Comment #54
webflo commentedI tried to use import-maps today and experienced some incompatibility issues in Firefox. The Import-Map was defined after a script with type="module".
The error message was: "Import maps are not allowed after a module load or preload has started."
I think import-maps should move to HTML head. Maybe build it in hook_page_attachments?
Comment #55
catchComment #56
quietone commentedComment #58
catchJust double-checked firefox again and nothing has changed since #48 https://caniuse.com/mdn-html_elements_script_type_importmap_multiple_imp... so +1 to adding the polyfill.
Comment #59
nod_Something to keep an eye on too: https://lea.verou.me/blog/2026/external-import-maps-today/ and the corresponding lib https://github.com/nudeps/nudeps
Comment #60
godotislateTagging this as a D12 release priority.
Comment #61
longwaveLooks like multiple import maps will be supported in Firefox 150: https://bugzilla.mozilla.org/show_bug.cgi?id=1916277
This is scheduled for release in a month from now, but will initially be marked as experimental. We have to wait for Firefox ESR anyway, where hopefully it will be enabled by default, but no guarantees. The next ESR release is Firefox 153 which will be on 21 July 2026.
Comment #62
effulgentsia commentedLooking at recent comments on that Firefox issue, it looks like even with 150, it might be considered experimental and gated behind an off-by-default preference flag. In which case, we'll still need the polyfill until Firefox releases the feature ungated.
[Edit: sorry, I see you mentioned that in #61 as well. I missed that when I first read that comment.]
Comment #63
effulgentsia commentedhttps://bugzilla.mozilla.org/show_bug.cgi?id=2021012 is the new Firefox ticket for enabling it by default.
Comment #64
catchNow that #3527914: Use UMD installation method for CKEditor5 went in without this, I don't think it's critical, but it would still be good to support, so moving to 'major'.