Introduction to Import Maps and HTTP Imports

Omar Mashaal begins his talk by introducing the topic of import maps and HTTP imports in JavaScript, emphasizing their significance in simplifying module resolution and file type management.

Omar's Background and Company Overview

Omar introduces himself and his company, Exhibitionist Digital, highlighting their work with arts museums and festivals.

Creativity in Modern Web Building

He talks about coming from a creative-driven space and the fascination with building websites in modern ways.

Challenges with Using ESM Today

Omar discusses the pitfalls of using ECMAScript modules (ESM) today, including the dual package hazard and complexities in module resolution.

Exploration of Deno Runtime

He shares his experience with Deno during the lockdown period, noting its features like all ESM, import maps, no NPM, TypeScript support, and built-in tools.

Understanding Import Maps

Omar explains what import maps are, describing them as a JSON file that maps bare specifiers to JavaScript files.

Advantages of Import Maps in Browsers

He highlights the benefits of import maps in browsers, including the reduction of tooling and build chains and support for server-side rendering, client-side rendering, and dynamic imports.

The Simplicity of Native JavaScript

Omar emphasizes the simplicity and maintainability of writing and deploying native JavaScript without heavy dependencies.

Example of Import Maps in Action

Provides an example of how import maps can be used practically, using the Micromark library as an illustration.

Dynamic Imports and Code Splitting

Discussion on dynamic imports and code splitting in JavaScript frameworks like React, and how these can be achieved without bundling.

Browser and Runtime Support for Import Maps

Omar talks about the support for import maps in major browsers and compares JavaScript runtimes like Deno and Node.js in their approach to import maps.

Tooling and Management of Import Maps

He addresses the challenges in tooling around import maps and managing them, particularly the reliance on CDNs.

Optimal Module Design in JavaScript Libraries

Omar recommends an article on designing JavaScript libraries with optimal module design and discusses the movement of libraries towards ESM.

Using CDNs for ESM Today

He explores the role of CDNs like ESM.SH in using NPM ecosystem packages as ESM and their importance in the transition to ESM.

Role of JSPM in ESM Adoption

Omar discusses the role of JSPM in generating import maps for NPM dependencies and its adoption by frameworks like Ruby on Rails.

Framework Support for Import Maps

He highlights frameworks supporting import maps, including Ruby on Rails, and the benefits of using import maps in web development.

ESM without Bundlers in Modern React Applications

Omar discusses the use of ESM without bundlers in modern React applications, highlighting server-side rendering and lazy imports.

Conclusion and Contact Information

Conclusion of the talk with Omar providing his contact details and inviting attendees to explore Import Maps

Thanks, everyone.

so I'm giving a talk on import maps and HTTP imports and how we can actually use them today.

the last talk quickly discussed how we could use import maps in the future of JavaScript.

And, import maps are one of the most exciting things happening in JavaScript, if you ask me, because all of that complexity about module resolution and how to manage all these different file types is, it's almost achieved a complexity that's, far beyond what you could comprehend to go to the first talk.

But what if there was a way that you could just import a JavaScript file and use it with a file extension?

It's insane to me that it's weird.

But, first off, my name's Omar.

I run a company called Exhibitionist Digital.

So we work with a lot of, arts museums and festivals around.

you might have seen some of our work.

Rising is on right now and, we do a lot of work with them and also with, ticketing around a lot of these festivals.

yeah, we come from a really creative driven space and, building websites in the most modern way has been something that's, I've been fascinated with for the past few years.

and just quickly to recap some of the pitfalls with trying to use ESM today, there's things like the dual package hazard, which you've seen how you have to export a huge array of different outputs to tackle CJS, ESM, old versions of Node, new versions of Node, different versions of TypeScript, and each one of them has their own sort of module resolution, which gets utterly confusing because, Everyone makes different decisions about what should be imported.

Libraries can ship both CJS and ESM, and sometimes your codebase will actually import both versions, which can be a bit confusing.

