Rails Stimulus และ Importmaps 2026: JavaScript สมัยใหม่ไม่ต้องใช้ Build Tools

Stimulus และ Importmaps ใน Rails 8.1 ช่วยให้เขียน JavaScript สมัยใหม่โดยไม่ต้องใช้ bundler บทความนี้ครอบคลุม controller, action, target และ package npm

Rails Stimulus และ Importmaps 2026: JavaScript สมัยใหม่ไม่ต้องใช้ Build Tools

Rails Stimulus และ Importmaps ขจัดความจำเป็นในการใช้ webpack, esbuild หรือขั้นตอน build JavaScript ใดๆ Rails 8.1 มาพร้อมกับ importmap-rails และ stimulus-rails เป็นค่าเริ่มต้น ทำให้นักพัฒนาสามารถเพิ่มพฤติกรรมโต้ตอบให้กับ HTML ที่ render จาก server โดยไม่ต้องจัดการ node_modules หรือการตั้งค่า bundler ที่ซับซ้อน

ไม่ต้องมีขั้นตอน Build

Importmaps ใช้ ES modules ดั้งเดิมของเบราว์เซอร์ การรัน bin/importmap pin chart.js จะดึง package จาก CDN และลงทะเบียนใน config/importmap.rb ไม่ต้อง npm install ไม่ต้อง compile ไม่ต้องรอ

วิธีที่ Importmaps แทนที่ Bundler แบบดั้งเดิม

Importmaps ใช้ประโยชน์จากการรองรับ ES module ดั้งเดิมของเบราว์เซอร์เพื่อโหลดไฟล์ JavaScript โดยตรง แทนที่จะรวม dependency ทั้งหมดเป็นไฟล์เดียว เบราว์เซอร์จะดึงแต่ละ module ตามความต้องการ JSPM CDN แปลง package npm เป็น URL ที่เข้ากันได้กับเบราว์เซอร์

ไฟล์ config/importmap.rb ทำหน้าที่เป็น manifest:

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

การตั้งค่านี้บอกเบราว์เซอร์ว่าจะหาแต่ละ module ได้ที่ไหน คำสั่ง pin_all_from จะลงทะเบียน controller Stimulus ทั้งหมดจากไดเรกทอรีที่ระบุโดยอัตโนมัติ

เบราว์เซอร์ที่รองรับ importmaps ได้แก่ Chrome 89+, Edge 89+, Firefox 108+ และ Safari 16.4+ สำหรับเบราว์เซอร์รุ่นเก่า Rails จะใส่ polyfill es-module-shims โดยอัตโนมัติ

การสร้าง Controller Stimulus สำหรับ UI โต้ตอบ

Controller Stimulus เชื่อมต่อพฤติกรรม JavaScript กับ element HTML ผ่าน data attributes แต่ละ controller เป็น class ที่ extend จาก Controller ของ @hotwired/stimulus

app/javascript/controllers/dropdown_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"

// จัดการการแสดง dropdown พร้อมรองรับ keyboard
export default class extends Controller {
  static targets = ["menu", "button"]
  static values = { open: Boolean }

  // สลับ dropdown เมื่อคลิกปุ่ม
  toggle() {
    this.openValue = !this.openValue
  }

  // ปิดเมื่อคลิกนอก dropdown
  clickOutside(event) {
    if (!this.element.contains(event.target)) {
      this.openValue = false
    }
  }

  // ถูกเรียกอัตโนมัติเมื่อ openValue เปลี่ยน
  openValueChanged() {
    this.menuTarget.classList.toggle("hidden", !this.openValue)
    this.buttonTarget.setAttribute("aria-expanded", this.openValue)
  }

  // Connect lifecycle callback
  connect() {
    document.addEventListener("click", this.clickOutside.bind(this))
  }

  // Disconnect lifecycle callback - ทำความสะอาด
  disconnect() {
    document.removeEventListener("click", this.clickOutside.bind(this))
  }
}

HTML ที่เกี่ยวข้องใช้ data attributes เพื่อเชื่อมต่อ controller:

