How to pin/import stimulus component controllers?
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
-
Related answer on stackoverflow (with fewer technical details): https://stackoverflow.com/a/73228193/19846520
-
Rails guide on the assets pipeline: https://guides.rubyonrails.org/asset_pipeline.html