Also, and just at the end of the day, they're promoting non standards where all this module resolution that's happening in the node world isn't really how JavaScript is going forward and how browsers import things.

it's not hard to see why we're in a state that we're in.

a little bit of a prerequisite is, I got obsessed with Deno, over the lockdown period because I think they just went to 1.

0, and, I got, I just loved everything that it said on the label, like all ESM, import maps, no NPM, which they've backed on now, and, yeah, it is a great runtime, it comes with TypeScript and ESM, its own linter, its own format, formatter, and, everything is like tools included.

And it's made by the same person who was the original creator of Node, You've probably seen these slides.

I'm not sure if this talk is another regret to add to his list, but everyone references it, and it just covers some of the things that we're going to be talking about moving forward.

one of his regrets was package dot json, which could read some of his notes there, but it forced Node down this module resolution rabbit hole that it's in.

And again, force requiring things without an extension, because now it's not just js, there's a million different extensions, and some are standard, some are not, and it's just, you can't always make the right choice if you're trying to do it magically.

And also index.

js, which is they call them barrel files, where you basically import all the functions in your library, and then export them out of one file, which can cause a lot of unnecessary complexity, which we'll talk about in a bit.

But starting from the get go, what is an import map?

basically it's a JSON, file that allows you to map bare specifiers to, to JavaScript files, whether they're a fully qualified URL or local JavaScript files in your project.

And why is this cool?

most people I would imagine are actually writing ESM now in their projects.

You're using ESM imports, exports, and all of the kind of bare specifiers that Node is built on.

The complexity comes in everything that the Node server runtime is doing and all of the bundlers and typescripts and so on are going, but what's, what you might not know is that you can have that same functionality in your browser with bare specifiers, with, ESM JavaScript, and it all just works in all the major browsers, as of this year.

Which allows you to heavily reduce the tooling and build chains that we're so accustomed to.

You actually don't need a whole lot to do a lot of the things that we like to see, which is like server side rendering, client side rendering, dynamic imports, code splitting.

There's ways to do it without Bundlers.

yeah, no bundling is required.

Don't let big JS gaslight any thinking that bundlers are necessary because everything comes with a cost and these bundlers are adding so much complexity that in a handful of years, I can't imagine trying to have navigate what we're currently in, I've seen legacy projects that we've worked on and it's just you see the, like lines in stone, you can see all these different, popular things that everyone thought was a good idea, but now it's moved on and we're trying to crawl our way back.

yeah, and the best part about all of this is that it's literally just JavaScript at the day, it's native JavaScript, you're writing it correctly, you're reading it correctly, you can deploy it.

It's quite easy to maintain because you don't have a whole lot of dependencies.

And here's a quick example of how you could literally copy this and paste it into an HTML file and it will just work.

So Micromark is a library, which we'll talk about in the future, is converts Markdown files and it works in server and in the browser.

And you can see just how easy that is and it doesn't require any sort of complex tooling and build process to make that work.

And, you can also, because import maps take a JavaScript file and map it to a bare specifier that you've, defined, they can have a chain of dependencies, and I don't personally use scopes too much at the moment, but if you had some sort of, nested dependency that you wanted to swap out, you could actually use the scopes field and target it that way, which is actually very confusing and I don't, I think it's going to hamper people's adoption of this, but, some of the things you can do without bundlers that a lot of bundlers say is a, selling point is code splitting.

I don't know if you use frameworks and you come, and see these outputs that are generic, automatically generated and it might be common versus like video player it's basically splitting your code base into small chunks to try and dynamically load it or only load it when it's necessary and a performance optimization.

But, there's a lot of tools right now, like React, which support things called lazy imports or dynamic imports.

And I put this example because I'm mostly a front end developer, and this is a way that you kind of code split your application dynamically without bundling.

And basically, when suspense hits this kind of other component fallback, it will dynamically import that JavaScript file, when it's needed.

if you have a router and you're doing dynamic imports only certain code will be loaded, with different routes and that works in Deno and in the browser.

And it's it's, really amazing to watch when you get it all running.

