I have opened an issue in the importmap-rails gem github repository here about this but thought I'd throw the question out here in case anyone might have a workaround
This is what I have discovered so far
A new engine with Rails 7 alpha 2 or Rails 7.0, generated using rails plugin new custom_page --mountable --full
generates a new engine that includes the importmap-rails gem in the bundled gems but there is no ability to use it. Adding spec.add_dependency 'importmap-rails'
to the enginename.gemspec makes no difference, nor does adding a require importmap-rails
to engine.rb. There is no importmap executable in the bin directory.
A call to bundle info importmap-rails
Produces a promising result showing that the gem is installed by default
* importmap-rails (0.8.1)
Summary: Use ESM with importmap to manage modern JavaScript in Rails without transpiling or bundling.
Homepage: https://github.com/rails/importmap-rails
Source Code: https://github.com/rails/importmap-rails
Path: /home/jamie/.rvm/gems/ruby-3.0.0@custom_page/gems/importmap-rails-0.8.1
A call to rails --tasks
shows
rails app:importmap:install # Setup Importmap for the app
But I believe this is coming from the test application generated by the --full option rather than being available to the rails command for the engine. I was expecting to see the same without app: prefix A call to this task resolves to a template error as shown
rails app:importmap:install
Don't know how to build task 'app:template' (See the list of available tasks with
rails --tasks
) Did you mean? app:tmp:create
If there is a workaround solution to this I'd be grateful to hear it and I'm sure others will too. The reason for me wanting this is that I totally failed to introduced webpacker in a rails 6.1.4 engine and I was hoping this was going to be my, much improved, solution
You don't need to use the install task to set up importmaps. All it does is a few copy paste operations and it doesn't really help with the engine set up anyway.
Add importmaps to engine's gemspec file:
# my_engine/my_engine.gemspec
spec.add_dependency "importmap-rails"
Update engine.rb:
# my_engine/lib/my_engine/engine.rb
require "importmap-rails"
module MyEngine
class Engine < ::Rails::Engine
isolate_namespace MyEngine
initializer "my-engine.importmap", before: "importmap" do |app|
# NOTE: this will add pins from this engine to the main app
# https://github.com/rails/importmap-rails#composing-import-maps
app.config.importmap.paths << root.join("config/importmap.rb")
# NOTE: something about cache; I did not look into it.
# https://github.com/rails/importmap-rails#sweeping-the-cache-in-development-and-test
app.config.importmap.cache_sweepers << root.join("app/assets/javascripts")
end
# NOTE: add engine manifest to precompile assets in production
initializer "my-engine.assets" do |app|
app.config.assets.precompile += %w[my_engine_manifest]
end
end
end
Update assets manifest:
# my_engine/app/assets/config/my_engine_manifest.js
//= link_tree ../javascripts/my_engine .js
Add javascript entry point for our engine, if needed. Pins will be available without this file.
# my_engine/app/assets/javascripts/my_engine/application.js
// do some javascript
document.querySelector("h1").innerText = "hi, i'm your engine";
console.log("hi, again");
Update engine's layout:
# my_engine/app/views/layouts/my_engine/application.html.erb
<!DOCTYPE html>
<html>
<head>
<!--
NOTE: This loads/imports main app `application.js` and all the pins from
the main app and from the engine (because we set it up in the engine.rb).
-->
<%= javascript_importmap_tags %>
<!--
NOTE: To add engine's javascript functionality we have to load the
entrypoint here or `import` it in the main app `application.js`
-->
<%= javascript_import_module_tag "my_engine/application" %>
</head>
<body> <%= yield %> </body>
</html>
Create importmap.rb and pin my_engine/application
, this name has to match with javascript_import_module_tag
. It cannot clash with any other name in the main app, so you can't just use application
:
# my_engine/config/importmap.rb
# NOTE: this pin works because `my_engine/app/assets/javascripts
# is in the `Rails.application.config.assets.paths`
pin "my_engine/application"
Some extras to test the setup:
# config/routes.rb
Rails.application.routes.draw do
mount MyEngine::Engine => "/"
end
# my_engine/config/routes.rb
MyEngine::Engine.routes.draw do
get "home", to: "homes#index"
end
# my_engine/app/controllers/my_engine/homes_controller.rb
module MyEngine
class HomesController < ApplicationController
def index; end
end
end
# my_engine/app/views/my_engine/homes/index.html.erb
<h1>Home</h1>
At this point you should have this in your rendered layout's <head>
tag, among other things:
<script type="importmap" data-turbo-track="reload">{
"imports": {
"application": "/assets/application-66ce7505c61e3e4910ff16e7c220e1fbfb39251cd82e4bab8d325b3aae987cf9.js",
"my_engine/application": "/assets/my_engine/application-31ce493e8376b4c20703a50f38d419ae309ffe410b7ab7fec47440e02eef08a8.js",
}
}</script>
<script type="module">import "application"</script>
<script type="module">import "my_engine/application"</script>
H1
tag should change to <h1>hi, i'm your engine</h1>
on reload.
Additional importmaps can be added manually with https://generator.jspm.io/.
For bonus points, bin/importmap
can be customized to work inside the engine. Create a new importmap
file inside bin directory.
# my_engine/bin/importmap
#!/usr/bin/env ruby
# NOTE: don't forget to `chmod u+x bin/importmap` to make it executable.
# make sure we are loading the correct versions of things
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
# NOTE: importmap requires some rails goodness that we don't have in the engine,
# because we don't have `config/application.rb` that loads the environment.
require "rails"
# importmap-rails is not loaded automatically
require "importmap-rails"
# the actual command runner
require "importmap/commands"
Run from inside the engine directory:
$ bin/importmap pin react
Pinning "react" to https://ga.jspm.io/npm:[email protected]/index.js
$ cat config/importmap.rb
pin "my_engine/application"
pin "react", to: "https://ga.jspm.io/npm:[email protected]/index.js"
I haven't tested it too much, so any feedback would be welcome. Restart the server if something doesn't show up, I don't know how reloading behaves with all this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With