erb
<%# app/views/components/_dropdown.html.erb %>
<div data-controller="dropdown" data-dropdown-open-value="false">
  <button data-dropdown-target="button"
          data-action="click->dropdown#toggle"
          aria-haspopup="true">
    ตัวเลือก
  </button>
  <ul data-dropdown-target="menu" class="hidden">
    <li>แก้ไข</li>
    <li>ลบ</li>
  </ul>
</div>

Stimulus 3.2.2 นำเสนอ Outlets API และ keyboard event modifiers คุณสมบัติเหล่านี้ลด boilerplate เมื่อประสานงานระหว่าง controller หรือกรอง keystroke

การเพิ่ม Package npm ด้วย Importmaps

การเพิ่ม library JavaScript ต้องการเพียงคำสั่งเดียว:

bash
# Pin chart.js จาก JSPM CDN
bin/importmap pin chart.js

คำสั่งนี้เพิ่ม entry ใน config/importmap.rb:

ruby
# config/importmap.rb
pin "chart.js", to: "https://ga.jspm.io/npm:chart.js@4.4.1/dist/chart.js"

Library สามารถ import ใน controller ใดก็ได้:

app/javascript/controllers/chart_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"
import { Chart, registerables } from "chart.js"

Chart.register(...registerables)

export default class extends Controller {
  static targets = ["canvas"]
  static values = {
    type: { type: String, default: "bar" },
    data: Object
  }

  connect() {
    this.chart = new Chart(this.canvasTarget, {
      type: this.typeValue,
      data: this.dataValue,
      options: { responsive: true }
    })
  }

  disconnect() {
    this.chart.destroy()
  }
}

เอกสาร importmap-rails ครอบคลุมการ vendor package ในเครื่องสำหรับการพัฒนาแบบ offline หรือเมื่อการเข้าถึง CDN ถูกจำกัด

การจัดการ Dependency ของ Package

บาง package npm มี dependency tree ที่ลึก ใช้ bin/importmap pin package-name --download เพื่อ vendor dependency ทั้งหมดในเครื่อง วิธีนี้ดีกว่าสำหรับ package ที่มี transitive imports มาก

Action Parameters และ Event Modifiers

Stimulus 3.x นำเสนอ action parameters ทำให้สามารถส่งค่าจาก HTML ไปยัง method ของ controller โดยตรง:

erb
<%# ส่ง parameter ผ่าน data attributes %>
<button data-controller="item"
        data-action="click->item#delete"
        data-item-id-param="42"
        data-item-confirm-param="true">
  ลบ
</button>
app/javascript/controllers/item_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  delete(event) {
    const { id, confirm } = event.params
    
    if (confirm && !window.confirm("ลบรายการนี้?")) {
      return
    }
    
    // ดำเนินการลบโดยใช้ parameter id
    fetch(`/items/${id}`, { method: "DELETE" })
  }
}

Keyboard event modifiers กรองว่าปุ่มใดจะเรียก action:

erb
<%# เรียกเฉพาะเมื่อกดปุ่ม Enter %>
<input type="text"
       data-controller="search"
       data-action="keydown.enter->search#submit">

<%# รวม modifier หลายตัว %>
<input type="text"
       data-controller="shortcuts"
       data-action="keydown.ctrl+s->shortcuts#save:prevent">

Modifier :prevent เรียก event.preventDefault() และ :stop เรียก event.stopPropagation() สิ่งเหล่านี้แทนที่การจัดการ event ด้วยตนเองใน method ของ controller

พร้อมที่จะพิชิตการสัมภาษณ์ Ruby on Rails แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Target Lifecycle Callbacks และ Default Values

Stimulus เรียก method lifecycle เมื่อ target ปรากฏหรือหายไปจาก DOM:

app/javascript/controllers/tabs_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["panel"]
  static values = {
    index: { type: Number, default: 0 }
  }

  // ถูกเรียกเมื่อ target panel ใหม่ถูกเพิ่มใน DOM
  panelTargetConnected(element) {
    element.hidden = this.panelTargets.indexOf(element) !== this.indexValue
  }

  // ถูกเรียกเมื่อ target panel ถูกลบจาก DOM
  panelTargetDisconnected(element) {
    // ทำความสะอาดถ้าจำเป็น
  }

  select(event) {
    this.indexValue = event.params.index
  }

  indexValueChanged() {
    this.panelTargets.forEach((panel, index) => {
      panel.hidden = index !== this.indexValue
    })
  }
}