Cause all this complexity and dynamic loading is happening without any sort of, pre compiling.

And as we talked on before, it's, supported now in every major browser.

Which is very cool.

And in runtimes, this is another juicy topic is which JavaScript runtime are we using these days?

And, the thing that I like about Deno is that all of this stuff just comes out of the box fully supported.

Node is still catching up.

I don't think there's a way to use import maps that I know of.

They've just recently got, fetch in the, standard library, which took a while.

And, Deno comes with most of that stuff.

And I don't think Bun has any plan on supporting import maps.

as with everything, there's pros and cons, and, it's definitely early days, but it's really exciting, the, complexity that you can achieve with this method.

so much of the NPM ecosystem has not been migrated to ESM yet, but I think it will eventually get there, it's just taking a very, long time.

tooling around import maps and managing import maps is very clunky, to say the least.

And, relying on CDNs, which we'll talk about next, can be a bit of a liability.

This one's a bit edgy, but, I do think, I'm, in the camp that I think it's better to rip the band aid off sooner than later, because we're seeing more and more libraries actually move to ESM.

I think Node Fetch was one of the last recent holdouts, and they moved last year, and every, it was a pretty controversial.

But, the fact that Node didn't have Fetch, and we're relying on a third party library, that's, it's just odd that's not something that came with Node years and years ago.

It's just, they made a controversial choice to actually just drop CJS, I believe.

And you're seeing a lot of other libraries do the same thing.

this is an article that I recommend everyone read.

It's by my friend Jaden, who some of you might know.

But, he's really obsessed with how we could design JavaScript libraries with optimal module design.

And basically, the gist of it is modules should only have one single purpose default export, which means you could deep import any sort of required function or code and you're not relying on, magic module resolution and, tree shaking and all that stuff.

And Sindre pops up here again as well, this was his, blog post talking about why he's moving all of his packages to ESM.

And, yeah, very, controversial decision, but at the end of the day, the points that he makes are moving forward, moving the JavaScript ecosystem forward and not trying to keep backtracking to make sure all these magic things work that we're used to.

another great developer is Worm, I don't know if but this is like a screenshot of some of his libraries and he's been doing this sort of notation where he explains how you could use it with NPM, with Deno, with browsers, and I think it's really great to see this sort of, documentation with libraries and it at the end of the day, we're going to be using whatever framework or library or runtime we want, it's just good to have options and documentation about how to use libraries like this, and at the top there you can see this package is ESM only.

so yeah, another kind of short, stopgap measure to actually use ESM today is there are many CDNs that allow you to use many things in the ES, NPM ecosystem as ESM.

And one of the most popular ones is ESM.SH.

And this lets you use any package in browser and in Deno.

And it's like a surgical tool because If there's some sort of transient dependency in a library, or you need to do something to modify the non ESM dependency, this handles so many of those edge cases, and I don't think it would be tenable to actually use ESM without some tools like this.

But having said that, I don't think that this is the end game for ESM.

If libraries are actually written as ESM and promoted that way, then we wouldn't necessarily need something like this, so it is a stopgap and it is a build step in a way.

another great CDN is JSPM, and they have a really handy online, tool, which actually will allow you to put all of your, dependencies in from NPM, and it will generate, proper import maps that you can use in browser or in Deno, and, this, the, person who makes this is an interesting case because he's actually been, sponsored by Rails to do all of their import maps, they've moved to import maps as of their last major version.

And also we're starting to see some sort of build tooling around import maps.

And, a common complaint about import maps and CDNs in general is that you don't want to deploy an app that's pointing to some CDN that's modifying ESM because you don't want critical functionality on some third party CDN.

tools like this will actually bring those, third party dependencies and make them locally so that you're actually serving just raw ESM from whatever web server that you're using.

And it just removes all dependencies and points of failure in using tools like that.

And we're starting to see some sort of frameworks that support import maps, and I might get, it's controversial in this conference, but, Ruby on Rails is probably one of the most exciting frameworks and biggest well supported that have actually moved all to import maps.

