importmap-rails 0.2.1 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbd2170db15c02ccf1f860205163effe77dc5ede4c0196cbc424e09484740a71
4
- data.tar.gz: be12a543aa3ce6950551d3044114e19adbcaadfb4c25bc3adbad45cad2dd0c9b
3
+ metadata.gz: 7ac84f89fb26d278f81e9a7ce208f804833eab66cd76876884cf068ec5b68d94
4
+ data.tar.gz: 540c19550f7d4a7f3c7d5beb7fdba155fc1526b6a32c1a76c4b8f0be7b5486e2
5
5
  SHA512:
6
- metadata.gz: 69d4ba2a8c5dcacfc2afe7111c8806da5d2a845590cebc4cf15338ac2f8a913af8f95d32910df142476b039fd91325b9a3157f72904c921d85207966269d776a
7
- data.tar.gz: 2483d8791a05f12f19cb6e09b9372a254da9c60f9586c78d9470e9d57521848452590ebc66c16aca51dff19f4b71df35478c87b4c9ae6bbbf30a238e398a2dd3
6
+ metadata.gz: be5069a7570c5f56ee11833d387c1e16d0075e8699bd6e23c62bd7ff2296dd38760195d4e4d3296489bda6070406bc2016771bc423b396ff0f1a9fb1c5197e15
7
+ data.tar.gz: c6c5c08a36c69b2707d5047b5d2957cc0d0a7ac67fcce8343ea53316b43e3df008e47381ae77d5e4c8d624f9eb6cef0260c6ff255f78d9bd2fe4855494187855
data/README.md CHANGED
@@ -13,14 +13,14 @@ There's [native support for import maps in Chrome/Edge 89+](https://caniuse.com/
13
13
  2. Run `./bin/bundle install`
14
14
  3. Run `./bin/rails importmap:install`
15
15
 
16
- By default, all the files in `app/assets/javascripts` and the three major Rails JavaScript libraries are already mapped. You can add more mappings in `config/initializers/assets.rb`.
16
+ By default, all the files in `app/assets/javascripts` and the three major Rails JavaScript libraries are already mapped. You can add more mappings in `config/initializers/importmap.rb`.
17
17
 
18
18
  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.
19
19
 
20
20
 
21
21
  ## Usage
22
22
 
23
- The import map is configured programmatically through the `Rails.application.config.importmap` assignment, which by default is setup in `config/initializers/assets.rb` after running the installer. (Note that since this is a config initializer, you must restart your development server after making any changes.)
23
+ The import map is configured programmatically through the `Rails.application.config.importmap` assignment, which by default is setup in `config/initializers/importmap.rb` after running the installer. (Note that since this is a config initializer, you must restart your development server after making any changes.)
24
24
 
25
25
  This programmatically configured 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/assets/javascripts/application.js`, which is copied and digested by the asset pipeline.
26
26
 
@@ -31,7 +31,7 @@ It makes sense to use logical names that match the package names used by NPM, su
31
31
 
32
32
  ## Use with Hotwire
33
33
 
34
- This gem was designed for use with [Hotwire](https://hotwired.dev) in mind. The Hotwire gems, like [turbo-rails](https://github.com/hotwired/turbo-rails) and [stimulus-rails](https://github.com/hotwired/stimulus-rails) (both bundled as [hotwire-rails](https://github.com/hotwired/hotwire-rails)), are automatically configured for use with `importmap-rails`. This means you won't have to manually setup the path mapping in `config/initializers/assets.rb`, and instead can simply refer to the logical names directly in your `app/assets/javascripts/application.js`, like so:
34
+ This gem was designed for use with [Hotwire](https://hotwired.dev) in mind. The Hotwire gems, like [turbo-rails](https://github.com/hotwired/turbo-rails) and [stimulus-rails](https://github.com/hotwired/stimulus-rails) (both bundled as [hotwire-rails](https://github.com/hotwired/hotwire-rails)), are automatically configured for use with `importmap-rails`. This means you won't have to manually setup the path mapping in `config/initializers/importmap.rb`, and instead can simply refer to the logical names directly in your `app/assets/javascripts/application.js`, like so:
35
35
 
36
36
  ```js
37
37
  import "@hotwired/turbo-rails"
@@ -41,7 +41,7 @@ import "@hotwired/stimulus-autoloader"
41
41
 
42
42
  ## Use with Skypack (and other CDNs)
43
43
 
