Getting started

# How import maps work

An import map is a string substitution for bare module specifiers, done by the browser.

## Why import maps

[Import maps](https://github.com/WICG/import-maps) let you import JavaScript modules using logical names that map to versioned, digested files — directly from the browser. You can build modern JavaScript applications with libraries made for ES modules (ESM) without transpiling or bundling, which frees you from Webpack, Yarn, npm and the rest of the JavaScript toolchain. All you need is the asset pipeline already included in Rails.

You ship many small JavaScript files instead of one big one. With HTTP/2 that no longer carries a material performance penalty during the initial transport, and it caches better over time: a change to one file invalidates that file, not a whole bundle.

[Import maps are supported natively in all major, modern browsers](https://caniuse.com/?search=importmap). For legacy browsers without native support there is [es-module-shims](https://github.com/guybedford/es-module-shims).

## Bare module specifiers

A "bare module specifier" looks like `import React from "react"`. The ES module loader doesn't accept it: a specifier has to be an absolute path, a relative path, or an HTTP URL.

```javascript
import React from "/Users/DHH/projects/basecamp/node_modules/react"   // absolute path
import React from "./node_modules/react"                              // relative path
import React from "https://ga.jspm.io/npm:react@17.0.1/index.js"     // HTTP URL
```

The import map is a clean API for mapping a bare specifier like `"react"` to one of those three. A pin in `config/importmap.rb`:

```ruby
pin "react", to: "https://ga.jspm.io/npm:react@17.0.2/index.js"
```

means "every time you see `import React from "react"`, change it to `import React from "https://ga.jspm.io/npm:react@17.0.2/index.js"`".

## Usage

The import map is set up through `Rails.application.importmap` from `config/importmap.rb`. The file is reloaded in development when it changes; restart the server if you remove pins and need them gone from the rendered map or the list of preloads.

The map is inlined in the `<head>` of your layout by `<%= javascript_importmap_tags %>`, as a `<script type="importmap">` with the JSON configuration. The entrypoint is then imported with `<script type="module">import "application"</script>`; the logical name `application` maps to `app/javascript/application.js`.

In `app/javascript/application.js` you set up your application by importing any of the modules the import map defines, with the full ESM feature set: named exports, default exports, `import *`.

Use logical names that match the npm package names, so that if you later move to transpiling or bundling, no module import has to change.

## Local modules

Local modules in `app/javascript/src` or other sub-folders of `app/javascript` (such as `channels`) must be pinned to be importable. `pin_all_from` pins every file in a folder, so you don't `pin` each module:

```ruby
pin_all_from "app/javascript/src", under: "src", to: "src"

# With automatic integrity calculation
enable_integrity!
pin_all_from "app/javascript/controllers", under: "controllers", integrity: true
```

`under:` sets the prefix of the bare specifier — what `import` statements say. `to:` sets the prefix of the asset path the file is served from, and defaults to `under:`, so in this example it is redundant: drop it and `under:` goes directly after the first parameter. `enable_integrity!` turns on integrity calculation globally and `integrity: true` computes a hash for every file in the directory — see [Subresource integrity](https://importmap-plus.zoolutions.llc/docs/integrity).

Which lets you write:

```ruby
import { ExampleFunction } from "src/example_function"
```

> **Note:** Sprockets used to serve assets it couldn't find from app/javascript by relative path, so local files didn't need pinning. Propshaft has no such fallback: with Propshaft you pin your local modules.