Propshaft Pt. 3: Assets ❤️ Importmaps

Jon Sully headshot

Jon Sully

@jon-sully

After covering how Propshaft works in Part 1 and how Propshaft interplays with typical CDN setups in Part 2, we realized that it’s worth clarifying how Propshaft integrates with Importmaps, too!

Importmaps are still a new technology and feel, to me at least, very different than previous Javascript setups. But the idea itself is pretty simple — give the browser the list of all the individual Javascript files and modules that it might need and let it pull in each file individually (as needed). Don’t just bundle all your Javascript into a big, single bundle!

When thinking about it in the Propshaft context, the key piece of understanding we need to remember here is that Importmaps are a front-end (browser) technology. In a modern Rails app, Ruby may stitch together the Importmap file and deliver it to the browser, but it’s ultimately the browser that actually does things. To the back-end, it’s still just serving static Javascript files.

Let’s observe how this plays out here on this very site. Since this is a Rails site and we’re using Importmaps, we’ve got our own perfect example!

If you load the root of Judoscale.com you’ll find the Importmap included in the <head> block returned:

<script type="importmap">{
  "imports": {
    "alpinejs": "/assets/alpinejs-c73cfd8c.js",
    "@docsearch/js": "/assets/@docsearch--js-ee29e1f7.js",
    "@hotwired/turbo": "/assets/@hotwired--turbo-c1ef7fdc.js",
    "application": "/assets/application-c47afbda.js",
    "components/currentTocHighlighter": "/assets/components/currentTocHighlighter-17ffccd0.js",
    "components/dbConnectionCalculator": "/assets/components/dbConnectionCalculator-def9998f.js",
    "components/pricingCalculator": "/assets/components/pricingCalculator-3ba3311a.js",
    "docsearch": "/assets/docsearch-10aac8bf.js"
  }
}</script>

And we can essentially read this as, “if some Javascript calls import alpinejs, fire off a request to Judoscale.com/assets/alpinejs-c73cfd8c.js then import it in real-time”. The importmap is indeed a lookup-table between “it’s called this” and “here’s where you get it” — a map!

Understanding how these names convert to HTTP requests helps clarify Propshaft’s role here. The Importmap strategy simply means that each Javascript file will be served individually, via its own HTTP request/response. But that works great with Propshaft, because it’s still just some piece of static content, fingerprinted, served via an HTTP request!

Visually, it means that our previous model of Propshaft setup and delivery (with bundles), which looks like:

Diagram of Propshaft fingerprinting then serving a single JS bundle file and a CSS bundle file

…is really no different than this new style of individual files, as far as Propshaft is concerned:

Diagram of Propshaft fingerprinting then serving three different individual JS files and a CSS bundle file

Just a few more files than the bundled approach! But the mental model is the exact same — Propshaft doesn’t know whether your *.js files are individual modules or bundles! To the Rails app, these are the same. Just a browser requesting some static, fingerprinted Javascript.

The mental model being the same isn’t actually even the best part! The best part is that, while in the bundled model we need to use the javascript_include_tag so Rails can work with Propshaft to specify the fingerprinted filename the browser should call for, we can skip all of that with Importmaps! The importmap-rails gem handles all of the fingerprint-name-matching for us. We just use the new javascript_importmap_tags helper and need not think about fingerprints ever again 😁 (more about that here).

And one last note to mention here… since the Importmap strategy doesn’t actually change the fact that Javascript is being loaded by HTTP requests, all of our CDN tooling upgrades from Part 2 still work great! Our Javascript will just get cached by our CDN on an individual basis (file-by-file) rather than as one big bundle. This can actually have benefits for cache-busting when the files change (only changed files are re-downloaded by the user rather than the whole bundle), but we’ll save that for another post!

With that said, Propshaft and Importmaps make a great match for a modern Rails application. Propshaft just fingerprints files and Importmaps allows browsers to pull Javascript on a per-file basis — no build required. This is the Rails 8 way!