importmap-rails 0.1.3 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +35 -8
- data/app/assets/javascripts/es-module-shims@0.12.2.js +3 -0
- data/app/helpers/importmap/importmap_tags_helper.rb +17 -4
- data/lib/importmap/engine.rb +10 -4
- data/lib/importmap/map.rb +97 -0
- data/lib/importmap/version.rb +1 -1
- data/lib/install/install.rb +15 -18
- metadata +3 -3
- data/lib/importmap/paths.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ec141d17e7859eac9dae9d91c47f04957f650f2cef7eeae7cc0449b9c92cd89
|
4
|
+
data.tar.gz: a2ce05f0a9c340668d407f5c7e2cf64b2fe68f262e567ab94fb1ccc029b10487
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e692f3214ec9f899ebffd3f239a8f87202811b8098de1f2b2906bcb2558d446801a34fe359b3a94b8b8768220d54918f089ed8149ede3e04708d29f435314872
|
7
|
+
data.tar.gz: 89eafbbd709cd3e9eb2e97310c96297465bb935b713e841c8b8f7c9dd4a7d4f59c05b0d5720f0d9087dfbcb3a7e86d17c6d89a6c061622c9b8e6387719707795
|
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/
|
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
|
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/
|
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,12 +41,12 @@ 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/
|
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
|
-
Rails.application.config.importmap.
|
48
|
-
|
49
|
-
|
47
|
+
Rails.application.config.importmap.draw do
|
48
|
+
pin "trix", to: "https://cdn.skypack.dev/trix"
|
49
|
+
pin "md5", to: "https://cdn.skypack.dev/md5"
|
50
50
|
end
|
51
51
|
```
|
52
52
|
|
@@ -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
|
@@ -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 `
|
12
|
-
# By default, `Rails.application.config.importmap.
|
13
|
-
def javascript_inline_importmap_tag(
|
14
|
-
tag.script(
|
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
|
data/lib/importmap/engine.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require "importmap/
|
1
|
+
require "importmap/map"
|
2
2
|
|
3
3
|
module Importmap
|
4
4
|
class Engine < ::Rails::Engine
|
5
|
-
config.importmap =
|
6
|
-
config.importmap.paths = Importmap::Paths.new.tap { |paths| paths.assets_in "app/assets/javascripts" }
|
5
|
+
config.importmap = Importmap::Map.new
|
7
6
|
|
8
7
|
config.autoload_once_paths = %W( #{root}/app/helpers )
|
9
8
|
|
10
9
|
initializer "importmap.assets" do
|
11
10
|
if Rails.application.config.respond_to?(:assets)
|
12
|
-
Rails.application.config.assets.precompile += %w( es-module-shims )
|
11
|
+
Rails.application.config.assets.precompile += %w( es-module-shims.js )
|
12
|
+
Rails.application.config.assets.paths << Rails.root.join("app/javascript")
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -18,5 +18,11 @@ module Importmap
|
|
18
18
|
helper Importmap::ImportmapTagsHelper
|
19
19
|
end
|
20
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
|
21
27
|
end
|
22
28
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class Importmap::Map
|
2
|
+
attr_reader :files, :directories
|
3
|
+
attr_accessor :cached
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@files, @directories = {}, {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def draw(&block)
|
10
|
+
instance_eval(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pin(name, to: nil, preload: true)
|
14
|
+
@files[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
|
15
|
+
end
|
16
|
+
|
17
|
+
def pin_all_from(path, under: nil, preload: true)
|
18
|
+
@directories[path] = MappedDir.new(path: path, under: under, preload: preload)
|
19
|
+
end
|
20
|
+
|
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
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
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|
|
47
|
+
begin
|
48
|
+
resolver.asset_path(mapping.path)
|
49
|
+
rescue Sprockets::Rails::Helper::AssetNotFound
|
50
|
+
Rails.logger.warn "Importmap skipped missing path: #{mapping.path}"
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end.compact
|
54
|
+
end
|
55
|
+
|
56
|
+
def expanded_preloading_files_and_directories
|
57
|
+
expanded_files_and_directories.select { |name, mapping| mapping.preload }
|
58
|
+
end
|
59
|
+
|
60
|
+
def expanded_files_and_directories
|
61
|
+
@files.dup.tap { |expanded| expand_directories_into expanded }
|
62
|
+
end
|
63
|
+
|
64
|
+
def expand_directories_into(paths)
|
65
|
+
@directories.values.each do |mapping|
|
66
|
+
if (absolute_path = absolute_root_of(mapping.path)).exist?
|
67
|
+
find_javascript_files_in_tree(absolute_path).each do |filename|
|
68
|
+
module_filename = filename.relative_path_from(absolute_path)
|
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
|
71
|
+
|
72
|
+
paths[module_name] = MappedFile.new(name: module_name, path: module_path, preload: mapping.preload)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Strip off the extension, /index, or any versioning data for an absolute module name.
|
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
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_javascript_files_in_tree(path)
|
91
|
+
Dir[path.join("**/*.js{,m}")].collect { |file| Pathname.new(file) }.select(&:file?)
|
92
|
+
end
|
93
|
+
|
94
|
+
def absolute_root_of(path)
|
95
|
+
(pathname = Pathname.new(path)).absolute? ? pathname : Rails.root.join(path)
|
96
|
+
end
|
97
|
+
end
|
data/lib/importmap/version.rb
CHANGED
data/lib/install/install.rb
CHANGED
@@ -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/
|
13
|
-
// Configure your import map in config/initializers/
|
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,27 +18,24 @@ 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
|
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/
|
24
|
-
|
23
|
+
say "Configure importmap paths in config/initializers/importmap.rb"
|
24
|
+
create_file Rails.root.join("config/initializers/importmap.rb") do <<-RUBY
|
25
|
+
Rails.application.config.importmap.draw do
|
26
|
+
pin "application"
|
25
27
|
|
26
|
-
#
|
27
|
-
|
28
|
-
#
|
29
|
-
# Ensure that libraries listed in the path have been linked in the asset pipeline manifest or precompiled.
|
30
|
-
paths.asset "@rails/actioncable", path: "actioncable.esm.js"
|
31
|
-
paths.asset "@rails/activestorage", path: "activestorage.esm.js"
|
32
|
-
paths.asset "@rails/actiontext", path: "actiontext.js"
|
33
|
-
paths.asset "trix"
|
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"
|
34
31
|
|
35
|
-
# Use libraries directly from JavaScript CDNs
|
36
|
-
#
|
37
|
-
#
|
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
|
38
35
|
|
39
|
-
#
|
36
|
+
# Pin vendored modules by first adding the following to app/assets/config/manifest.js:
|
40
37
|
# //= link_tree ../../../vendor/assets/javascripts .js
|
41
|
-
#
|
38
|
+
# pin_all_from "vendor/assets/javascripts"
|
42
39
|
end
|
43
40
|
RUBY
|
44
41
|
end
|
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.
|
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-
|
11
|
+
date: 2021-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,7 +38,7 @@ files:
|
|
38
38
|
- app/helpers/importmap/importmap_tags_helper.rb
|
39
39
|
- lib/importmap-rails.rb
|
40
40
|
- lib/importmap/engine.rb
|
41
|
-
- lib/importmap/
|
41
|
+
- lib/importmap/map.rb
|
42
42
|
- lib/importmap/version.rb
|
43
43
|
- lib/install/install.rb
|
44
44
|
- lib/shim.js
|
data/lib/importmap/paths.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
class Importmap::Paths
|
2
|
-
attr_reader :files, :directories
|
3
|
-
|
4
|
-
def initialize
|
5
|
-
@files = {}
|
6
|
-
@directories = {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def asset(name, path: nil)
|
10
|
-
@files[name] = path || "#{name}.js"
|
11
|
-
end
|
12
|
-
|
13
|
-
def assets_in(path, append_base_path: false)
|
14
|
-
@directories[path] = append_base_path
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_json(resolver)
|
18
|
-
{ "imports" => map_to_asset_paths(resolver) }.to_json
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
def map_to_asset_paths(resolver)
|
23
|
-
expanded_files_and_directories.transform_values do |path|
|
24
|
-
begin
|
25
|
-
resolver.asset_path(path)
|
26
|
-
rescue Sprockets::Rails::Helper::AssetNotFound
|
27
|
-
Rails.logger.warn "Importmap skipped missing asset: #{path}"
|
28
|
-
nil
|
29
|
-
end
|
30
|
-
end.compact
|
31
|
-
end
|
32
|
-
|
33
|
-
def expanded_files_and_directories
|
34
|
-
@files.dup.tap { |expanded| expand_directories_into expanded }
|
35
|
-
end
|
36
|
-
|
37
|
-
def expand_directories_into(paths)
|
38
|
-
@directories.each do |(path, append_base_path)|
|
39
|
-
if (absolute_path = absolute_root_of(path)).exist?
|
40
|
-
find_javascript_files_in_tree(absolute_path).each do |filename|
|
41
|
-
module_filename = filename.relative_path_from(absolute_path)
|
42
|
-
module_name = module_name_from(module_filename)
|
43
|
-
module_path = append_base_path ? absolute_path.basename.join(module_filename).to_s : module_filename.to_s
|
44
|
-
|
45
|
-
paths[module_name] = module_path
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Strip off the extension, /index, or any versioning data for an absolute module name.
|
52
|
-
def module_name_from(filename)
|
53
|
-
filename.to_s.remove(filename.extname).remove("/index").split("@").first
|
54
|
-
end
|
55
|
-
|
56
|
-
def find_javascript_files_in_tree(path)
|
57
|
-
Dir[path.join("**/*.js{,m}")].collect { |file| Pathname.new(file) }.select(&:file?)
|
58
|
-
end
|
59
|
-
|
60
|
-
def absolute_root_of(path)
|
61
|
-
(pathname = Pathname.new(path)).absolute? ? pathname : Rails.root.join(path)
|
62
|
-
end
|
63
|
-
end
|