Default values ขจัดการตรวจสอบ null ตัวเลือก default ในการกำหนด value รับประกันว่า value จะมีอยู่เสมอ แม้ว่า data attribute จะไม่มีใน HTML

การประสานงาน Controller ด้วย Outlets API

Outlets API ซึ่งนำเสนอใน Stimulus 3.2 ทำให้ controller สามารถอ้างอิง controller อื่นบนหน้า:

app/javascript/controllers/form_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static outlets = ["validation"]

  submit(event) {
    // เข้าถึง instance ของ validation controller ทั้งหมด
    const allValid = this.validationOutlets.every(v => v.isValid())
    
    if (!allValid) {
      event.preventDefault()
    }
  }
}
app/javascript/controllers/validation_controller.jsjavascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "error"]
  static values = { pattern: String }

  isValid() {
    const valid = new RegExp(this.patternValue).test(this.inputTarget.value)
    this.errorTarget.hidden = valid
    return valid
  }
}
erb
<%# เชื่อมต่อ outlets ใน HTML %>
<form data-controller="form"
      data-form-validation-outlet=".validation-field">
  <div class="validation-field"
       data-controller="validation"
       data-validation-pattern-value="^\S+@\S+$">
    <input type="email" data-validation-target="input">
    <span data-validation-target="error" hidden>อีเมลไม่ถูกต้อง</span>
  </div>
  <button data-action="click->form#submit">ส่ง</button>
</form>

Outlets ให้ทางเลือกที่สะอาดกว่าการ dispatch custom events เมื่อ controller ต้องสื่อสารโดยตรง

Outlets ต้องการการมีอยู่ของ DOM

การอ้างอิง outlet จะถูกแก้ไขเมื่อ controller เชื่อมต่อ หาก element เป้าหมายถูกเพิ่มแบบไดนามิก การอ้างอิง outlet จะไม่อัปเดตอัตโนมัติ ใช้ callback outletConnected และ outletDisconnected เพื่อจัดการสถานการณ์แบบไดนามิก

คำถามสัมภาษณ์ทางเทคนิคเกี่ยวกับสถาปัตยกรรม Frontend ของ Rails

ผู้สัมภาษณ์มักถามเกี่ยวกับแนวทาง JavaScript ของ Rails เพื่อประเมินความเข้าใจด้านสถาปัตยกรรม:

ทำไมเลือก Importmaps แทน bundler แบบดั้งเดิม?

Importmaps ลดความซับซ้อนสำหรับแอปพลิเคชันที่ JavaScript เป็นส่วนเสริม ไม่ใช่หลัก ข้อแลกเปลี่ยน: tree-shaking และการเพิ่มประสิทธิภาพขั้นสูงไม่พร้อมใช้งาน และ package ที่มีการแจกจ่ายแบบ CommonJS เท่านั้นต้องการการแปลงด้วยตนเองหรือ vendor สำหรับแอปพลิเคชันที่ใช้ JavaScript มาก jsbundling-rails กับ esbuild ยังคงเป็นตัวเลือกที่ใช้ได้

Stimulus แตกต่างจาก React หรือ Vue อย่างไร?

Stimulus เพิ่มประสิทธิภาพ HTML ที่มีอยู่แทนที่จะสร้างมันขึ้นมา Markup ที่ render จาก server ยังคงเป็นแหล่งความจริง โดย Stimulus เพิ่มพฤติกรรมผ่าน data attributes โมเดลนี้ทำงานได้ดีกับ Turbo และวงจร request-response แบบดั้งเดิมของ Rails เส้นโค้งการเรียนรู้ต่ำกว่า แต่การจัดการ state ฝั่งไคลเอนต์ที่ซับซ้อนยากกว่าที่จะนำไปใช้

เกิดอะไรขึ้นเมื่อ controller Stimulus เชื่อมต่อ?

