It’s a great idea to puts component stimulus controlers alongside with component classes and tempaltes, which is the default of bin/rails generate stimulus:component.

But, how to make it work?

Question

It is recommended to generate stimulus controllers for componenets under app/components . For example:

$ bin/rails g component dropzone
$ bin/rails g stimulus:component dropzone

# which generate followings:
app/components
|-- dropzone_component.rb 
|-- dropzone_component.html.slim
`-- dropzone_component_controller.js

But how to pin that component controller?

Solution

This issue relates to three parts of modern Rails stack, namely Asset, Importmap and Stimulus. So we need to take three steps to fix it.

Part I: Asset - make it accessable on the web server

Rails has its asset pipeline to process (including compiling) and serve the images, styleshets, javascripts and any other stuff. So first we should let Rails know there are javascript assets under app/components:

# config/initializers/assets.rb
Rails.application.config.assets.paths << Rails.root.join("app/components")

You might be curious that there is no “app/javascript” nor “vendor/javascript” in the defaults. Actually, Importmap will inject that two paths.

Then add the path to the manifests also:

// app/assets/config/manifest.js
//= link_tree ../../components .js

Part II: Importmap - make it importable in javascript

Importmap is great. Let’s make it works for component controllers:

pin_all_from "app/components", under: "components", to: ""
#              ^                        ^               ^
#              |                        |               |
#       where the files           where to import       where these assets
#        actually live          for `stimulus` app      are served  

We must pass a “” to the :to attribute of pin_all_from to get rid of the prefix path. Or it will try to find component controller on “components/*”, while the asset pipeline serves them on the root path.

Part III: Stimulus - make it finally works

Since the all stimulus component controllers were pinned under components, this step is also one-line-adding:

// app/javascript/controllers/index.js
eagerLoadControllersFrom('components', application)

That’s it. Now all component controllers should work~

Don’t forget add data-controller attribute to the template and restart the Rails server since we modify an initializer.

References