A Quick Look at Import Maps

Published

I know Import Maps aren't all that new, however I've recently been messing around with them in a demo app that is natively using ESModules and it's such a nice developer experience!

For those that aren't aware, import maps allow you to configure/tell the browser how it should resolve bare specifiers when it encounters them. Here's an example in practice:

1<!-- Import Map -->
2<script type="importmap">
3{
4 "imports": {
5 "<bare-specifier>": "<where-to-resolve-that-import-from>",
6 "react": "https://esm.sh/react@19.0.0"
7 }
8}
9</script>
1<!-- Import Map -->
2<script type="importmap">
3{
4 "imports": {
5 "<bare-specifier>": "<where-to-resolve-that-import-from>",
6 "react": "https://esm.sh/react@19.0.0"
7 }
8}
9</script>

This import map will "remap" any "react" bare specifier to "https://esm.sh/react@19.0.0", which is using esm.sh to transform any npm package to a compatible ESModule on the fly!

Now we can ship code that looks like this to the browser and it will work like it would in Node.js, Deno, Bun, or your favorite JS runtime!

1import {createElement} from 'react';
2
3let el = createElement('marquee');
1import {createElement} from 'react';
2
3let el = createElement('marquee');

Import Maps are cool and all - but I also wanted to highlight a really cool feature of using them along with package.json#imports within an isomorphic application.

package.json#imports (the "imports" key within a package.json file) is similar to the import map concept but for Node.js projects. It allows code being run within Node to similarly resolve import specifiers to different locations, here's an example mapping #framework/* imports to a specific directory:

1{
2 "imports": {
3 "#framework/*": "./framework/*.mjs"
4 }
5}
1{
2 "imports": {
3 "#framework/*": "./framework/*.mjs"
4 }
5}
1// will be effectively the same as:
2// import {foo} from './framework/foo.mjs'
3import {foo} from '#framework/foo';
1// will be effectively the same as:
2// import {foo} from './framework/foo.mjs'
3import {foo} from '#framework/foo';

We can combine these two features to build an isomorphic application, and importantly we can also resolve imports differently based on the environment (e.g. resolve a package that uses Node.js features on the server, and then a stub or alternative on the client)!

I'll leave a bit of this as an exercise to the reader, but I do have an example codebase here that you can look at to see how it works in practice if you don't want to derive the solution yourself 😅.