# JavaScript without npm? A dive into Rails 7's importmap approach
Using JavaScript in Rails has a very long history. With Rails 7, the latest approach has been to move away from the tools from the JavaScript community like npm, yarn, and Webpack. Instead, Rails 7 introduces [importmap-rails](https://github.com/rails/importmap-rails) as a solution that “embraces the platform” and uses native JavaScript modules (also known as “ES Modules” or “ESM”).
## What’s Importmap-rails?
Importmap-rails is touted as a gem that “let you import JavaScript modules directly from the browser” 1. The documentation claims that “this frees you from needing Webpack, Yarn, npm, or any other part of the JavaScript toolchain.”
At first, I felt like those claims are a bit ambiguous and a bit of marketing hype. After exploring importmap-rails a bit more, I think I have another way of looking at it. importmap-rails is a replacement for npm for app builders. That is: take away npm’s authoring tools (like `npm publish`), and you might get a tool that has importmap-rails’s feature set. Have a look at a few things importmap does:
- `importmap pin` adds a package (like `npm install`.)
- `importmap.rb` is a file that list your packages (like `package.json`.)
- `importmap audit` checks for security bulletins for the packages in importmap.rb (like `npm audit`.)
- `impotrmap outdated` checks for outdated packages (like `npm outdated`).
## Importmap command
Rails 7 apps provide a `bin/importmap` command, which is an analogue of what `npm` or `yarn` would be used for.
```
› bin/importmap
Commands:
importmap audit # Run a security audit
importmap help [COMMAND] # Describe available commands or one specific command
importmap json # Show the full importmap in json
importmap outdated # Check for outdated packages
importmap pin [*PACKAGES] # Pin new packages
importmap unpin [*PACKAGES] # Unpin existing packages
```
## Adding a package
To add a package, use `bin/importmap pin `, much like you might do with `npm install` or `yarn add`.
```
› bin/importmap pin canvas-confetti
Pinning "canvas-confetti" to https://ga.jspm.io/npm:canvas-confetti@1.5.1/dist/confetti.module.mjs
```
This adds a line to the `config/importmap.rb` to take note of that package as a dependency. Rather than put the files in `node_modules` like npm, it only takes note of a [jspm cdn](https://jspm.org/import-map-cdn) URL.
```
# Pin npm packages by running ./bin/importmap
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "canvas-confetti", to: "https://ga.jspm.io/npm:canvas-confetti@1.5.1/dist/confetti.module.mjs"
```
## Importmap markup
In development, Rails will render a `
```
## Aside: Importing JavaScript in browsers
Being able to import JavaScript files from other JavaScript files has always been something that had many different approaches. Even Rails had it’s own—[sprockets](https://github.com/rails/sprockets-rails)—which continues to be supported to this day in Rails 7.
Is wasn’t long until a new standard was made to be the native way for importing JavaScript files. This new standard way is called JavaScript modules. 3 In the wild, it might be more commonly known as “ES modules” or “ESM” after its name prior to standardisation, but it’s JavaScript modules all the same.
Browsers have supported JavaScript modules since 2017. This `import` statement will work in any modern browser today, as long as it’s placed in a `
```
![[2022-08-30 canvas confetti codepen.mov]]
## About importmaps
Being able to import straight from URL’s is great, but it can easily get repetitive. To work around this, an “import map” can be used to alias URL’s into easily-readable names.
The gem importmap-rails got its name because generating these importmap blocks is sole bread-and-butter. It’s the mechanism that allows developers use 3rd-party JavaScripts commonly found in npm in the browser, all without having to modify scripts.
```
```
## Browser support for import maps
In 2022, import maps are only supported by the latest Chrome, Edge, and Opera browsers. Even the Webkit-based Safari doesn’t support it natively yet, and Firefox doesn’t either. It’s still considered an unofficial standard.
![[Pasted image 20220830105604.png]]
Source: https://caniuse.com/import-maps
## ES module shim
Rails will also load a package called [es-module-shims](https://github.com/guybedford/es-module-shims). This seems to be behaviour [built into Rails’s importmap-rails](https://github.com/rails/importmap-rails/search?q=es-module-shims).
```html
```
It seems es-module-shims adds a bit of runtime cost. Even for a modern browser like Firefox 103, the console tells me there’s some 2ms being spent in compiling the asm.js code. This is despite es-module-shims claiming that *“users with import maps entirely bypass the shim code entirely”* 2 — that’s because importmaps aren’t supported in Firefox (yet).
![[Pasted image 20220830103238.png]]
## Importing application.js
Finally, Rails will add an import for `application.js` by ways of a `
```
## Module preload
To speed things along, Rails will also generate `` tags. Typically, the browser will have to download application.js to find its dependencies, leading to a “waterfall” style download where files are downloaded in stages.
Instead, these modulepreload tags will signal browsers to [prefetch](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/modulepreload) scripts that are dependenies of application.js, so they can be downloaded right away.
```html
```
## Is un-bundling faster?
I tried looking for benchmarks on whether this approach of loading many small files (“un-bundling”), is better than one big file (“bundling”). I wasn’t able to find any, but anecdotally, there are reports that JavaScript modules and HTTP2 aren’t faster than bundlers at all. In fact, they may even be slower, possibly due to the way compression works.
- https://twitter.com/devongovett/status/1257342740691550208
On the JavaScript community, there’s been progress on being less reliant on bundling. [Vite](https://vitejs.dev/) is one of these “un-bundlers” that take advantage of JavaScript module support in the browser. However, it still performs bundling in production because it’s a very efficient way to serve files to browsers.
- https://twitter.com/youyuxi/status/1257367902371618818
## Limitations of jspm
With importmap-rails, third-party packages will be loaded from a public CDN, jspm. This might be a concern with private apps. If jspm is down, sites that use it will be affected as well.
## No React, no TypeScript
With importmap-rails, there is no build step that proccesses JavaScript files. That means it’s not possible to use things like TypeScript, React, Vue, Svelte, Solid, Angular, or any of the common JavaScript tools today used to build frontends.
Rather than rely on tools like React, the Rails community suggests to use [Stimulus](https://stimulus.hotwired.dev/) instead. Stimulus also comes default with Rails 7.
## More limitations
No building. No tree-shaking. No CSS modules. No automated upgrade tools like Dependabot (yet). No automated security audits like Snyk (yet).
## My thoughts
Pros:
- Simple, no bundlers to configure
- Faster deployments, no “npm install” or bundling
Cons:
- Hard dependency on a public CDN
- Downtime in public CDN’s will affect your site
- Runtime cost for Firefox and Safari
- No support for TypeScript
- No support for React (and other tools needing a build step)
- No automated upgrade tools like Dependabot
- No automated security audits like Snyk
## Links
1. https://github.com/rails/importmap-rails#importmap-for-rails
2. https://github.com/guybedford/es-module-shims#es-module-shims
3. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
4. https://web.dev/performance-http2/