DEV Community

julianrubisch
julianrubisch

Posted on • Originally published at blog.minthesize.com on

StimulusReflex with Importmaps on Rails 7

This short post is intended as a how-to get up and running with StimulusReflex and the new importmap-rails gem to go all transpiler/bundler-less in your asset handling. Since not all generators in StimulusReflex have been updated to reflect this, I will keep this post updated ✌.

For reference, here is the link to the Github repository with the source code created in this walkthrough.

If you have any questions meanwhile, don’t hesitate to jump on the Discord server 💚.

Prepare Rails

Since Rails 7.0, importmaps, along with Hotwire aka Turbo and Stimulus, is the default frontend stack. Before starting out, we’ll make sure that we’re running the correct Rails version.

$ rails -v
Rails 7.0.0

$ rails new stimulus_reflex_importmaps
$ cd stimulus_reflex_importmaps
Enter fullscreen mode Exit fullscreen mode

This will install and configure turbo-rails, stimulus-rails, and importmap-rails, which are the default starting with Rails 7.

Note: If you start from an existing app (e.g. Rails 6.1), you will need to add and install those yourself:

$ bundle add importmap-rails turbo-rails stimulus-rails
$ bin/rails importmap:install
$ bin/rails turbo:install
$ bin/rails stimulus:install
Enter fullscreen mode Exit fullscreen mode

Set up StimulusReflex

Next, we’re going to add the StimulusReflex gem with using the most recent prerelease.

$ bundle add stimulus_reflex -v 3.5.0.pre8
Enter fullscreen mode Exit fullscreen mode

Since the StimulusReflex installer hasn’t been updated to reflect the latest changes in Rails, we’ll have to do a couple of chores ourselves.

1. Enable Caching

StimulusReflex advises to use the cache store for session persistence, so we’ll configure that in development.rb:

# config/environments/development.rb
Rails.application.configure do
  # ...
  config.session_store :cache_store
  # ...
end
Enter fullscreen mode Exit fullscreen mode

And enable it:

$ bin/rails dev:cache
Enter fullscreen mode Exit fullscreen mode

2. Appease the StimulusReflex Sanity Checker

By default, StimulusReflex’s sanity checker will prevent your Rails process from starting if the Ruby and Javascript versions are not congruent. While the behavior of the checker is reworked at the moment, we will have to work around it for now.

To this end, set up a StimulusReflex initializer to bypass it:

$ bin/rails generate stimulus_reflex:initializer
Enter fullscreen mode Exit fullscreen mode
# config/initializers/stimulus_reflex.rb

StimulusReflex.configure do |config|
  config.on_failed_sanity_checks = :warn
end

Enter fullscreen mode Exit fullscreen mode

3. Pin the Required Packages

For the importmap approach, we need to use the bin/importmap tool to pin the StimulusReflex javascript package as follows:

$ bin/importmap pin stimulus_reflex@3.5.0-pre8

Pinning "stimulus_reflex" to https://ga.jspm.io/npm:stimulus_reflex@3.5.0-pre8/javascript/stimulus_reflex.js
Pinning "@hotwired/stimulus" to https://ga.jspm.io/npm:@hotwired/stimulus@3.0.1/dist/stimulus.js
Pinning "@rails/actioncable" to https://ga.jspm.io/npm:@rails/actioncable@7.0.0/app/assets/javascripts/actioncable.esm.js
Pinning "cable_ready" to https://ga.jspm.io/npm:cable_ready@5.0.0-pre8/javascript/index.js
Pinning "morphdom" to https://ga.jspm.io/npm:morphdom@2.6.1/dist/morphdom.js
Pinning "stimulus" to https://ga.jspm.io/npm:stimulus@3.0.1/dist/stimulus.js
Enter fullscreen mode Exit fullscreen mode

Note that due to the namespace change that Hotwire has undergone, we have now pinned two separate Stimulus packages (@hotwired/stimulus and stimulus). StimulusReflex still references the legacy glue package stimulus, the work to transfer everything cleanly to @hotwired/stimulus is still in progress. Since we hand off the application to StimulusReflex, it shouldn’t be a problem though (see below).

