Let us create a space for our Rails Engine
mkdir hello_world && cd hello_world
echo 3.2.2 > .ruby-version
Now let's install the gem manager and the latest Ruby on Rails
gem install bundler rails
Then, let's generate the Rails Engine
rails plugin new . --mountable --database=sqlite3 --no-skip-javascript
In order for our Rails Engine to work in developer mode, we will need to complete some information in gemspec. In the hello_world.gemspec file, we need to specify:
In my case, it will look like as follows:
require_relative "lib/hello_world/version" Gem::Specification.new do |spec| spec.name = "hello_world" spec.version = HelloWorld::VERSION spec.authors = ["Tomasz Kowalewski"] spec.email = ["me@tkowalewski.pl"] spec.homepage = "https://hello-world.com" spec.summary = "Summary of HelloWorld." spec.description = "Description of HelloWorld." spec.license = "MIT" # Prevent pushing this gem to RubyGems.org. To allow pushes either set the "allowed_push_host" # to allow pushing to a single host or delete this section to allow pushing to any host. spec.metadata["allowed_push_host"] = "https://rubygems.org" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = "https://github.com/tkowalewski/hello_world" spec.metadata["changelog_uri"] = "https://github.com/tkowalewski/hello_world/blob/main/CHANGELOG.md" spec.files = Dir.chdir(File.expand_path(__dir__)) do Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] end spec.add_dependency "rails", ">= 7.1.2" end
Add CHANGELOG.md
touch CHANGELOG.md
Time to add importmap-rails and stimulus-rails to hello_world.gemspec.
spec.add_dependency "importmap-rails", "~> 1.2", ">= 1.2.3" spec.add_dependency "stimulus-rails", "~> 1.3"
Install all necessary dependencies
bundle install
The next step is to configure the javascript manifest accordingly. However, as we will be storing the controllers in app/assets/javascripts we need to update our manifest to include a file tree scan.
In the file app/assets/config/hello_world_manifest.js we replace the
//= link_directory ../javascripts/hello_world .js
to
//= link_tree ../javascripts/hello_world .js
We will now create our test stimulus controller. in the file app/assets/javascripts/hello_world/test_controller.js let's place:
import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { console.log("Connected") this.element.textContent = "Test" } }
By creating a controller for a Rails application (app/controllers/hello_world/home_controller.rb)
module HelloWorld class HomeController < ApplicationController def index; end end end
And we will define routines for it (config/routes.rb)
HelloWorld::Engine.routes.draw do root to: 'home#index' end
In the view (app/views/hello_world/home/index.html.erb) let's assign the handling to be done by our stimulus controller
<div data-controller="test"></div>
Then, in the application layout (app/views/layouts/hello_world/application.html.erb) in the head section, add a tag for importmap
<%= javascript_importmap_tags %>
After that, let us create a configuration for importmap by creating a config/importmap.rb file with the contents of
pin_all_from File.expand_path("../app/assets/javascripts", __dir__)
Let's not forget to configure our Rails Engine, so in the lib/hello_world/engine.rb file we should add the following initializers
module HelloWorld class Engine < ::Rails::Engine isolate_namespace HelloWorld initializer 'hello_world-engine.importmap', before: 'importmap' do |app| app.config.importmap.paths += [Engine.root.join('config/importmap.rb')] end initializer "hello_world-engine.assets" do |app| app.config.assets.precompile += %w[hello_world_manifest.js] end end end
Finally, let's move on to our dummy application and configure it to work with importmaps and stimulus. So let's add importmap-rails and stimulus-railsto the Gemfile
gem 'importmap-rails', '~> 1.2', '>= 1.2.3' gem 'stimulus-rails', '~> 1.3'
Installing gems
bundle install
We are installing the importmaps and stimulus for our dummy application
cd test/dummy && bin/rails importmap:install stimulus:install
Now we have to fix the directory structure in our dummy application, so that the application dummy javascript manifest does not attempt to load the contents from a non-existent folder.
mkdir app/assets/javascripts && touch app/assets/javascripts/.keep
The final step is to load our test controller (app/assets/javascripts/hello_world/test_controller.js) in the dummy application. In order for the dummy application (or another application in which we will use our Rails Engine) to "see" our controller, we need to add controllers to the Rails Engine in test/dummy/app/javascript/controllers/index.js. We do this by adding
eagerLoadControllersFrom("hello_world", application)
So that our test/dummy/app/javascript/controllers/index.js will look like this
// Import and register all your controllers from the importmap under controllers/* import { application } from "controllers/application" // Eager load all controllers defined in the import map under controllers/**/*_controller import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" eagerLoadControllersFrom("controllers", application) eagerLoadControllersFrom("hello_world", 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)
The final step is to launch the server
cd ../../ && bin/rails s
And the result of our work should be the content Test rendered by our test stimulus controller (app/assets/javascripts/hello_world/test_controller.js) on the web page at http://localhost:3000/hello_world.
Ok, we have tested our Rails Engine in a dummy application. Now, let's define the implementation steps for a real Rails application.
First, we add our Rails Engine as a gem to the Gemfile of the respective project
gem "hello_world"
Installing gems
bundle install
We install the Rails Engine in config/routes.rb
mount HelloWorld::Engine => "/hello_world"
We load the stimulus controllers in app/javascript/controllers/index.jsby adding the
eagerLoadControllersFrom("hello_world", application)
And we can enjoy a working Rails Engine :)