Let's say we've added a plugin directory:
app
- javascript
- plugin
app.js
index.js
Everything described below works because app/javascript directory
- is part of
Rails.application.config.assets.paths
- and
app/assets/config/manifest.js
# TLDR
# NOTE: pin a single file
# file name is relative to `app/javascript`; don't add `.js`
#
pin("plugin/app") # import "plugin/app"
pin("plugin/index") # import "plugin/index"
# NOTE: pin all the files in `plugin` directory and subdirectories.
# directory name is relative to `Rails.root`
#
pin_all_from("app/javascript/plugin", under: "plugin")
# import "plugin/app"
# import "plugin" // will import plugin/index.js
It seems pin_all_from did not get any documentation besides that it's there in the default importmap.rb.
pin_all_from(dir, under: nil, to: nil, preload: false)
https://github.com/rails/importmap-rails/blob/v1.1.2/lib/importmap/map.rb#L33
def pin_all_from(dir, under: nil, to: nil, preload: false)
clear_cache
@directories[dir] = MappedDir.new(dir: dir, under: under, path: to, preload: preload)
end
dir - Path relative to Rails.root or an absolute path.
Options:
:under - Optional pin prefix. Required if you have index.js file.
:to - Optional path to asset. Falls back to :under option. Required if :under is omitted.
This path is relative to Rails.application.config.assets.paths.
:preload - Adds a modulepreload link if set to true:
<link rel="modulepreload" href="/assets/turbo-5605bff731621f9ca32b71f5270be7faa9ccb0c7c810187880b97e74175d85e2.js">
We can pin all the files in the plugin directory:
pin_all_from "app/javascript/plugin", under: "plugin"
# NOTE: `index.js` file gets a special treatment, instead
# of pinning `plugin/index` it is just `plugin`.
{
"imports": {
"plugin/app": "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
"plugin": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
}
}
Here is how it all fits together:
"plugin/app": "/assets/plugin/app-04024382391bb...4145d8113cf788597.js"
# ^ ^ ^
# | | |
# :under | `-path_to_asset("plugin/app.js")
# | ^ ^
# | | |
# |.. (:to||:under)-' |
# "#{dir}/app.js" |
# '''''`-------------------------'
:to option might not be obvious here. It is useful if :under option is changed, which will make path_to_asset fail to find app.js.
For example, :under option can be anything you want, but :to option has to be a path that asset pipeline, Sprockets, can find (see Rails.application.config.assets.paths) and also precompile (see app/assets/config/manifest.js).
pin_all_from "app/javascript/plugin", under: "@plug", to: "plugin"
# Outputs these pins
#
# "@plug/app": "/assets/plugin/app-04024382391b1...16beb14ce788597.js"
# "@plug": "/assets/plugin/index-04024382391bb91...4ebeb14ce788597.js"
#
# and can be used like this
#
# import "@plug";
# import "@plug/app";
Specifying absolute path will bypass asset pipeline.
pin_all_from("app/javascript/plugin", under: "plugin", to: "/plugin")
# "plugin/app": "/plugin/app.js"
# "plugin": "/plugin/index.js"
#
# NOTE: It is up to you to set up `/plugin/*` route and serve these files.
To pin a single file use pin method.
pin(name, to: nil, preload: false)
https://github.com/rails/importmap-rails/blob/v1.1.2/lib/importmap/map.rb#L28
def pin(name, to: nil, preload: false)
clear_cache
@packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
end
name - Name of the pin.
Options:
:to - Optional path to asset. Falls back to {name}.js. This path is relative to Rails.application.config.assets.paths.
:preload - Adds a modulepreload link if set to true
When pinning a local file, specify name relative to app/javascript directory.
pin("plugin/app")
pin("plugin/index")
# NOTE: produces these pins
{
"imports": {
"plugin/app": "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
"plugin/index": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
}
}
Here is how it fits together:
"plugin/app": "/assets/plugin/app-04024382391bb...16cebeb14ce788597.js"
# ^ ^
# | |
# name `-path_to_asset("plugin/app.js")
# ^
# |
# (:to||"#{name}.js")-'
If you want to change the name of the pin, :to option is required to give path_to_asset a valid file location.
For example, to get the same pin for index.js file as the one we get from pin_all_from:
pin("plugin", to: "plugin/index")
{
"imports": {
"plugin": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
}
}
Don't forget that pinning your files doesn't make them load. They have to imported in application.js or in a separate module tag:
<%= javascript_import_module_tag "plugin" %>
You can mess around with Importmap in the console, it's faster to debug and learn what works and what doesn't:
>> map = Importmap::Map.new
>> map.pin_all_from("app/javascript/plugin", under: "plugin")
# NOTE: ApplicationController has the required `path_to_asset` helper
>> ApplicationController.helpers.path_to_asset("plugin/app.js")
=> "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
# NOTE: `path_to_asset` searches relative to these paths
>> Rails.application.config.assets.paths
>> puts map.to_json(resolver: ApplicationController.helpers)
{
"imports": {
"plugin/app": "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
"plugin": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
}
}
# `pin` seems so much simpler now
>> map.pin("application")
>> puts map.to_json(resolver: ApplicationController.helpers)
{
"imports": {
"application": "/assets/application-8cab2d9024ef6f21fd55792af40001fd4ee1b72b8b7e14743452fab1348b4f5a.js"
}
}
# Importmap from config/importmap.rb
>> Rails.application.importmap