And there's a couple of Deno, frameworks that we're using as well.

but I don't know if anyone's familiar with this, but, as of Ruby on Rails version 7, they fully support import maps in dev and production.

Has anyone heard of that at all?

No, probably not.

this is also, DHH, he does a lot of posts around sort of move and, big, they're, definitely not of the, current trend, I would say, but he's been really supportive of a bundle of this JavaScript reality.

And, Guy Bedford is the one who made ES, JSPM.

io.

And, they've moved all of their, software that they build, as well as the Rails, platform itself to, be running off of import maps, which is pretty cool.

Cause I wouldn't have expected cutting edge JavaScript coming from the Rails community, but more than the Node, but, and anyway, this is a framework that we've been building at work, and we use it for some of our smaller projects, but it basically is a way to use all ESM without a bundler and achieve, all the niceties that you would expect in a really modern React application, which server side rendering, client side rendering, suspense, lazy imports, all the streaming stuff, the head management streaming, all of this stuff works without a necessary, not the node bundler type of way.

You just ship raw ESM and it works dynamically with Deno and your browser.

And that's pretty much it.

this is my slide.

If you want to check out some of our work, you could, check out our website there or find me on GitHub or Twitter.

Thanks.

Import Maps + HTTP Imports

Native ESM in the browser and beyond... TODAY!

It's not so scary

The image depicts a stylized treasure map surrounded by the sea, with an old-fashioned ship at the left border and various symbols and landmarks such as "SEA MONSTER," "SKULL ROCK ISLAND," "LAVA ROCK," "MERMAID ROCK," and a compass rose indicating the direction northeast. There is also an "X" marking a spot on the map, likely representing a treasure location.

Omar Mashaal

Exhibitionist Digital

POWERHOUSE

Screenshot of Powerhouse Museum web page

Catching up on common ESM pitfalls

Dual package hazard

Shipping both CJS and ESM can result in your codebase/bundler including multiple copies of the same code.

Inconsistent package standards

Libraries can ship any combination of CJS, ESM, or both.

JS runtimes, bundlers, and frameworks commonly promote non-standards

Choose your libraries and tooling very carefully.

The image features a graphic representation including a video game scene from what appears to be Activision's Pitfall!, illustrating obstacles or pitfalls that one might encounter.

Deno

JS runtime built on web standards. All ESM, and integrates many web API's such as fetch, readable streams, and more.

Home page for Deno

Node regrets

