Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable engine support #103

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,37 @@ There's [native support for import maps in Chrome/Edge 89+](https://caniuse.com/

## Installation

Importmap for Rails is automatically included in Rails 7+ for new applications, but you can also install it manually in existing applications:
Note: In order to use JavaScript from Rails frameworks like Action Cable, Action Text, and Active Storage, you must be running Rails 7.0+. This was the first version that shipped with ESM compatible builds of these libraries.

### a) Rails apps

Importmap for Rails is automatically included in Rails 7+ for new applications, but you can also install it manually in existing applications:

1. Add `importmap-rails` to your Gemfile with `gem 'importmap-rails'`
2. Run `./bin/bundle install`
3. Run `./bin/rails importmap:install`

Note: In order to use JavaScript from Rails frameworks like Action Cable, Action Text, and Active Storage, you must be running Rails 7.0+. This was the first version that shipped with ESM compatible builds of these libraries.
### b) Rails engines

This gem behaves differently when it is loaded into a Rails Engine. We assume that you wish to load the gem's behaviour into the Engine's codebase rather than loading it into the **dummy** application's codebase. You may wish to copy the changes that the installer makes into the **dummy** application.

Importmap for Rails is automatically included in Rails 7+ for new engines, but you can also install it manually in existing engines:

1. In your `<engine>.gemspec` file, add the following:
```ruby
spec.add_dependency 'importmap-rails'
```
2. Near the top of your `lib/<engine_name>/engine.rb` file, add the following:
```ruby
require 'importmap-rails'
```
3. If you wish to load a non-standard version of this gem, such as the latest unreleased version in the **main**
branch, add the following to your `Gemfile`:
```ruby
gem 'importmap-rails'
```
4. Run `./bin/bundle install`
5. Run `./bin/rails app:importmap:install`. Please note the addition of `app:` in this command.

## How do importmaps work?

Expand Down Expand Up @@ -58,7 +82,7 @@ import React from "react"

The import map is setup through `Rails.application.importmap` via the configuration in `config/importmap.rb`. This file is automatically reloaded in development upon changes, but note that you must restart the server if you remove pins and need them gone from the rendered importmap or list of preloads.