Callback connect() ทำงานหลังจาก Stimulus ระบุ attribute data-controller บน element Target และ value ได้รับการแก้ไขแล้วในจุดนี้ Controller ยังคงทำงานจนกว่า element จะถูกลบออกจาก DOM ซึ่ง disconnect() จะถูกเรียก

การเข้าใจแนวคิดเหล่านี้แสดงถึงความคุ้นเคยกับ ปรัชญาของ Hotwire และกลยุทธ์ frontend ของ Rails

การ Debug Controller Stimulus ใน Development

Stimulus มีโหมด debug ที่บันทึก event lifecycle ของ controller ไปยัง console:

app/javascript/application.jsjavascript
import { Application } from "@hotwired/stimulus"

const application = Application.start()

// เปิดโหมด debug ใน development
if (process.env.NODE_ENV === "development") {
  application.debug = true
}

export { application }

เมื่อเปิดโหมด debug console จะแสดงเมื่อ controller เชื่อมต่อ ยกเลิกการเชื่อมต่อ และเมื่อ value เปลี่ยน Output นี้ช่วยติดตามปัญหากับ data attributes หรือ selector ของ target

สำหรับการ debug ที่ซับซ้อนกว่า extension เบราว์เซอร์ Stimulus Devtools แสดง controller ที่ทำงานอยู่ทั้งหมดและ state ปัจจุบันของพวกมัน

การผสานรวม Stimulus กับ Rails Modern Asset Pipeline

Rails 8.1 ใช้ Propshaft เป็น asset pipeline เริ่มต้น แทนที่ Sprockets Propshaft จัดการ asset แบบ static เช่น รูปภาพและ CSS ในขณะที่ Importmaps จัดการ JavaScript อย่างอิสระ เรียนรู้เพิ่มเติมเกี่ยวกับ แนวทาง asset pipeline ของ Rails ใน module เฉพาะ

การแยกนี้ชัดเจน: Propshaft compile และ fingerprint ไฟล์ CSS, Importmaps แก้ไข module JavaScript ตอน runtime ไม่มีขั้นตอน compilation สำหรับ JavaScript ลดเวลา deployment และขจัด node_modules จาก production image

ruby
# Gemfile - การตั้งค่าเริ่มต้นของ Rails 8.1
gem "propshaft"
gem "importmap-rails"
gem "stimulus-rails"
gem "turbo-rails"

Stack นี้แสดงถึงคำตอบที่มีความเห็นของ Rails สำหรับการพัฒนา frontend: HTML ที่ render จาก server ถูกเพิ่มประสิทธิภาพด้วย JavaScript ที่มีเป้าหมาย ส่งมอบโดยไม่มีความซับซ้อนตอน build-time

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ประเด็นสำคัญสำหรับการพัฒนา JavaScript ของ Rails

  • Importmaps โหลด package npm ผ่าน CDN โดยไม่ต้อง bundling ใช้ bin/importmap pin package-name เพื่อเพิ่ม dependency เวอร์ชัน 2.2.2 ของ importmap-rails เป็นรุ่นปัจจุบัน
  • Controller Stimulus เชื่อมต่อ JavaScript กับ HTML ผ่าน attribute data-controller, data-action และ data-target Framework จัดการ lifecycle โดยอัตโนมัติ
  • Action parameters ส่งข้อมูลจาก HTML ไปยัง method ของ controller ผ่าน attribute data-*-param ลดความจำเป็นในการ query DOM
  • Outlets API ประสานงาน controller หลายตัวบนหน้าโดยไม่ต้องใช้ custom events หรือ global state
  • โหมด debug (application.debug = true) บันทึกกิจกรรม controller ทั้งหมดไปยัง console ระหว่างการพัฒนา
  • Propshaft จัดการ CSS และ asset แบบ static ในขณะที่ Importmaps จัดการ JavaScript ทั้งสองระบบทำงานอย่างอิสระ
  • สำหรับแอปพลิเคชันที่ใช้ JavaScript มาก jsbundling-rails ให้ทางหนีไปสู่ esbuild หรือ webpack ในขณะที่ยังคงรักษา convention ของ Rails
ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Ruby on Rails เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 29 สิงหาคม 2569

แชร์

บทความที่เกี่ยวข้อง