(he's had a few)

Regret: package.json

  • Isaac, in NPM, invented package.json (for the most part).
  • But I sanctioned it by allowing Node's require() to inspect package.json files for "main"
  • Ultimately I included NPM in the Node distribution, which made it the defacto standard.
  • It's unfortunate that there is a centralized (privately controlled even) repository for modules.

Screenshot from a talk by Isaac Schluter, creator of Node and Deno

Regret: require("module") without the extension ".js"

  • Needlessly less explicit.
  • Not how browser javascript works. You cannot omit the ".js" in a script tag src attribute.
  • The module loader has to query the file system at multiple locations trying to guess what the user intended.

Screenshot from a talk by Isaac Schluter

Regret: index.js

I thought it was cute, because there was index.html ...

It needlessly complicated the module loading system.

It became especially unnecessary after require supported package.json.

Screenshot from a talk by Isaac Schluter

What is an Import Map?

Import Maps allow usage of bare specifiers across your ESM codebase.

{
  "imports": {
    "react": "https://esm.sh/react",
    "app": "/src/app.js"
  }
}

Bare specifiers resolve to relative paths or fully-qualified urls.

Why is this cool?

  • This allows you to both write and ship applications in modern js.
  • Your tooling/build chain can be heavily reduced.
  • No bundling is required! (Don't let Big JS™️ gaslight you into thinking bundlers are necessary, everything comes at a cost!)
  • Source code and dependencies can be dynamically loaded as needed!
  • Leaning into native features is less susceptible to anti-patterns.
  • It's just JS at the end of the day.

A Simple Example

<script type="importmap">
{
  "imports": {
    "micromark": "https://esm.sh/micromark"
  }
}
</script>

<script type="module">
  import {micromark} from 'micromark'

  console.log(micromark('# Hello, *world*!'))
</script>

Scopes

Scopes allow different versions of a module to be used depending on what code is doing the importing.

<script type="importmap">
{
  "imports": {
    "square": "./module/shapes/square.js"
  },
  "scopes": {
    "/modules/customshapes/": {
      "square": "https://example.com/modules/shapes/square.js"
    }
  }
}
</script>
  

Legacy Code Splitting

Many frameworks and bundlers have a feature known as 'code-splitting'

//<script src="123-common.bundle.js"></script>
//<script src="456-video-player.bundle.js"></script>
  

This is done as an optimisation to try and limit bundled javascript between routes, templates, or sections of your application.

Modern Code Splitting

Using ESM and import maps allow source code and modules to be loaded dynamically, as needed.

Specifiers in your importmap are not downloaded/parsed until imported in your source code.

This can be combined with concepts such as "Suspense" and "Lazy Loading" offered by libraries like React.


import { lazy, Suspense } from 'react';
<
const OtherComponent = lazy(() => import('./OtherComponent.jsx'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}
  

Browser Support

Import Maps are supported in all major, evergreen, browsers

Runtime Support

  • Deno: Fully Supported
  • Node:

    Open discussion on native support. 3rd-party loader available

  • Bun: Not planned

Cons

  • Much of the NPM ecosystem has not been migrated to ESM (yet)
  • Tooling for managing import maps is still in early days. This can feel clunky and very manual.
  • Relying on ESM CDNs can be a liability

How we can improve our ecosystem

The image depicts a trash can overflowing with garbage, including bags and a discarded bottle. There are two signs associated with the trash can; one says "NO LITTERING" and the other says "DUMP IT!"

How we can improve our ecosystem

Screenshot of an article called "Optimal JavaScript module design" by Jayden Seric. Text appears as follows from the article.

Jayden Seric   Blog

Optimal JavaScript module design

Perhaps the most acute yet least recognized problem in the JavaScript ecosystem is the widespread lack of understanding of optimal module design. Famous developers with decades of experience are propagating anti-patterns at the same rate as fresh beginners. Entire classes of problems can be avoided with less tooling and effort by simply following one golden rule...

The golden rule for optimal modules

JavaScript modules should only have single

Legends of ESM

sindresorhus

Screenshot of github page for Pure ESM Package by sindreorhus.

Legends of ESM

woorm

Screenshot of page for ESM Only by wooorm.

NPM + ESM CDNs

The current state of the NPM ecosystem is still split across CommonJS and ESM, and can be a very frustrating experience, to say the least.

New tooling is available that bridges the gap, and allows usage of much of the NPM ecosystem, free to import via Import Maps via CDNs

NPM + ESM CDNs

ESM.SH

A surgical tool for using ESM today. Supports TypeScript headers, converts legacy CJS packages to ESM, aliasing dependencies, and much more.

Screenshot of page for ESM.SH

NPM + ESM CDNs

JSPM.IO

Features a great web interface for exploring how to use import maps in browser, deno and more.

Screenshot of page for JSPM.IO.

Modern Frameworks which
support Import Maps today!

  • Ruby on Rails
  • Ultra (React/Deno)
  • Aleph (React/Deno)

Ruby on Rails (V7) natively supports importmaps!

Screenshot of header for an article

Import Maps Under the Hood in Rails 7

Paweł Dąbrowski on Mar 2, 2022

Screenshot for start of an article by David Heinemeier Hansson, titled "HEY is running its JavaScript off import maps"

Ultra: Deno + React

{
  "imports": {
    "react": "https://esm.sh/[email protected]",
    "react-dom": "https://esm.sh/[email protected]",
    "ultra/": "https://deno.land/x/[email protected]/"
  }
}
  

End