importmap-rails 0.2.2 → 0.2.3

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: 5193a35b0581011cda86209dc8d5999fb9014b31f2bda311bbaffd1a2859dcff
4
- data.tar.gz: 2ca482f5d6d97214b30308b869439ba2e5c3a61759ae6da20ae424ad1c88d3c3
3
+ metadata.gz: 0ec141d17e7859eac9dae9d91c47f04957f650f2cef7eeae7cc0449b9c92cd89
4
+ data.tar.gz: a2ce05f0a9c340668d407f5c7e2cf64b2fe68f262e567ab94fb1ccc029b10487
5
5
  SHA512:
6
- metadata.gz: 5e8847248c55d406f8c2b49fa233711e1aa5c3d1de5119fba36f957f1723b1000740f2cf89010fef077e2693ed48e4b4234ba14e6b36330a67fa2291bec479a9
7
- data.tar.gz: b523ecac3ecc3b0dd3a232b2b3917e6c116c4a2dc8e5954af2c6d79fae19d4d635af0d763ecdba0a0cc12797dae6f907e09d025fe84ff97cc858c6ff5f1ad00f
6
+ metadata.gz: e692f3214ec9f899ebffd3f239a8f87202811b8098de1f2b2906bcb2558d446801a34fe359b3a94b8b8768220d54918f089ed8149ede3e04708d29f435314872
7
+ data.tar.gz: 89eafbbd709cd3e9eb2e97310c96297465bb935b713e841c8b8f7c9dd4a7d4f59c05b0d5720f0d9087dfbcb3a7e86d17c6d89a6c061622c9b8e6387719707795
data/README.md CHANGED
@@ -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.2"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -9,7 +9,7 @@ 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
12
+ create_file Rails.root.join("app/javascript/application.js") do <<-JS
13
13
  // Configure your import map in config/initializers/importmap.rb
14
14
 
15
15
  // import "@rails/actioncable"
@@ -18,25 +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
23
  say "Configure importmap paths in config/initializers/importmap.rb"
24
24
  create_file Rails.root.join("config/initializers/importmap.rb") do <<-RUBY
25
- # Configure import map to be used for ESM
26
25
  Rails.application.config.importmap.draw do
27
- # All JavaScript files in the tree are mapped to their name
28
- pin_all_from "app/assets/javascripts"
29
-
30
- # Match libraries with their NPM package names for possibility of later porting.
31
- # Ensure that libraries listed in the path have been linked in the asset pipeline manifest or precompiled.
32
- pin "@rails/actioncable", to: "actioncable.esm.js"
33
- pin "@rails/activestorage", to: "activestorage.esm.js"
34
- pin "@rails/actiontext", to: "actiontext.js"
35
- pin "trix", to: "trix.js"
36
-
37
- # Use libraries directly from JavaScript CDNs
38
- # pin "vue", to: "https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js"
39
- # pin "d3", to: "https://cdn.skypack.dev/pin/d3@v7.0.0-03vFl9bie0TSesDkWTJV/mode=imports/optimized/d3.js"
26
+ pin "application"
27
+
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"
31
+
32
+ # Use libraries directly from JavaScript CDNs (see https://www.skypack.dev, https://cdnjs.com, https://www.jsdelivr.com/esm)
33
+ # pin "vue", to: "https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js", preload: true
34
+ # pin "d3", to: "https://cdn.skypack.dev/pin/d3@v7.0.0-03vFl9bie0TSesDkWTJV/mode=imports/optimized/d3.js", preload: true
40
35
 
41
36
  # Pin vendored modules by first adding the following to app/assets/config/manifest.js:
42
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.2
4
+ version: 0.2.3
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