4. Initialize the StimulusReflex Client

We use the ActionCable consumer obtained from Turbo’s cable interface to intialize StimulusReflex with a top level await:

// app/javascript/controllers/index.js

import StimulusReflex from "stimulus_reflex"; // <-- add this
import { application } from "./application";
import { cable } from "@hotwired/turbo-rails"; // <-- add this

// Eager load all controllers defined in the import map under controllers/\*\*/\*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
eagerLoadControllersFrom("controllers", application);

// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)

// initialize StimulusReflex w/top-level await
const consumer = await cable.getConsumer()
StimulusReflex.initialize(application, { consumer, debug: true });
Enter fullscreen mode Exit fullscreen mode

Test Everything with a CounterReflex

For the rest of this walkthrough, I follow the official Quickstart Guide closely (for any in-depth explanation of what is going on here, please look there). Let’s first add a PagesController with only an index route:

$ bin/rails g controller Pages index
Enter fullscreen mode Exit fullscreen mode
# config/routes.rb

Rails.application.routes.draw do
  resources :pages, only: :index
end
Enter fullscreen mode Exit fullscreen mode
# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def index
  end
end
Enter fullscreen mode Exit fullscreen mode

Then, we’ll generate a CounterReflex with an increment action and use it to increase a @count index variable derived from a data-count attribute on the invoking element:

$ bin/rails g stimulus_reflex Counter increment
Enter fullscreen mode Exit fullscreen mode
# app/reflexes/counter_reflex.rb

class CounterReflex < ApplicationReflex
  def increment
    @count = element.dataset[:count].to_i + element.dataset[:step].to_i
  end
end
Enter fullscreen mode Exit fullscreen mode

In the index view, we’ll furnish a simple <a> tag with a data-reflex attribute that invokes this increment action on click:

<!-- app/views/pages/index.html.erb -->
<a href="#"
 data-reflex="click->Counter#increment"
 data-step="1"
 data-count="<%= @count.to_i %>">
 Increment <%= @count.to_i %>
</a>
Enter fullscreen mode Exit fullscreen mode

Now it’s time to start Rails and kick the tires:

$ bin/rails s
Enter fullscreen mode Exit fullscreen mode

../assets/images/posts/2022/2022-01-06-importmaps-counter.gif

Success! 🎉

Testing Client-Side Invocation

Now that we’ve established that the declarative way works, let’s double check that imperative client-side invocation functions, too. Again, nothing new here, refer to the relevant section in the Quick Start Guide.

Instead of a data-reflex, we are going to reference the Stimulus controller directly, and hook up a data-action:

<!-- app/views/pages/index.html.erb -->
<a href="#"
 data-controller="counter"
 data-action="click->counter#increment">
 Increment <%= @count %>
</a>
Enter fullscreen mode Exit fullscreen mode

Remember that this is now dependent on our Stimulus counter_controller, so we have some work to do there:

// app/javascript/controllers/counter_controller.js

import ApplicationController from './application_controller'

export default class extends ApplicationController {
  connect () {
    super.connect()
  }

  increment() {
    this.stimulate('Counter#increment', 1)
  }
}
Enter fullscreen mode Exit fullscreen mode

To stay in sync with the Quick Start guide, let’s switch to session storage and passing the step via an argument:

# app/reflexes/counter_reflex.rb

class CounterReflex < ApplicationReflex
  def increment(step = 1)
    session[:count] = session[:count].to_i + step
  end
end
Enter fullscreen mode Exit fullscreen mode

We pick this up in the controller again, to render a new @count:

# app/controllers/pages_controller.rb

class PagesController < ApplicationController
  def index
    @count = session[:count].to_i
  end
end
Enter fullscreen mode Exit fullscreen mode

Voilà 🥂

Conclusion

We’ve established a way to get StimulusReflex up and running with importmaps. The tooling isn’t there yet, but this post essentially lays out the steps that have to be taken to patch it. I will keep updating it as the installers and generators evolve to become importmap-rails compliant.

Top comments (0)