# Rails Stimulus e Importmaps em 2026: JavaScript Moderno Sem Ferramentas de Build > Guia completo sobre Rails Stimulus e Importmaps em 2026. Aprenda a criar aplicações JavaScript modernas sem webpack ou bundlers complexos usando Hotwire. - Published: 2026-08-29 - Updated: 2026-08-29 - Author: Anthony Fillion-Maillet - Reading time: 8 min --- O desenvolvimento JavaScript tradicionalmente exigiu configurações complexas, bundlers sofisticados e tempos de compilação prolongados. O Rails 8 em 2026 oferece uma abordagem radicalmente diferente com Stimulus e Importmaps, permitindo criar aplicações interativas sem nenhuma ferramenta de build. > Stimulus e Importmaps vêm incluídos por padrão no Rails 8. Essa combinação permite desenvolver interfaces ricas mantendo a simplicidade que caracteriza a filosofia do Ruby on Rails. ## Entendendo Importmaps no Rails 8 Importmaps é uma tecnologia nativa dos navegadores modernos que permite importar módulos JavaScript diretamente através de URLs, sem necessidade de compilação prévia. O Rails integra essa funcionalidade de forma transparente. O arquivo de configuração está localizado na raiz do projeto: ```ruby # 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" ``` Essa configuração indica ao Rails quais módulos JavaScript estão disponíveis e onde encontrá-los. O comando `pin_all_from` é particularmente útil pois registra automaticamente todos os controllers Stimulus presentes na pasta especificada. Para adicionar uma biblioteca externa a partir de um CDN, utiliza-se o seguinte comando: ```bash bin/importmap pin lodash ``` O Rails baixa automaticamente o pacote do jspm.io e o adiciona ao arquivo de configuração. ## Arquitetura do Stimulus O Stimulus é baseado em um conceito simples mas poderoso: os controllers. Cada controller é uma classe JavaScript que se conecta ao HTML através de atributos data. Essa abordagem mantém uma separação clara entre comportamento e apresentação. A seguir está a estrutura de um controller Stimulus padrão: ```javascript // app/javascript/controllers/search_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["input", "results", "loading"] static values = { url: String, debounce: { type: Number, default: 300 } } connect() { console.log("Search controller connected") } search() { clearTimeout(this.timeout) this.timeout = setTimeout(() => { this.performSearch() }, this.debounceValue) } async performSearch() { const query = this.inputTarget.value if (query.length < 2) return this.loadingTarget.classList.remove("hidden") try { const response = await fetch(`${this.urlValue}?q=${encodeURIComponent(query)}`) const html = await response.text() this.resultsTarget.innerHTML = html } finally { this.loadingTarget.classList.add("hidden") } } } ``` O HTML correspondente utiliza atributos data para estabelecer a conexão: ```erb
``` ## Gerenciamento de Eventos com Actions O sistema de actions do Stimulus permite vincular eventos DOM a métodos dos controllers de forma declarativa. A sintaxe segue o formato `evento->controller#método`. ```javascript // app/javascript/controllers/form_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["submit", "error"] static values = { submitting: Boolean } connect() { this.submittingValue = false } async submit(event) { event.preventDefault() if (this.submittingValue) return this.submittingValue = true this.submitTarget.disabled = true this.errorTarget.textContent = "" const formData = new FormData(this.element) try { const response = await fetch(this.element.action, { method: "POST", body: formData, headers: { "Accept": "text/vnd.turbo-stream.html" } }) if (!response.ok) { throw new Error("Erro ao enviar o formulário") } const html = await response.text() Turbo.renderStreamMessage(html) } catch (error) { this.errorTarget.textContent = error.message } finally { this.submittingValue = false this.submitTarget.disabled = false } } submittingValueChanged() { this.submitTarget.textContent = this.submittingValue ? "Enviando..." : "Enviar" } } ``` ```erb <%= form_with url: messages_path, data: { controller: "form" } do |f| %>
<%= f.text_area :content, required: true %> <%= f.submit "Enviar", data: { form_target: "submit", action: "click->form#submit" } %> <% end %> ``` ## Comunicação Entre Controllers Aplicações complexas frequentemente requerem que múltiplos controllers se comuniquem entre si. O Stimulus oferece várias abordagens para gerenciar essas interações. A primeira abordagem utiliza eventos customizados do JavaScript: ```javascript // app/javascript/controllers/cart_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["count", "total"] static values = { items: Array } connect() { this.element.addEventListener("cart:item-added", this.handleItemAdded.bind(this)) } disconnect() { this.element.removeEventListener("cart:item-added", this.handleItemAdded.bind(this)) } handleItemAdded(event) { const { item } = event.detail this.itemsValue = [...this.itemsValue, item] this.updateDisplay() } updateDisplay() { this.countTarget.textContent = this.itemsValue.length const total = this.itemsValue.reduce((sum, item) => sum + item.price, 0) this.totalTarget.textContent = `R$ ${total.toFixed(2)}` } } ``` ```javascript // app/javascript/controllers/product_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static values = { id: Number, name: String, price: Number } addToCart() { const event = new CustomEvent("cart:item-added", { bubbles: true, detail: { item: { id: this.idValue, name: this.nameValue, price: this.priceValue } } }) this.element.dispatchEvent(event) } } ``` ## Integração com Turbo O Stimulus funciona em conjunto com o Turbo, o outro componente principal do Hotwire. Essa sinergia permite criar aplicações altamente reativas enquanto minimiza o JavaScript necessário. ```javascript // app/javascript/controllers/infinite_scroll_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["entries", "pagination"] static values = { loading: Boolean } connect() { this.observer = new IntersectionObserver( entries => this.handleIntersection(entries), { rootMargin: "100px" } ) if (this.hasPaginationTarget) { this.observer.observe(this.paginationTarget) } } disconnect() { this.observer.disconnect() } async handleIntersection(entries) { const entry = entries[0] if (!entry.isIntersecting || this.loadingValue) return const nextPageLink = this.paginationTarget.querySelector("a[rel='next']") if (!nextPageLink) return this.loadingValue = true try { const response = await fetch(nextPageLink.href, { headers: { "Accept": "text/vnd.turbo-stream.html" } }) const html = await response.text() Turbo.renderStreamMessage(html) } finally { this.loadingValue = false } } } ``` O controller Rails correspondente retorna Turbo Stream: ```ruby # app/controllers/articles_controller.rb class ArticlesController < ApplicationController def index @articles = Article.order(created_at: :desc).page(params[:page]) respond_to do |format| format.html format.turbo_stream end end end ``` ```erb <%# app/views/articles/index.turbo_stream.erb %> <%= turbo_stream.append "articles" do %> <%= render @articles %> <% end %> <%= turbo_stream.replace "pagination" do %> <% end %> ``` ## Criação de Controllers Reutilizáveis A modularidade é essencial para manter uma base de código limpa. Os controllers Stimulus podem ser projetados para serem altamente configuráveis através de values. ```javascript // app/javascript/controllers/clipboard_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["source", "button"] static values = { successMessage: { type: String, default: "Copiado!" }, errorMessage: { type: String, default: "Erro" }, resetDelay: { type: Number, default: 2000 } } async copy() { const text = this.sourceTarget.value || this.sourceTarget.textContent try { await navigator.clipboard.writeText(text) this.showFeedback(this.successMessageValue, "success") } catch { this.showFeedback(this.errorMessageValue, "error") } } showFeedback(message, type) { const originalText = this.buttonTarget.textContent this.buttonTarget.textContent = message this.buttonTarget.classList.add(`feedback-${type}`) setTimeout(() => { this.buttonTarget.textContent = originalText this.buttonTarget.classList.remove(`feedback-${type}`) }, this.resetDelayValue) } } ``` ```erb
npm install @hotwired/stimulus
``` ## Testes de Controllers Stimulus Os testes garantem a confiabilidade dos controllers. Existem várias abordagens possíveis, incluindo Jest ou os testes de sistema do Rails. ```javascript // test/javascript/controllers/clipboard_controller.test.js import { Application } from "@hotwired/stimulus" import ClipboardController from "controllers/clipboard_controller" describe("ClipboardController", () => { let application beforeEach(() => { document.body.innerHTML = `
` application = Application.start() application.register("clipboard", ClipboardController) }) afterEach(() => { application.stop() }) test("copies text to clipboard on click", async () => { const writeText = jest.fn().mockResolvedValue(undefined) Object.assign(navigator, { clipboard: { writeText } }) const button = document.querySelector("[data-action]") button.click() await new Promise(resolve => setTimeout(resolve, 0)) expect(writeText).toHaveBeenCalledWith("test content") }) }) ``` ## Otimização de Performance Embora os Importmaps eliminem a necessidade de bundling, algumas otimizações continuam sendo relevantes para aplicações em produção. A configuração do cache HTTP é fundamental: ```ruby # config/environments/production.rb config.public_file_server.headers = { "Cache-Control" => "public, max-age=31536000, immutable" } ``` O preload de módulos críticos melhora o tempo de carregamento inicial: ```erb <%# app/views/layouts/application.html.erb %> <%= javascript_importmap_tags %> <%= preload_link_tag "application", as: "script", type: "module" %> ``` Para módulos externos volumosos, recomenda-se o carregamento lazy: ```ruby # config/importmap.rb pin "chart.js", preload: false ``` ```javascript // app/javascript/controllers/chart_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { async connect() { const { Chart } = await import("chart.js") this.initializeChart(Chart) } initializeChart(Chart) { // Configuração do gráfico } } ``` ## Conclusão Rails Stimulus e Importmaps representam uma evolução significativa na forma de construir interfaces web interativas. Ao eliminar a complexidade das ferramentas de build tradicionais, essa abordagem permite que desenvolvedores se concentrem na criação de funcionalidades em vez da configuração do ambiente de desenvolvimento. A combinação de Stimulus para comportamento JavaScript e Turbo para atualizações dinâmicas oferece uma alternativa poderosa aos frameworks JavaScript pesados. Essa arquitetura respeita os princípios fundamentais do Rails: convenção sobre configuração e melhoria progressiva do HTML. Para equipes que buscam uma solução sustentável e de alta performance sem a complexidade dos bundlers modernos, Stimulus e Importmaps constituem uma opção relevante em 2026. A curva de aprendizado reduzida e a integração nativa com Rails os tornam uma opção particularmente atrativa para projetos de qualquer tamanho. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/ruby-on-rails/rails-stimulus-importmaps-modern-javascript-without-build-tools