O que é um importmap: sem bundler necessário

Prévia grátis 4 min de leitura

A maioria dos frameworks JavaScript requer uma etapa de build: um bundler como Webpack, esbuild ou Vite que transforma, concatena e minifica seu JavaScript antes do navegador poder usá-lo. Rails 8 adota uma abordagem diferente: importmaps. Sem Node.js. Sem node_modules. Sem etapa de build para JavaScript.

O problema que importmaps resolvem

Antes dos importmaps, carregar JavaScript no navegador significava uma de duas coisas:

  1. Tags script: <script src="lodash.js"> seguido de <script src="app.js">. Cada biblioteca precisava de sua própria tag, a ordem de carregamento importava e tudo vivia no escopo global. Conflitos eram comuns.

  2. Bundlers: ferramentas como Webpack que leem declarações import no seu código, resolvem dependências, concatenam tudo em um ou poucos arquivos e geram bundles de saída com fingerprint. Isso funciona, mas acrescenta complexidade: arquivos de configuração, pipelines de build, Node.js como dependência e tempos de build longos em projetos grandes.

Importmaps dão a você a sintaxe import da opção 2 sem a etapa de build da opção 2. O navegador faz a resolução de módulos nativamente.

Como importmaps funcionam

Um importmap é um objeto JSON que diz ao navegador: "quando você ver import X from 'stimulus', busque desta URL." É um recurso nativo do navegador, e os navegadores modernos não precisam de polyfill para ele.

<script type="importmap">
{
  "imports": {
    "@hotwired/stimulus": "/assets/stimulus.min-4b1e420e.js",
    "application": "/assets/application-edf3ab74.js"
  }
}
</script>

<script type="module">
  import { Application } from "@hotwired/stimulus"
  // Navegador sabe buscar /assets/stimulus.min-4b1e420e.js
</script>

O bloco <script type="importmap"> mapeia nomes de módulos para URLs. Quando um <script type="module"> executa uma declaração import, o navegador procura o nome no importmap e busca o arquivo correto. Sem bundler necessário: o navegador é o resolvedor de módulos.

Como esta aplicação usa importmaps

O layout desta aplicação inclui uma chamada de helper que gera todas as tags JavaScript:

<%# app/views/layouts/application.html.erb %>
<%= javascript_importmap_tags %>

Esse único helper gera três coisas:

  1. Um bloco <script type="importmap"> com o mapeamento completo de módulos para URLs.
  2. Uma tag <link rel="modulepreload"> para cada pin que não esteja marcado com preload: false (para desempenho).
  3. Uma tag <script type="module"> que importa o ponto de entrada da aplicação.

O mapeamento vem de config/importmap.rb, que começa assim:

# config/importmap.rb
pin "application"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"
pin "@hotwired/turbo-rails", to: "turbo.min.js"

Cada declaração pin mapeia um nome de módulo para um arquivo JavaScript. Quando você escreve import "application" no navegador, ele resolve para /assets/application-edf3ab74.js: a versão com fingerprint de app/javascript/application.js.

O ponto de entrada: application.js

// app/javascript/application.js
import "controllers"
import "@hotwired/turbo-rails"

import "lexxy"
import "@rails/actiontext"

Este é o ponto de partida. Quando o navegador carrega a página, ele:

  1. Lê o importmap para saber onde cada módulo está.
  2. Executa application.js como um módulo.
  3. Segue cada import: carregando controllers, @hotwired/turbo-rails, lexxy e @rails/actiontext.
  4. Cada um deles pode importar mais módulos, que o navegador resolve pelo mesmo importmap.
flowchart TD
  A["javascript_importmap_tags"] --> B["script type=importmap (mapeamento JSON)"]
  A --> C["link rel=modulepreload (performance)"]
  A --> D["script type=module (ponto de entrada)"]
  D --> E["application.js"]
  E --> F["controllers/index.js"]
  E --> G["@hotwired/turbo-rails"]
  E --> H["lexxy"]
  E --> I["@rails/actiontext"]
  F --> J["Todos os controllers Stimulus"]

Sem node_modules, sem package.json

Esta aplicação não tem diretório node_modules, nem package.json, nem package-lock.json. Dependências JavaScript são gerenciadas através de config/importmap.rb e uma de duas opções:

  • Arquivos vendorizados em vendor/javascript/: baixados uma vez, commitados no repositório.
  • URLs de CDN: buscados diretamente de um CDN em tempo de execução.
# Vendorizado: o arquivo está em vendor/javascript/
pin "codemirror", preload: false  # => vendor/javascript/codemirror.js

# CDN: buscado da rede em tempo de execução
pin "mermaid", to: "https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.esm.min.mjs", preload: false

Ambas as abordagens evitam Node.js completamente. O trade-off: você não tem tree-shaking (remoção de código não utilizado das bibliotecas). Para a maioria das aplicações Rails, multiplexação HTTP/2 e cache do navegador tornam esse trade-off aceitável.

O que este curso cobre

Nas próximas seis aulas, você vai entender cada camada do asset pipeline desta aplicação:

  • A API pin (aula 2): como pin, pin_all_from e to: mapeiam módulos para arquivos.
  • Propshaft (aula 3): o servidor de assets que aplica fingerprint e serve arquivos, o substituto moderno do Sprockets.
  • Carregamento de controllers Stimulus (aula 4): como eagerLoadControllersFrom auto-registra controllers.
  • Adicionando uma dependência (aula 5): o workflow bin/importmap pin para novos pacotes JavaScript.
  • Preocupações de produção (aula 6): preloading, caching, fallback de CDN e Content Security Policy.
  • Juntando tudo (aula 7): o pipeline completo de config/importmap.rb à execução no navegador.
Aula 1 de 7 · grátis

Continue com a aula 2

A API pin: mapeando módulos para arquivos

Trata de pin, pin_all_from, pins de CDN e vendorizados, o JSON resolvido e a ordem de busca do Propshaft.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...