turbo-hmr
Hot Module Replacement for Turbo with Importmap.
This gem detects when pinned Stimulus controller modules change across a Turbo-driven navigation (e.g., Turbo.visit). If the new page’s importmap updates any Stimulus controllers, the gem attempts to swap in the updated controllers without a full page reload by:
- Disconnecting existing controller instances for the changed identifiers
- Re-importing the updated modules
- Re-registering/reconnecting controllers so instances start using the new code
- Preventing the full page reload
If any changed importmap item can’t be safely hot-swapped, we fall back upon the default Turbo.visit behavior to ensure correctness.
Why
More seamless deployments for long-lived single-page sessions.
Turbo’s morphing normally keeps you on the page without a full reload, but the browser doesn’t automatically re-import modules whose specifiers are unchanged while their URLs in the importmap have changed, and instead just does a full page load. This gem bridges that gap by watching for importmap diffs and applying a targeted hot swap.
How It Works
This gem disables Turbo's built-in data-turbo-track="reload" behavior for importmaps and replaces it with smarter logic:
- During Turbo navigations, it intercepts
turbo:before-fetch-responseto extract the incoming page's importmap - In
turbo:before-render, it compares importmaps and detects what changed - If only Stimulus controllers changed, it hot-swaps them without a full page reload
- Controllers are hot-swapped by calling
Stimulus.unload()andStimulus.register()with the new module - If any non-controller imports changed, it triggers a full reload via
Turbo.visit()
Caveat emptor: This process assumes that all stimulus controllers can be safely unloaded and reloaded without problematic side-effects. Users of this gem need to ensure that their controllers are safe to re-import!
Installation
- Add to your Gemfile:
gem "turbo-hmr"
- Run the installer:
bin/rails generate turbo:hmr:install
This will:
- Pin the
turbo-hmrJavaScript module inconfig/importmap.rb - Set it up in
app/javascript/application.js
Limitations
- Only swaps Stimulus controllers; other module changes will trigger a full page reload.
- Assumes that controllers do not have problematic side-effects during module evaluation. The onus is upon the user of this gem to ensure their controllers are safe to re-import.
- Assumes importmap entries include cache-busting digests when files change (Rails does this by default in development/test).
- Requires disabling
data-turbo-track="reload"on the importmap script tag (this gem monkeypatches importmaps-rails to do this).
Roadmap
- Configurable module matching (allow/deny lists)
- Opt-in HMR for non-controller modules
License
MIT