44
- Instead of mapping JavaScript modules to files in your application's path, you can also reference them directly from JavaScript CDNs like Skypack. Simply add them to the `config/initializers/assets.rb` with the URL instead of the local path:
44
+ Instead of mapping JavaScript modules to files in your application's path, you can also reference them directly from JavaScript CDNs like Skypack. Simply add them to the `config/initializers/importmap.rb` with the URL instead of the local path:
45
45
 
46
46
  ```ruby
47
47
  Rails.application.config.importmap.draw do
@@ -56,7 +56,34 @@ Now you can use these in your application.js entrypoint like you would any other
56
56
  import "trix"
57
57
  import md5 from "md5"
58
58
  console.log(md5("Hash it out"))
59
- ```
59
+ ```
60
+
61
+
62
+ ## Preloading pinned modules
63
+
64
+ To mitigate the waterfall effect where the browser has to load one file after another before it can get to the deepest nested import, we use [modulepreload links](https://developers.google.com/web/updates/2017/12/modulepreload) in [browsers that support it](https://caniuse.com/?search=modulepreload). Pinned modules are preloaded by default, but you can turn this off with `preload: false`.
65
+
66
+ Example:
67
+
68
+ ```ruby
69
+ # config/initializers/importmap.rb
70
+ Rails.application.config.importmap.draw do
71
+ pin "trix", to: "https://cdn.skypack.dev/trix"
72
+ pin "md5", to: "https://cdn.skypack.dev/md5", preload: false
73
+ end
74
+
75
+ # app/views/layouts/application.html.erb
76
+ <%= javascript_importmap_tags %>
77
+
78
+ # will include the following link before the importmap is setup:
79
+ <link rel="modulepreload" href="https://cdn.skypack.dev/trix">
80
+ ...
81
+ ```
82
+
83
+
84
+ ## Caching the import map and preload modules
85
+
86
+ The import map should be cached in production, and is so by default via the `config.importmap.cached` option that will be set to the same value as `config.action_controller.perform_caching`, unless explicitly set differently.
60
87
 
61
88
 
62
89
  ## Expected errors from using the es-module-shim
@@ -1,5 +1,8 @@
1
1
  /* ES Module Shims 0.12.2 */
2
2
  (function () {
3
+ // Temporary hack around https://github.com/guybedford/es-module-shims/issues/148
4
+ if (navigator.userAgent.match("Chrome")) return
5
+
3
6
  const resolvedPromise = Promise.resolve();
4
7
 
5
8
  let baseUrl;
@@ -2,16 +2,17 @@ module Importmap::ImportmapTagsHelper
2
2
  # Setup all script tags needed to use an importmap-powered entrypoint (which defaults to application.js)
3
3
  def javascript_importmap_tags(entry_point = "application")
4
4
  safe_join [
5
+ javascript_importmap_module_preload_tags,
5
6
  javascript_inline_importmap_tag,
6
7
  javascript_importmap_shim_tag,
7
8
  javascript_import_module_tag(entry_point)
8
9
  ], "\n"
9
10
  end
10
11
 
11
- # Generate an inline importmap tag using the passed `importmap` object to produce the JSON map.
12
- # By default, `Rails.application.config.importmap` is used for this object.
13
- def javascript_inline_importmap_tag(importmap = Rails.application.config.importmap)
14
- tag.script(importmap.to_json(self).html_safe, type: "importmap", "data-turbo-track": "reload")
12
+ # Generate an inline importmap tag using the passed `importmap_json` JSON string.
13
+ # By default, `Rails.application.config.importmap.to_json(resolver: self)` is used.
14
+ def javascript_inline_importmap_tag(importmap_json = Rails.application.config.importmap.to_json(resolver: self))
15
+ tag.script(importmap_json.html_safe, type: "importmap", "data-turbo-track": "reload")
15
16
  end
16
17
 
17
18
  # Include the es-module-shim needed to make importmaps work in browsers without native support (like Firefox + Safari).
@@ -23,4 +24,16 @@ module Importmap::ImportmapTagsHelper
23
24
  def javascript_import_module_tag(module_name)
24
25
  tag.script %(import "#{module_name}").html_safe, type: "module"
25
26
  end
27
+
28
+ # Link tags for preloading all modules marked as preload: true in the `importmap`
29
+ # (defaults to Rails.application.config.importmap), such that they'll be fetched
30
+ # in advance by browsers supporting this link type (https://caniuse.com/?search=modulepreload).
31
+ def javascript_importmap_module_preload_tags(importmap = Rails.application.config.importmap)
32
+ javascript_module_preload_tag *importmap.preloaded_module_paths(resolver: self)
33
+ end
34
+
35
+ # Link tag(s) for preloading the JavaScript module residing in `*paths`. Will return one link tag per path element.
36
+ def javascript_module_preload_tag(*paths)
37
+ safe_join(Array(paths).collect { |path| tag.link rel: "modulepreload", href: path }, "\n")
38
+ end
26
39
  end
@@ -9,6 +9,7 @@ module Importmap
9
9
  initializer "importmap.assets" do
10
10
  if Rails.application.config.respond_to?(:assets)
11
11
  Rails.application.config.assets.precompile += %w( es-module-shims.js )
12
+ Rails.application.config.assets.paths << Rails.root.join("app/javascript")
12
13
  end
13
14
  end
14
15
 
@@ -17,5 +18,11 @@ module Importmap
17
18
  helper Importmap::ImportmapTagsHelper
18
19
  end
19
20
  end
21
+
22
+ initializer "importmap.caching" do |app|
23
+ if Rails.application.config.importmap.cached.nil?
24
+ Rails.application.config.importmap.cached = app.config.action_controller.perform_caching
25
+ end
26
+ end
20
27
  end
21
28
  end
data/lib/importmap/map.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  class Importmap::Map
2
2
  attr_reader :files, :directories
3
+ attr_accessor :cached
3
4
 
4
5
  def initialize
5
6
  @files, @directories = {}, {}
@@ -9,51 +10,81 @@ class Importmap::Map
9
10
  instance_eval(&block)
10
11
  end
11
12
 
12
- def pin(name, to:)
13
- @files[name] = to
13
+ def pin(name, to: nil, preload: true)
14
+ @files[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
14
15
  end
15
16
 
16
- def pin_all_from(path, append_base_path: false)
17
- @directories[path] = append_base_path
17
+ def pin_all_from(path, under: nil, preload: true)
18
+ @directories[path] = MappedDir.new(path: path, under: under, preload: preload)
18
19
  end
19
20
 
20
- def to_json(resolver)
21
- { "imports" => resolve_asset_paths(resolver) }.to_json
21
+ def preloaded_module_paths(resolver:)
22
+ cache_as(:preloaded_module_paths) do
23
+ resolve_asset_paths(expanded_preloading_files_and_directories, resolver: resolver).values
24
+ end
25
+ end
26
+
27
+ def to_json(resolver:)
28
+ cache_as(:json) do
29
+ { "imports" => resolve_asset_paths(expanded_files_and_directories, resolver: resolver) }.to_json
30
+ end
22
31
  end
23
32
 
24
33
  private
25
- def resolve_asset_paths(resolver)
26
- expanded_files_and_directories.transform_values do |path|
34
+ MappedFile = Struct.new(:name, :path, :preload, keyword_init: true)
35
+ MappedDir = Struct.new(:path, :under, :preload, keyword_init: true)
36
+
37
+ def cache_as(name)
38
+ if (cached && result = instance_variable_get("@cached_#{name}"))
39
+ result
40
+ else
41
+ instance_variable_set("@cached_#{name}", yield)
42
+ end
43
+ end
44
+
45
+ def resolve_asset_paths(paths, resolver:)
46
+ paths.transform_values do |mapping|
27
47
  begin
28
- resolver.asset_path(path)
48
+ resolver.asset_path(mapping.path)
29
49
  rescue Sprockets::Rails::Helper::AssetNotFound
30
- Rails.logger.warn "Importmap skipped missing path: #{path}"
50
+ Rails.logger.warn "Importmap skipped missing path: #{mapping.path}"
31
51
  nil
32
52
  end
33
53
  end.compact
34
54
  end
35
55
 
56
+ def expanded_preloading_files_and_directories
57
+ expanded_files_and_directories.select { |name, mapping| mapping.preload }
58
+ end
59
+
36
60
  def expanded_files_and_directories
37
61
  @files.dup.tap { |expanded| expand_directories_into expanded }
38
62
  end
39
63
 
40
64
  def expand_directories_into(paths)
41
- @directories.each do |(path, append_base_path)|
42
- if (absolute_path = absolute_root_of(path)).exist?
65
+ @directories.values.each do |mapping|
66
+ if (absolute_path = absolute_root_of(mapping.path)).exist?
43
67
  find_javascript_files_in_tree(absolute_path).each do |filename|
44
68
  module_filename = filename.relative_path_from(absolute_path)
45
- module_name = module_name_from(module_filename)
46
- module_path = append_base_path ? absolute_path.basename.join(module_filename).to_s : module_filename.to_s
69
+ module_name = module_name_from(module_filename, mapping.under)
70
+ module_path = mapping.under ? absolute_path.basename.join(module_filename).to_s : module_filename.to_s
47
71
 
48
- paths[module_name] = module_path
72
+ paths[module_name] = MappedFile.new(name: module_name, path: module_path, preload: mapping.preload)
49
73
  end
50
74
  end
51
75
  end
52
76
  end
53
77
 
54
78
  # Strip off the extension, /index, or any versioning data for an absolute module name.
55
- def module_name_from(filename)
56
- filename.to_s.remove(filename.extname).remove("/index").split("@").first
79
+ def module_name_from(filename, under)
80
+ filename_without_ext = filename.to_s.remove(filename.extname)
81
+
82
+ if filename_without_ext == "index" && under
83
+ under
84
+ else
85
+ module_name = filename_without_ext.split("@").first
86
+ under ? "#{under}/#{module_name}" : module_name
87
+ end
57
88
  end
58
89
 
59
90
  def find_javascript_files_in_tree(path)
@@ -1,3 +1,3 @@
1
1
  module Importmap
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.5"
3
3
  end
@@ -9,8 +9,8 @@ else
9
9
  end
10
10
 
11
11
  say "Create application.js module as entrypoint"
12
- create_file Rails.root.join("app/assets/javascripts/application.js") do <<-JS
13
- // Configure your import map in config/initializers/assets.rb
12
+ create_file Rails.root.join("app/javascript/application.js") do <<-JS
13
+ // Configure your import map in config/initializers/importmap.rb
14
14
 
15
15
  // import "@rails/actioncable"
16
16
  // import "@rails/activestorage"
@@ -18,26 +18,20 @@ JS
18
18
  end
19
19
 
20
20
  say "Ensure JavaScript files are in the asset pipeline manifest"
21
- append_to_file Rails.root.join("app/assets/config/manifest.js"), %(//= link_tree ../javascripts .js\n)
21
+ append_to_file Rails.root.join("app/assets/config/manifest.js"), %(//= link_tree ../../javascript .js\n)
22
22
 
23
- say "Configure importmap paths in config/initializers/assets.rb"
24
- append_to_file Rails.root.join("config/initializers/assets.rb") do <<-RUBY
25
-
26
- # Configure import map to be used for ESM
23
+ say "Configure importmap paths in config/initializers/importmap.rb"
24
+ create_file Rails.root.join("config/initializers/importmap.rb") do <<-RUBY
27
25
  Rails.application.config.importmap.draw do
28
- # All JavaScript files in the tree are mapped to their name
29
- pin_all_from "app/assets/javascripts"
26
+ pin "application"
30
27
 
31
- # Match libraries with their NPM package names for possibility of later porting.
32
- # Ensure that libraries listed in the path have been linked in the asset pipeline manifest or precompiled.
33
- pin "@rails/actioncable", to: "actioncable.esm.js"
34
- pin "@rails/activestorage", to: "activestorage.esm.js"
35
- pin "@rails/actiontext", to: "actiontext.js"
36
- pin "trix", to: "trix.js"
28
+ # Use libraries available via the asset pipeline (locally or via gems).
29
+ # pin "@rails/actioncable", to: "actioncable.esm.js"
30
+ # pin "@rails/activestorage", to: "activestorage.esm.js"
37
31
 
38
- # Use libraries directly from JavaScript CDNs
32
+ # Use libraries directly from JavaScript CDNs (see https://www.skypack.dev, https://esm.sh, https://www.jsdelivr.com/esm)
39
33
  # pin "vue", to: "https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js"
40
- # pin "d3", to: "https://cdn.skypack.dev/pin/d3@v7.0.0-03vFl9bie0TSesDkWTJV/mode=imports/optimized/d3.js"
34
+ # pin "d3", to: "https://esm.sh/d3?bundle"
41
35
 
42
36
  # Pin vendored modules by first adding the following to app/assets/config/manifest.js:
43
37
  # //= link_tree ../../../vendor/assets/javascripts .js
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: importmap-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-14 00:00:00.000000000 Z
11
+ date: 2021-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails