diff --git a/README.md b/README.md index 2adf16e..3bb4335 100644 --- a/README.md +++ b/README.md @@ -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 `.gemspec` file, add the following: + ```ruby + spec.add_dependency 'importmap-rails' + ``` +2. Near the top of your `lib//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? @@ -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 `` of your application layout using `<%= javascript_importmap_tags %>`, which will setup the JSON configuration inside a ``. 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 `` of your application layout using `<%= javascript_importmap_tags %>`, which will set up the JSON configuration inside a ``. 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. diff --git a/app/helpers/importmap/importmap_tags_helper.rb b/app/helpers/importmap/importmap_tags_helper.rb index d249f3b..737d8d5 100644 --- a/app/helpers/importmap/importmap_tags_helper.rb +++ b/app/helpers/importmap/importmap_tags_helper.rb @@ -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 @@ -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 diff --git a/lib/install/install.rb b/lib/install/install.rb index 03d8dcc..64ace6c 100644 --- a/lib/install/install.rb +++ b/lib/install/install.rb @@ -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 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 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 diff --git a/lib/tasks/importmap_tasks.rake b/lib/tasks/importmap_tasks.rake index bb8c550..14e67a6 100644 --- a/lib/tasks/importmap_tasks.rake +++ b/lib/tasks/importmap_tasks.rake @@ -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