This import map is inlined in the `<head>` of your application layout using `<%= javascript_importmap_tags %>`, which will setup the JSON configuration inside a `<script type="importmap">` tag. After that, the [es-module-shim](https://github.com/guybedford/es-module-shims) is loaded, and then finally the application entrypoint is imported via `<script type="module">import "application"</script>`. That logical entrypoint, `application`, is mapped in the importmap script tag to the file `app/javascript/application.js`.
This import map is inlined in the `<head>` of your application layout using `<%= javascript_importmap_tags %>`, which will set up the JSON configuration inside a `<script type="importmap">` tag. After that, the [es-module-shim](https://github.com/guybedford/es-module-shims) is loaded, and then finally the application entrypoint is imported via `<script type="module">import "application"</script>`. That logical entrypoint, `application`, is mapped in the importmap script tag to the file `app/javascript/application.js`.

It's in `app/javascript/application.js` you setup your application by importing any of the modules that have been defined in the import map. You can use the full ESM functionality of importing any particular export of the modules or everything.

Expand Down
4 changes: 2 additions & 2 deletions app/helpers/importmap/importmap_tags_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def javascript_inline_importmap_tag(importmap_json = Rails.application.importmap
# Configure es-modules-shim with nonce support if the application is using a content security policy.
def javascript_importmap_shim_nonce_configuration_tag
if content_security_policy?
tag.script({ nonce: content_security_policy_nonce }.to_json.html_safe,
tag.script({ nonce: content_security_policy_nonce }.to_json.html_safe,
type: "esms-options", nonce: content_security_policy_nonce)
end
end
Expand All @@ -34,7 +34,7 @@ def javascript_importmap_shim_tag(minimized: true)
# Import a named JavaScript module(s) using a script-module tag.
def javascript_import_module_tag(*module_names)
imports = Array(module_names).collect { |m| %(import "#{m}") }.join("\n")
tag.script imports.html_safe,
tag.script imports.html_safe,
type: "module", nonce: content_security_policy_nonce
end

Expand Down
44 changes: 29 additions & 15 deletions lib/install/install.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
APPLICATION_LAYOUT_PATH = Rails.root.join("app/views/layouts/application.html.erb")

if APPLICATION_LAYOUT_PATH.exist?
say "Add Importmap include tags in application layout"
insert_into_file APPLICATION_LAYOUT_PATH.to_s, "\n <%= javascript_importmap_tags %>", before: /\s*<\/head>/
else
say "Default application.html.erb is missing!", :red
say " Add <%= javascript_importmap_tags %> within the <head> tag in your custom layout."
# frozen_string_literal: true

# This file is called by +lib/tasks/importmap_tasks.rake+.

rails_root = Pathname.new(Rails.root.to_s.split(%r{\/(spec|test)\/dummy}).first)
say "Adding configuration for the importmap-rails gem into: #{rails_root}"

Dir.glob("#{rails_root}/**/application.html.*").each do |layout_path|
say "Add Importmap include tags in application layout file at #{layout_path}"
spaces = IO.foreach(layout_path).grep(/(\s*)body/).first.match(/(\s*)/)[1]
case layout_path.split('.html.').last
when 'erb'
insert_into_file layout_path.to_s, "\n#{spaces} <%= javascript_importmap_tags %>", before: /\s*<\/head>/
when 'slim'
insert_into_file layout_path.to_s, "\n#{spaces} = javascript_importmap_tags", before: /\s*body/
when 'haml'
insert_into_file layout_path.to_s, "\n#{spaces} =javascript_importmap_tags", before: /\s*%body/
else
say "Couldn't find an application.html.erb|haml|slim file!", :red
say " Add <%= javascript_importmap_tags %> within the <head> tag into your custom layout."
end
end

say "Create application.js module as entrypoint"
create_file Rails.root.join("app/javascript/application.js") do <<-JS
create_file rails_root.join("app/javascript/application.js") do <<-JS
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
JS
end

say "Use vendor/javascript for downloaded pins"
empty_directory "vendor/javascript"
keep_file "vendor/javascript"
empty_directory rails_root.join("vendor/javascript")
keep_file rails_root.join("vendor/javascript")

if (sprockets_manifest_path = Rails.root.join("app/assets/config/manifest.js")).exist?
sprockets_manifest_path = rails_root.join("app/assets/config/manifest.js")
if sprockets_manifest_path.exist?
say "Ensure JavaScript files are in the Sprocket manifest"
append_to_file sprockets_manifest_path,
%(//= link_tree ../../javascript .js\n//= link_tree ../../../vendor/javascript .js\n)
end

say "Configure importmap paths in config/importmap.rb"
copy_file "#{__dir__}/config/importmap.rb", "config/importmap.rb"
copy_file "#{__dir__}/config/importmap.rb", rails_root.join("config/importmap.rb")

say "Copying binstub"
copy_file "#{__dir__}/bin/importmap", "bin/importmap"
chmod "bin", 0755 & ~File.umask, verbose: false
copy_file "#{__dir__}/bin/importmap", rails_root.join("bin/importmap")
chmod rails_root.join("bin"), 0755 & ~File.umask, verbose: false
5 changes: 3 additions & 2 deletions lib/tasks/importmap_tasks.rake
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace :importmap do
desc "Setup Importmap for the app"
task :install do
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/install.rb", __dir__)}"
task :install do |task|
namespace = task.name.split(/importmap:install/).first
system "#{RbConfig.ruby} ./bin/rails #{namespace}app:template LOCATION=#{File.expand_path("../install/install.rb", __dir__)}"
end
end