importmap-plus 2.0.2 → 2.1.0
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 +4 -4
- data/CHANGELOG.md +57 -1
- data/README.md +1 -1
- data/Rakefile +6 -1
- data/app/controllers/importmap/freshness.rb +2 -0
- data/app/helpers/importmap/importmap_tags_helper.rb +2 -0
- data/lib/importmap/batch_resolver.rb +2 -0
- data/lib/importmap/commands.rb +2 -0
- data/lib/importmap/doctor.rb +2 -0
- data/lib/importmap/early_hints.rb +2 -0
- data/lib/importmap/engine.rb +15 -0
- data/lib/importmap/esm_run.rb +11 -2
- data/lib/importmap/graph.rb +20 -11
- data/lib/importmap/http_retries.rb +5 -0
- data/lib/importmap/import_scanner.rb +2 -0
- data/lib/importmap/integrity.rb +2 -0
- data/lib/importmap/map.rb +97 -54
- data/lib/importmap/minifier.rb +6 -2
- data/lib/importmap/module_inspector.rb +2 -0
- data/lib/importmap/npm.rb +11 -1
- data/lib/importmap/package_graph.rb +2 -0
- data/lib/importmap/packager.rb +20 -5
- data/lib/importmap/pin_set.rb +77 -0
- data/lib/importmap/provider_chain.rb +2 -0
- data/lib/importmap/reloader.rb +2 -0
- data/lib/importmap/shareable.rb +68 -0
- data/lib/importmap/vendored_graph.rb +2 -0
- data/lib/importmap/version.rb +3 -1
- data/lib/importmap-plus.rb +2 -0
- data/lib/importmap-rails.rb +2 -0
- data/lib/install/install.rb +2 -0
- data/lib/tasks/importmap_tasks.rake +2 -0
- metadata +22 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79dc17549a50164758c2fe012056e4c64ca1290f09d5ef9a6c9c52b5df64b3c7
|
|
4
|
+
data.tar.gz: 3c4eddba8ee4c5c95a898dd5ebb2a50a2f34e4c7022d03cbbdb2d515c13a9589
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a84cb54c15c6bc465d3f2a5c3bf3f337a28601ea14f3d402b6894e1cf128173d14995ea0c0968bfb5851128f38b502003a902db25ab479e1ee75a5c521de17d
|
|
7
|
+
data.tar.gz: c0a778797572cba636a5c3cb90049393fb5ba5229cd8dc01222ed932cd840e664e9a448c312575cf3937e64148322fb12754c4dd3afee3b8cb9aa7aa6603bd39
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Every app's map is finalized once it has booted, in every environment.**
|
|
8
|
+
After initialization the engine calls the new `Importmap::Map#finalize!`:
|
|
9
|
+
every pin and directory is deeply frozen, Ractor-shareable on Ruby 3+ (but
|
|
10
|
+
for a pinned value no Ractor can share, below), and so is every set of pins
|
|
11
|
+
the map publishes after it. A development reload, the
|
|
12
|
+
cache sweeper, and a `pin` or `draw` after boot work as before, each
|
|
13
|
+
publishing a new, frozen set, so nothing that pins or reloads starts raising.
|
|
14
|
+
`Importmap::Map#finalized?` says whether a map has been. Outside the engine
|
|
15
|
+
(an `Importmap::Map.new` passed to the helpers) `finalize!` is yours to call.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Behaviour change: the pins are frozen once the app has booted.** Changing
|
|
20
|
+
one in place, such as `importmap.packages["md5"].preload = false`, raises
|
|
21
|
+
`FrozenError` in development and test as well as production. Change the map
|
|
22
|
+
with `pin`, `pin_all_from` and `draw`; an app that can't sets
|
|
23
|
+
`config.importmap.finalize = false`, which leaves its pins unfrozen.
|
|
24
|
+
- **A pinned value no Ractor can share is kept as it is, never raised on.** A
|
|
25
|
+
pin whose `to:` is an object holding a lock, a logger or a lambda over the
|
|
26
|
+
app's state stays the object the app gave, unfrozen, and works on the main
|
|
27
|
+
Ractor only; its pin and every other pin are still frozen. The same goes for
|
|
28
|
+
the class-level URIs below.
|
|
29
|
+
- **`Importmap::Map#packages` and `#directories` return frozen hashes**, the
|
|
30
|
+
current generation's (see the reload fix below). Change the map with `pin`,
|
|
31
|
+
`pin_all_from` and `draw`.
|
|
32
|
+
- **The class-level URIs are stored deeply frozen.** `Packager.endpoint`,
|
|
33
|
+
`Packager.esm_run_resolver` (`EsmRun.resolver`) and `Npm.base_uri` are set
|
|
34
|
+
once, in an initializer; the URI you assign is frozen in place, so no thread
|
|
35
|
+
can change it under another. The default minifier is looked for when
|
|
36
|
+
`importmap/packager` loads rather than on first use; one you set, a lambda
|
|
37
|
+
over your own state included, is kept as given, and `nil` puts the default
|
|
38
|
+
back.
|
|
39
|
+
- Every Ruby file in the gem has `# frozen_string_literal: true`, but for the
|
|
40
|
+
two install templates copied into an app (`bin/importmap` and
|
|
41
|
+
`config/importmap.rb`), which are the app's files.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **A request during a reload renders a whole map.** The reloader
|
|
46
|
+
re-evaluated `config/importmap.rb` into the map every request reads, one pin
|
|
47
|
+
at a time, so a request mid-reload could render half of the new map and half
|
|
48
|
+
of the old. Every change now builds the next pin set on a copy and swaps it
|
|
49
|
+
in whole, and a draw that raises leaves the map it started from. A reload
|
|
50
|
+
still adds to the map, as before.
|
|
51
|
+
|
|
52
|
+
- **A render no longer caches the map from before a change.** The rendered
|
|
53
|
+
JSON and preloads were memoised with an unlocked check-then-set that a pin,
|
|
54
|
+
`enable_integrity!` or the cache sweeper reset underneath: a render that
|
|
55
|
+
started first finished after the reset and cached the old map until the next
|
|
56
|
+
change. Each render is now cached in the pin set it read, under a lock, and
|
|
57
|
+
renders that miss together all get the one that was kept.
|
|
58
|
+
|
|
59
|
+
## 2.0.2
|
|
4
60
|
|
|
5
61
|
### Fixed
|
|
6
62
|
|
data/README.md
CHANGED
|
@@ -64,7 +64,7 @@ bundle exec rake test # the command tests talk to live CDNs (jspm, jsDe
|
|
|
64
64
|
cd docs && bin/dev # the docs site, a docs-kit app at docs/
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
Releases are cut with `bin/release
|
|
67
|
+
Releases are cut with `bin/release` (the zoolutions release kit), which bumps the version and lockfile pins, pushes `main` and publishes a GitHub Release; that fires `.github/workflows/release.yml` (RubyGems trusted publishing) and `.github/workflows/deploy-docs.yml` (the docs site).
|
|
68
68
|
|
|
69
69
|
## License
|
|
70
70
|
|
data/Rakefile
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "bundler/setup"
|
|
2
4
|
|
|
3
5
|
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
|
4
6
|
load "rails/tasks/engine.rake"
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
# No `bundler/gem_tasks`: its `release` task (tag + `gem push` from this machine)
|
|
9
|
+
# would merge into the release kit's `rake release[X.Y.Z]` (rakelib/release.rake)
|
|
10
|
+
# and publish with a local API key. Releases go through bin/release; the gem is
|
|
11
|
+
# built and pushed by release.yml over trusted publishing.
|
|
7
12
|
|
|
8
13
|
require "rake/testtask"
|
|
9
14
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Importmap::ImportmapTagsHelper
|
|
2
4
|
# Setup all script tags needed to use an importmap-powered entrypoint (which defaults to application.js)
|
|
3
5
|
def javascript_importmap_tags(entry_point = "application", importmap: Rails.application.importmap)
|
data/lib/importmap/commands.rb
CHANGED
data/lib/importmap/doctor.rb
CHANGED
data/lib/importmap/engine.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "importmap/map"
|
|
2
4
|
require "importmap/early_hints"
|
|
3
5
|
|
|
@@ -13,6 +15,7 @@ module Importmap
|
|
|
13
15
|
config.importmap.rescuable_asset_errors = []
|
|
14
16
|
config.importmap.preload_strategy = :all
|
|
15
17
|
config.importmap.early_hints = true
|
|
18
|
+
config.importmap.finalize = true
|
|
16
19
|
|
|
17
20
|
config.autoload_once_paths = %W( #{root}/app/helpers #{root}/app/controllers )
|
|
18
21
|
|
|
@@ -72,5 +75,17 @@ module Importmap
|
|
|
72
75
|
app.config.importmap.rescuable_asset_errors << Sprockets::Rails::Helper::AssetNotFound
|
|
73
76
|
end
|
|
74
77
|
end
|
|
78
|
+
|
|
79
|
+
# Once the app has booted, in every environment, its map is finalized:
|
|
80
|
+
# every pin is deeply frozen (Ractor-shareable), and so is every set of
|
|
81
|
+
# pins a development reload, a cache sweep or a later pin publishes, so
|
|
82
|
+
# reloading works as before. Registered from an initializer, so it runs
|
|
83
|
+
# after the after_initialize blocks of config/application.rb and the
|
|
84
|
+
# environment files.
|
|
85
|
+
initializer "importmap.finalize" do |app|
|
|
86
|
+
app.config.after_initialize do
|
|
87
|
+
app.importmap.finalize! if app.config.importmap.finalize
|
|
88
|
+
end
|
|
89
|
+
end
|
|
75
90
|
end
|
|
76
91
|
end
|
data/lib/importmap/esm_run.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "uri"
|
|
2
4
|
require "json"
|
|
5
|
+
require "importmap/shareable"
|
|
3
6
|
|
|
4
7
|
# Everything this gem knows about jsDelivr's bundling endpoint
|
|
5
8
|
# (https://www.jsdelivr.com/esm): one minified ESM file per package, with its
|
|
@@ -32,8 +35,14 @@ class Importmap::EsmRun
|
|
|
32
35
|
|
|
33
36
|
# The jsDelivr data API versions are resolved through. Also readable and
|
|
34
37
|
# writable as Importmap::Packager.esm_run_resolver, which is where an app
|
|
35
|
-
# that points this at a mirror has always set it.
|
|
36
|
-
singleton_class.
|
|
38
|
+
# that points this at a mirror has always set it, in an initializer.
|
|
39
|
+
singleton_class.attr_reader :resolver
|
|
40
|
+
|
|
41
|
+
def self.resolver=(value)
|
|
42
|
+
# A boot-time setting, written before the commands that read it, and
|
|
43
|
+
# stored deeply frozen (Ractor-shareable), so none can change it in place.
|
|
44
|
+
@resolver = Importmap::Shareable.make(value) # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
45
|
+
end
|
|
37
46
|
self.resolver = URI("https://data.jsdelivr.com/v1/packages/npm/")
|
|
38
47
|
|
|
39
48
|
class << self
|
data/lib/importmap/graph.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "pathname"
|
|
2
4
|
require "set"
|
|
3
5
|
require "importmap/import_scanner"
|
|
@@ -24,9 +26,16 @@ class Importmap::Graph
|
|
|
24
26
|
# requiring the CLI onto the request path to borrow it would be a poor trade.
|
|
25
27
|
REMOTE_PATH_REGEXP = %r{\A(?:[a-zA-Z][a-zA-Z0-9+\-.]*:)?//}.freeze # :nodoc:
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
# +entries+ are the expanded pins to walk when given (Importmap::Map passes
|
|
30
|
+
# those of the generation it is rendering), +map+'s otherwise. One graph
|
|
31
|
+
# serves every render of a generation, so its file reads are memoised under
|
|
32
|
+
# a lock.
|
|
33
|
+
def initialize(map, roots: [], entries: nil)
|
|
34
|
+
@roots = Array(roots).map { |root| Pathname.new(root) }
|
|
35
|
+
@entries = (entries || map.each_expanded_package).to_h
|
|
36
|
+
@keys_by_path = @entries.each_with_object({}) { |(key, mapping), keys| keys[mapping.path] ||= key }
|
|
37
|
+
@edges = {}
|
|
38
|
+
@lock = Mutex.new
|
|
30
39
|
end
|
|
31
40
|
|
|
32
41
|
# The keys reachable from +entry_points+ through static imports, the entry
|
|
@@ -45,21 +54,21 @@ class Importmap::Graph
|
|
|
45
54
|
end
|
|
46
55
|
|
|
47
56
|
private
|
|
48
|
-
|
|
49
|
-
@entries ||= @map.each_expanded_package.to_h
|
|
50
|
-
end
|
|
57
|
+
attr_reader :entries
|
|
51
58
|
|
|
52
59
|
# The key a path belongs to, for resolving a relative import back into the
|
|
53
60
|
# map. Two keys can name one file; the first one drawn wins, and since both
|
|
54
61
|
# resolve to the same asset path the preload set can't tell them apart
|
|
55
62
|
# anyway.
|
|
56
|
-
|
|
57
|
-
@keys_by_path ||= entries.each_with_object({}) { |(key, mapping), keys| keys[mapping.path] ||= key }
|
|
58
|
-
end
|
|
63
|
+
attr_reader :keys_by_path
|
|
59
64
|
|
|
65
|
+
# The file is read outside the lock; the first render to store its edges
|
|
66
|
+
# wins.
|
|
60
67
|
def edges_from(key)
|
|
61
|
-
@edges
|
|
62
|
-
|
|
68
|
+
@lock.synchronize { @edges[key] } || begin
|
|
69
|
+
edges = compute_edges(entries[key])
|
|
70
|
+
@lock.synchronize { @edges[key] ||= edges }
|
|
71
|
+
end
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
def compute_edges(mapping)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "net/http"
|
|
2
4
|
require "openssl"
|
|
3
5
|
|
|
@@ -9,6 +11,9 @@ module Importmap::HttpRetries
|
|
|
9
11
|
RETRYABLE_ERRORS = [ SocketError, SystemCallError, Timeout::Error, EOFError, OpenSSL::SSL::SSLError, Net::ProtocolError ].freeze # :nodoc:
|
|
10
12
|
RETRYABLE_CODES = %w[ 429 500 502 503 504 ].freeze # :nodoc:
|
|
11
13
|
|
|
14
|
+
# Boot-time settings, also written as Packager.retry_attempts and
|
|
15
|
+
# retry_wait. The defaults are an Integer and a Float, so shareable; a
|
|
16
|
+
# value an app writes is kept as given.
|
|
12
17
|
singleton_class.attr_accessor :attempts, :wait
|
|
13
18
|
self.attempts = 3
|
|
14
19
|
self.wait = 0.5
|
data/lib/importmap/integrity.rb
CHANGED
data/lib/importmap/map.rb
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "pathname"
|
|
2
4
|
require "importmap/graph"
|
|
5
|
+
require "importmap/pin_set"
|
|
3
6
|
|
|
4
7
|
class Importmap::Map
|
|
5
|
-
|
|
8
|
+
# The current generation's, frozen: change the map through #pin,
|
|
9
|
+
# #pin_all_from and #draw.
|
|
10
|
+
def packages = @pins.packages
|
|
11
|
+
def directories = @pins.directories
|
|
6
12
|
|
|
7
13
|
PIN_REGEX = /^pin\s+["']([^"']+)["']/.freeze # :nodoc:
|
|
8
14
|
|
|
@@ -13,21 +19,25 @@ class Importmap::Map
|
|
|
13
19
|
class InvalidFile < StandardError; end
|
|
14
20
|
|
|
15
21
|
def initialize
|
|
16
|
-
@
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
@pins = Importmap::PinSet.new
|
|
23
|
+
# Serialises the writers (draws, pins, sweeps), each of which builds the
|
|
24
|
+
# next Importmap::PinSet on a draft. A render never takes it, so no
|
|
25
|
+
# request waits while config/importmap.rb is re-evaluated.
|
|
26
|
+
@writer = Mutex.new
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def draw(path = nil, &block)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
drafting do
|
|
31
|
+
if path && File.exist?(path)
|
|
32
|
+
begin
|
|
33
|
+
instance_eval(File.read(path), path.to_s)
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
Rails.logger.error "Unable to parse import map from #{path}: #{e.message}"
|
|
36
|
+
raise InvalidFile, "Unable to parse import map from #{path}: #{e.message}"
|
|
37
|
+
end
|
|
38
|
+
elsif block_given?
|
|
39
|
+
instance_eval(&block)
|
|
28
40
|
end
|
|
29
|
-
elsif block_given?
|
|
30
|
-
instance_eval(&block)
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
self
|
|
@@ -66,20 +76,32 @@ class Importmap::Map
|
|
|
66
76
|
# * For Propshaft, you must configure +config.assets.integrity_hash_algorithm+
|
|
67
77
|
# * External CDN packages should provide their own integrity hashes
|
|
68
78
|
def enable_integrity!
|
|
69
|
-
|
|
70
|
-
@integrity = true
|
|
79
|
+
drafting { |draft| draft.integrity = true }
|
|
71
80
|
end
|
|
72
81
|
|
|
73
82
|
def pin(name, to: nil, preload: true, integrity: true)
|
|
74
|
-
|
|
75
|
-
@packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload, integrity: integrity)
|
|
83
|
+
drafting { |draft| draft.packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload, integrity: integrity) }
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
def pin_all_from(dir, under: nil, to: nil, preload: true, integrity: true)
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
drafting { |draft| draft.directories[dir] = MappedDir.new(dir: dir, under: under, path: to, preload: preload, integrity: integrity) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Deep-freezes every pin and directory, Ractor-shareable where Ruby has
|
|
91
|
+
# Ractors, and every set of them the map publishes from now on: the engine
|
|
92
|
+
# calls it once the app has booted (config.importmap.finalize). The map
|
|
93
|
+
# still changes as before: a draw, a pin, a development reload or a cache
|
|
94
|
+
# sweep publishes a new, finalized set, so no reader is ever handed a pin
|
|
95
|
+
# another thread can change. A value the app pinned that no Ractor can
|
|
96
|
+
# share is left as it is (see Importmap::Shareable). Returns the map.
|
|
97
|
+
def finalize!
|
|
98
|
+
@writer.synchronize { @pins = @pins.finalized }
|
|
99
|
+
self
|
|
81
100
|
end
|
|
82
101
|
|
|
102
|
+
# Whether #finalize! has been called.
|
|
103
|
+
def finalized? = @pins.finalized?
|
|
104
|
+
|
|
83
105
|
# Returns an array of all the resolved module paths of the pinned packages. The `resolver` must respond to
|
|
84
106
|
# `path_to_asset`, such as `ActionController::Base.helpers` or `ApplicationController.helpers`. You'll want to use the
|
|
85
107
|
# resolver that has been configured for the `asset_host` you want these resolved paths to use. In case you need to
|
|
@@ -136,12 +158,12 @@ class Importmap::Map
|
|
|
136
158
|
# # Use a custom cache key for different asset hosts
|
|
137
159
|
# packages = importmap.preloaded_module_packages(resolver: helpers, cache_key: "cdn_host")
|
|
138
160
|
def preloaded_module_packages(resolver:, entry_point: "application", cache_key: :preloaded_module_packages)
|
|
139
|
-
cache_as(cache_key) do
|
|
140
|
-
expanded_preloading_packages_and_directories(entry_point:).filter_map do |_, package|
|
|
161
|
+
cache_as(cache_key) do |pins|
|
|
162
|
+
expanded_preloading_packages_and_directories(pins, entry_point:).filter_map do |_, package|
|
|
141
163
|
resolved_path = resolve_asset_path(package.path, resolver: resolver)
|
|
142
164
|
next unless resolved_path
|
|
143
165
|
|
|
144
|
-
resolved_integrity = resolve_integrity_value(package.integrity, package.path, resolver: resolver)
|
|
166
|
+
resolved_integrity = resolve_integrity_value(pins, package.integrity, package.path, resolver: resolver)
|
|
145
167
|
|
|
146
168
|
package = MappedFile.new(
|
|
147
169
|
name: package.name,
|
|
@@ -161,9 +183,9 @@ class Importmap::Map
|
|
|
161
183
|
# want these resolved paths to use. In case you need to resolve for different asset hosts, you can pass in a custom
|
|
162
184
|
# `cache_key` to vary the cache used by this method for the different cases.
|
|
163
185
|
def to_json(resolver:, cache_key: :json)
|
|
164
|
-
cache_as(cache_key) do
|
|
165
|
-
packages = expanded_packages_and_directories
|
|
166
|
-
map = build_import_map(packages, resolver: resolver)
|
|
186
|
+
cache_as(cache_key) do |pins|
|
|
187
|
+
packages = expanded_packages_and_directories(pins)
|
|
188
|
+
map = build_import_map(pins, packages, resolver: resolver)
|
|
167
189
|
JSON.pretty_generate(map)
|
|
168
190
|
end
|
|
169
191
|
end
|
|
@@ -199,24 +221,44 @@ class Importmap::Map
|
|
|
199
221
|
# #to_json is about to resolve, before a resolver has touched it. Returns an
|
|
200
222
|
# Enumerator without a block.
|
|
201
223
|
def each_expanded_package(&block)
|
|
202
|
-
expanded_packages_and_directories.each(&block)
|
|
224
|
+
expanded_packages_and_directories(@pins).each(&block)
|
|
203
225
|
end
|
|
204
226
|
|
|
205
227
|
private
|
|
206
228
|
MappedDir = Struct.new(:dir, :path, :under, :preload, :integrity, keyword_init: true)
|
|
207
229
|
MappedFile = Struct.new(:name, :path, :preload, :integrity, keyword_init: true)
|
|
208
230
|
|
|
231
|
+
# Memoised in the generation it renders, which the block is handed: a
|
|
232
|
+
# render reads one Importmap::PinSet start to finish, so a draw landing
|
|
233
|
+
# mid-render can neither mix two versions of the map into it nor have it
|
|
234
|
+
# cached over the newer one.
|
|
209
235
|
def cache_as(name)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
else
|
|
213
|
-
@cache[name.to_s] = yield
|
|
214
|
-
end
|
|
236
|
+
pins = @pins
|
|
237
|
+
pins.fetch(name.to_s) { yield pins }
|
|
215
238
|
end
|
|
216
239
|
|
|
240
|
+
# The same pins as a new generation, so every render is redone. Inside a
|
|
241
|
+
# draw there is nothing to do: the draw publishes a new generation itself.
|
|
217
242
|
def clear_cache
|
|
218
|
-
@
|
|
219
|
-
|
|
243
|
+
return if @writer.owned?
|
|
244
|
+
|
|
245
|
+
@writer.synchronize { @pins = @pins.recached }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Every change is written into a draft of the current generation and the
|
|
249
|
+
# finished generation published whole. A pin inside a draw lands in that
|
|
250
|
+
# draw's draft; a draw that raises publishes nothing.
|
|
251
|
+
def drafting
|
|
252
|
+
return yield @draft if @writer.owned?
|
|
253
|
+
|
|
254
|
+
@writer.synchronize do
|
|
255
|
+
@draft = @pins.draft
|
|
256
|
+
result = yield @draft
|
|
257
|
+
@pins = @draft.to_pin_set
|
|
258
|
+
result
|
|
259
|
+
ensure
|
|
260
|
+
@draft = nil
|
|
261
|
+
end
|
|
220
262
|
end
|
|
221
263
|
|
|
222
264
|
def rescuable_asset_error?(error)
|
|
@@ -242,29 +284,29 @@ class Importmap::Map
|
|
|
242
284
|
end
|
|
243
285
|
end
|
|
244
286
|
|
|
245
|
-
def build_import_map(packages, resolver:)
|
|
287
|
+
def build_import_map(pins, packages, resolver:)
|
|
246
288
|
map = { "imports" => resolve_asset_paths(packages, resolver: resolver) }
|
|
247
|
-
integrity = build_integrity_hash(packages, resolver: resolver)
|
|
289
|
+
integrity = build_integrity_hash(pins, packages, resolver: resolver)
|
|
248
290
|
map["integrity"] = integrity unless integrity.empty?
|
|
249
291
|
map
|
|
250
292
|
end
|
|
251
293
|
|
|
252
|
-
def build_integrity_hash(packages, resolver:)
|
|
294
|
+
def build_integrity_hash(pins, packages, resolver:)
|
|
253
295
|
packages.filter_map do |name, mapping|
|
|
254
296
|
next unless mapping.integrity
|
|
255
297
|
|
|
256
298
|
resolved_path = resolve_asset_path(mapping.path, resolver: resolver)
|
|
257
299
|
next unless resolved_path
|
|
258
300
|
|
|
259
|
-
integrity_value = resolve_integrity_value(mapping.integrity, mapping.path, resolver: resolver)
|
|
301
|
+
integrity_value = resolve_integrity_value(pins, mapping.integrity, mapping.path, resolver: resolver)
|
|
260
302
|
next unless integrity_value
|
|
261
303
|
|
|
262
304
|
[resolved_path, integrity_value]
|
|
263
305
|
end.to_h
|
|
264
306
|
end
|
|
265
307
|
|
|
266
|
-
def resolve_integrity_value(integrity, path, resolver:)
|
|
267
|
-
return unless
|
|
308
|
+
def resolve_integrity_value(pins, integrity, path, resolver:)
|
|
309
|
+
return unless pins.integrity
|
|
268
310
|
|
|
269
311
|
case integrity
|
|
270
312
|
when true
|
|
@@ -274,9 +316,9 @@ class Importmap::Map
|
|
|
274
316
|
end
|
|
275
317
|
end
|
|
276
318
|
|
|
277
|
-
def expanded_preloading_packages_and_directories(entry_point:)
|
|
278
|
-
preloading = expanded_packages_and_directories.select { |name, mapping| mapping.preload.in?([true, false]) ? mapping.preload : (Array(mapping.preload) & Array(entry_point)).any? }
|
|
279
|
-
reachable_only(preloading, entry_point: entry_point)
|
|
319
|
+
def expanded_preloading_packages_and_directories(pins, entry_point:)
|
|
320
|
+
preloading = expanded_packages_and_directories(pins).select { |name, mapping| mapping.preload.in?([true, false]) ? mapping.preload : (Array(mapping.preload) & Array(entry_point)).any? }
|
|
321
|
+
reachable_only(pins, preloading, entry_point: entry_point)
|
|
280
322
|
end
|
|
281
323
|
|
|
282
324
|
# With config.importmap.preload_strategy == :reachable, a pin that says
|
|
@@ -285,32 +327,33 @@ class Importmap::Map
|
|
|
285
327
|
# page without anyone maintaining a `preload: false` for it and for
|
|
286
328
|
# everything it depends on. A pin naming the entry point is the app
|
|
287
329
|
# overruling the graph, and `preload: false` is off either way.
|
|
288
|
-
def reachable_only(packages, entry_point:)
|
|
330
|
+
def reachable_only(pins, packages, entry_point:)
|
|
289
331
|
return packages unless Rails.application.config.importmap.preload_strategy == :reachable
|
|
290
332
|
|
|
291
|
-
reachable = graph.reachable_from(Array(entry_point))
|
|
333
|
+
reachable = graph(pins).reachable_from(Array(entry_point))
|
|
292
334
|
packages.select { |name, mapping| mapping.preload != true || reachable.include?(name) }
|
|
293
335
|
end
|
|
294
336
|
|
|
295
|
-
# Memoised
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
# read this back as its preload set.
|
|
299
|
-
#
|
|
300
|
-
|
|
301
|
-
|
|
337
|
+
# Memoised in the generation beside its renders, under a Symbol: every
|
|
338
|
+
# #cache_as key is a String, and the preload helper passes the entry
|
|
339
|
+
# point's own name as one, so an app with an entry point named "graph"
|
|
340
|
+
# can't read this back as its preload set. Redone with the rest of the
|
|
341
|
+
# generation's cache when a draw, or the sweeper seeing a .js file under a
|
|
342
|
+
# watched directory change, publishes a new one.
|
|
343
|
+
def graph(pins)
|
|
344
|
+
pins.fetch(:graph) { Importmap::Graph.new(self, roots: asset_paths, entries: expanded_packages_and_directories(pins)) }
|
|
302
345
|
end
|
|
303
346
|
|
|
304
347
|
def asset_paths
|
|
305
348
|
(config = Rails.application.config).respond_to?(:assets) ? config.assets.paths : []
|
|
306
349
|
end
|
|
307
350
|
|
|
308
|
-
def expanded_packages_and_directories
|
|
309
|
-
|
|
351
|
+
def expanded_packages_and_directories(pins)
|
|
352
|
+
pins.packages.dup.tap { |expanded| expand_directories_into expanded, pins.directories }
|
|
310
353
|
end
|
|
311
354
|
|
|
312
|
-
def expand_directories_into(paths)
|
|
313
|
-
|
|
355
|
+
def expand_directories_into(paths, directories)
|
|
356
|
+
directories.values.each do |mapping|
|
|
314
357
|
if (absolute_path = absolute_root_of(mapping.dir)).exist?
|
|
315
358
|
find_javascript_files_in_tree(absolute_path).each do |filename|
|
|
316
359
|
module_filename = filename.relative_path_from(absolute_path)
|
data/lib/importmap/minifier.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "open3"
|
|
2
4
|
require "tmpdir"
|
|
5
|
+
require "importmap/shareable"
|
|
3
6
|
|
|
4
7
|
# Minifies a vendored package with whichever JavaScript minifier is installed:
|
|
5
8
|
# bun, esbuild or terser, looked up in node_modules/.bin and then on PATH.
|
|
@@ -8,11 +11,12 @@ require "tmpdir"
|
|
|
8
11
|
class Importmap::Minifier
|
|
9
12
|
Error = Class.new(StandardError)
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
# Shareable: the lambdas capture nothing, and their self is this class.
|
|
15
|
+
TOOLS = Importmap::Shareable.make({
|
|
12
16
|
"bun" => ->(input, output) { [ "build", "--no-bundle", "--minify", "--format=esm", "--target=browser", input, "--outfile=#{output}" ] },
|
|
13
17
|
"esbuild" => ->(input, output) { [ input, "--minify", "--format=esm", "--outfile=#{output}" ] },
|
|
14
18
|
"terser" => ->(input, output) { [ input, "--module", "--compress", "--mangle", "--output", output ] }
|
|
15
|
-
}
|
|
19
|
+
}) # :nodoc:
|
|
16
20
|
|
|
17
21
|
class << self
|
|
18
22
|
def detect
|
data/lib/importmap/npm.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "net/http"
|
|
2
4
|
require "uri"
|
|
3
5
|
require "json"
|
|
4
6
|
require "importmap/http_retries"
|
|
7
|
+
require "importmap/shareable"
|
|
5
8
|
|
|
6
9
|
class Importmap::Npm
|
|
7
10
|
include Importmap::HttpRetries
|
|
@@ -11,7 +14,14 @@ class Importmap::Npm
|
|
|
11
14
|
Error = Class.new(StandardError)
|
|
12
15
|
HTTPError = Class.new(Error)
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
# The registry, or a mirror of it set in an initializer.
|
|
18
|
+
singleton_class.attr_reader :base_uri
|
|
19
|
+
|
|
20
|
+
def self.base_uri=(value)
|
|
21
|
+
# A boot-time setting, written before the commands that read it, and
|
|
22
|
+
# stored deeply frozen (Ractor-shareable), so none can change it in place.
|
|
23
|
+
@base_uri = Importmap::Shareable.make(value) # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
24
|
+
end
|
|
15
25
|
self.base_uri = URI("https://registry.npmjs.org")
|
|
16
26
|
|
|
17
27
|
def initialize(importmap_path = "config/importmap.rb", vendor_path: "vendor/javascript")
|
data/lib/importmap/packager.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "net/http"
|
|
2
4
|
require "uri"
|
|
3
5
|
require "json"
|
|
@@ -8,6 +10,7 @@ require "importmap/vendored_graph"
|
|
|
8
10
|
require "importmap/http_retries"
|
|
9
11
|
require "importmap/integrity"
|
|
10
12
|
require "importmap/esm_run"
|
|
13
|
+
require "importmap/shareable"
|
|
11
14
|
|
|
12
15
|
class Importmap::Packager
|
|
13
16
|
include Importmap::HttpRetries
|
|
@@ -132,7 +135,14 @@ class Importmap::Packager
|
|
|
132
135
|
end
|
|
133
136
|
end
|
|
134
137
|
|
|
135
|
-
|
|
138
|
+
# jspm's generator, or another one set in an initializer.
|
|
139
|
+
singleton_class.attr_reader :endpoint
|
|
140
|
+
|
|
141
|
+
def self.endpoint=(value)
|
|
142
|
+
# A boot-time setting, written before the commands that read it, and
|
|
143
|
+
# stored deeply frozen (Ractor-shareable), so none can change it in place.
|
|
144
|
+
@endpoint = Importmap::Shareable.make(value) # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
145
|
+
end
|
|
136
146
|
self.endpoint = URI("https://api.jspm.io/generate")
|
|
137
147
|
|
|
138
148
|
# CDNs reset connections and rate-limit bursts. Each request is tried this
|
|
@@ -158,12 +168,17 @@ class Importmap::Packager
|
|
|
158
168
|
end
|
|
159
169
|
|
|
160
170
|
# Anything responding to #call(source) => String. Defaults to the first of
|
|
161
|
-
# bun, esbuild or terser found on the machine
|
|
162
|
-
|
|
171
|
+
# bun, esbuild or terser found on the machine, looked for when this file
|
|
172
|
+
# loads, and stored shareable; assigning nil puts that default back. A
|
|
173
|
+
# minifier an app sets is used as given: a callable is seldom shareable, and
|
|
174
|
+
# only the CLI's own thread calls it.
|
|
175
|
+
singleton_class.attr_reader :minifier
|
|
163
176
|
|
|
164
|
-
def self.minifier
|
|
165
|
-
|
|
177
|
+
def self.minifier=(value)
|
|
178
|
+
# A boot-time setting, written before the commands that read it.
|
|
179
|
+
@minifier = value || Importmap::Shareable.make(Importmap::Minifier.new) # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
166
180
|
end
|
|
181
|
+
self.minifier = nil
|
|
167
182
|
|
|
168
183
|
attr_reader :vendor_path
|
|
169
184
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "importmap/shareable"
|
|
4
|
+
|
|
5
|
+
# One generation of an Importmap::Map: its pins, its pin_all_from directories
|
|
6
|
+
# and whether integrity is on, frozen together, plus the cache of what was
|
|
7
|
+
# rendered from them.
|
|
8
|
+
#
|
|
9
|
+
# The map never changes a generation in place. A draw, a pin or a cache sweep
|
|
10
|
+
# writes into a Draft of the current one and the map swaps the finished
|
|
11
|
+
# generation in whole, so a request rendering mid-reload renders the
|
|
12
|
+
# generation before it rather than a half-drawn map. What a render caches
|
|
13
|
+
# lands in the generation it read: a swap leaves the old cache behind, where
|
|
14
|
+
# clearing one shared cache would let a render that started before the change
|
|
15
|
+
# write the old map back into it.
|
|
16
|
+
class Importmap::PinSet
|
|
17
|
+
# A generation being drawn: the same things, writable, for the one writer
|
|
18
|
+
# holding the map's lock. The generation it publishes is finalized when the
|
|
19
|
+
# one it was drafted from was.
|
|
20
|
+
Draft = Struct.new(:packages, :directories, :integrity, :finalized) do
|
|
21
|
+
def to_pin_set = Importmap::PinSet.new(packages: packages, directories: directories, integrity: integrity, finalized: finalized)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :packages, :directories, :integrity
|
|
25
|
+
|
|
26
|
+
def initialize(packages: {}, directories: {}, integrity: false, finalized: false)
|
|
27
|
+
@packages = finalized ? deep_freeze(packages) : packages.freeze
|
|
28
|
+
@directories = finalized ? deep_freeze(directories) : directories.freeze
|
|
29
|
+
@integrity = integrity
|
|
30
|
+
@finalized = finalized
|
|
31
|
+
@lock = Mutex.new
|
|
32
|
+
@cache = {}
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Whether every pin and directory is deeply frozen: Ractor-shareable where
|
|
37
|
+
# Ruby has Ractors, but for a value an app gave that no Ractor can share.
|
|
38
|
+
def finalized? = @finalized
|
|
39
|
+
|
|
40
|
+
def draft
|
|
41
|
+
Draft.new(packages.dup, directories.dup, integrity, finalized?)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The same pins with an empty cache: what a sweep publishes when a watched
|
|
45
|
+
# .js file changes and every render has to be redone.
|
|
46
|
+
def recached
|
|
47
|
+
self.class.new(packages: packages, directories: directories, integrity: integrity, finalized: finalized?)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The same pins deeply frozen, as a new generation with an empty cache.
|
|
51
|
+
# Every generation drafted from it is finalized in turn.
|
|
52
|
+
def finalized
|
|
53
|
+
finalized? ? self : self.class.new(packages: packages, directories: directories, integrity: integrity, finalized: true)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The value cached under +key+, else the block's. The block runs outside the
|
|
57
|
+
# lock, because it calls the app's asset resolver, which may take locks of
|
|
58
|
+
# its own; when two renders miss at once the first one stored is kept and
|
|
59
|
+
# both return it.
|
|
60
|
+
def fetch(key)
|
|
61
|
+
cached = @lock.synchronize { @cache[key] }
|
|
62
|
+
return cached if cached
|
|
63
|
+
|
|
64
|
+
rendered = yield
|
|
65
|
+
@lock.synchronize { @cache[key] ||= rendered }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
# Each pin on its own, so one holding a value no Ractor can share (see
|
|
70
|
+
# Importmap::Shareable) leaves only itself unshared. The pins are the
|
|
71
|
+
# map's own structs: one that can't be shared is still frozen, around the
|
|
72
|
+
# value the app gave it.
|
|
73
|
+
def deep_freeze(entries)
|
|
74
|
+
entries.each_value { |entry| Importmap::Shareable.make(entry).freeze }
|
|
75
|
+
Importmap::Shareable.make(entries.freeze)
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/importmap/reloader.rb
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Deep-freezes a value set once at boot and read by every thread after it, so
|
|
4
|
+
# no reader can be handed something another thread is still changing:
|
|
5
|
+
# Ractor.make_shareable where Ruby has Ractors (CRuby 3.0+), which also lets
|
|
6
|
+
# a non-main Ractor read it. Where it hasn't (JRuby, TruffleRuby) the value
|
|
7
|
+
# and the hashes, arrays, structs and strings inside it are frozen. Freezes in
|
|
8
|
+
# place and returns the value.
|
|
9
|
+
#
|
|
10
|
+
# Never raises. A value no Ractor can share (a lambda that closes over the
|
|
11
|
+
# app's state, a logger, anything holding a Mutex or an IO) is returned as it
|
|
12
|
+
# is, and keeps working on the main Ractor only. A Hash, Array or Struct
|
|
13
|
+
# around a Proc that can't be shared is frozen around it; the Proc is not.
|
|
14
|
+
module Importmap::Shareable
|
|
15
|
+
def self.make(value)
|
|
16
|
+
return freeze_all(value) unless ractors?
|
|
17
|
+
return value if ::Ractor.shareable?(value)
|
|
18
|
+
return value unless freezable?(value)
|
|
19
|
+
|
|
20
|
+
::Ractor.make_shareable(value)
|
|
21
|
+
rescue ::Ractor::Error
|
|
22
|
+
value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.ractors? = defined?(::Ractor) ? true : false
|
|
26
|
+
|
|
27
|
+
# Whether Ractor.make_shareable can run on +value+ in place without giving
|
|
28
|
+
# up half-way, which would leave it half-frozen (a logger, frozen around the
|
|
29
|
+
# Monitor it can't share). Everything but a Proc is tried on a copy first,
|
|
30
|
+
# which raises without touching the original. A Proc can't be copied, but
|
|
31
|
+
# make_shareable checks a Proc before it freezes it, so it needs no dry run;
|
|
32
|
+
# one inside any other object is never tried. +seen+ holds the hashes,
|
|
33
|
+
# arrays and structs already being checked, so one that holds itself ends.
|
|
34
|
+
def self.freezable?(value, seen = {}.compare_by_identity)
|
|
35
|
+
case value
|
|
36
|
+
when Hash, Array, Struct
|
|
37
|
+
return true if seen.key?(value)
|
|
38
|
+
|
|
39
|
+
seen[value] = true
|
|
40
|
+
value.is_a?(Hash) ? value.all? { |key, item| freezable?(key, seen) && freezable?(item, seen) } : value.all? { |item| freezable?(item, seen) }
|
|
41
|
+
when Proc then true
|
|
42
|
+
else
|
|
43
|
+
::Ractor.make_shareable(value, copy: true) unless ::Ractor.shareable?(value)
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
rescue ::Ractor::Error, TypeError
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Without Ractors: the value itself, and the hashes, arrays, structs and
|
|
51
|
+
# strings inside it. Any other object inside it is left as it is.
|
|
52
|
+
# +seen+ as in freezable?.
|
|
53
|
+
def self.freeze_all(value, seen = {}.compare_by_identity)
|
|
54
|
+
return value if seen.key?(value)
|
|
55
|
+
|
|
56
|
+
seen[value] = true
|
|
57
|
+
case value
|
|
58
|
+
when Hash then value.each { |key, item| freeze_nested(key, seen); freeze_nested(item, seen) }
|
|
59
|
+
when Array, Struct then value.each { |item| freeze_nested(item, seen) }
|
|
60
|
+
end
|
|
61
|
+
value.freeze
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.freeze_nested(value, seen)
|
|
65
|
+
freeze_all(value, seen) if value.is_a?(Hash) || value.is_a?(Array) || value.is_a?(Struct) || value.is_a?(String)
|
|
66
|
+
end
|
|
67
|
+
private_class_method :ractors?, :freezable?, :freeze_all, :freeze_nested
|
|
68
|
+
end
|
data/lib/importmap/version.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Importmap
|
|
2
4
|
# importmap-plus versions independently of the importmap-rails it forks.
|
|
3
5
|
# UPSTREAM_VERSION is the importmap-rails release this tracks.
|
|
4
|
-
VERSION = "2.0
|
|
6
|
+
VERSION = "2.1.0"
|
|
5
7
|
UPSTREAM_VERSION = "2.2.3"
|
|
6
8
|
end
|
data/lib/importmap-plus.rb
CHANGED
data/lib/importmap-rails.rb
CHANGED
data/lib/install/install.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: importmap-plus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
@@ -17,6 +17,9 @@ dependencies:
|
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: 6.0.0
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9.0'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -24,6 +27,9 @@ dependencies:
|
|
|
24
27
|
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: 6.0.0
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9.0'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: activesupport
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -31,6 +37,9 @@ dependencies:
|
|
|
31
37
|
- - ">="
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
39
|
version: 6.0.0
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '9.0'
|
|
34
43
|
type: :runtime
|
|
35
44
|
prerelease: false
|
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -38,6 +47,9 @@ dependencies:
|
|
|
38
47
|
- - ">="
|
|
39
48
|
- !ruby/object:Gem::Version
|
|
40
49
|
version: 6.0.0
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '9.0'
|
|
41
53
|
- !ruby/object:Gem::Dependency
|
|
42
54
|
name: actionpack
|
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -45,6 +57,9 @@ dependencies:
|
|
|
45
57
|
- - ">="
|
|
46
58
|
- !ruby/object:Gem::Version
|
|
47
59
|
version: 6.0.0
|
|
60
|
+
- - "<"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '9.0'
|
|
48
63
|
type: :runtime
|
|
49
64
|
prerelease: false
|
|
50
65
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -52,6 +67,9 @@ dependencies:
|
|
|
52
67
|
- - ">="
|
|
53
68
|
- !ruby/object:Gem::Version
|
|
54
69
|
version: 6.0.0
|
|
70
|
+
- - "<"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '9.0'
|
|
55
73
|
description: 'A drop-in replacement for importmap-rails. Same Importmap:: API, same
|
|
56
74
|
pins, same import map, plus `bin/importmap pin --minify`, `--from esm.run` for jsDelivr''s
|
|
57
75
|
bundled builds, `--lock` to hold a package at a version, and a pin comment that
|
|
@@ -86,8 +104,10 @@ files:
|
|
|
86
104
|
- lib/importmap/npm.rb
|
|
87
105
|
- lib/importmap/package_graph.rb
|
|
88
106
|
- lib/importmap/packager.rb
|
|
107
|
+
- lib/importmap/pin_set.rb
|
|
89
108
|
- lib/importmap/provider_chain.rb
|
|
90
109
|
- lib/importmap/reloader.rb
|
|
110
|
+
- lib/importmap/shareable.rb
|
|
91
111
|
- lib/importmap/vendored_graph.rb
|
|
92
112
|
- lib/importmap/version.rb
|
|
93
113
|
- lib/install/bin/importmap
|
|
@@ -99,7 +119,7 @@ licenses:
|
|
|
99
119
|
- MIT
|
|
100
120
|
metadata:
|
|
101
121
|
homepage_uri: https://github.com/zoolutions/importmap-plus
|
|
102
|
-
source_code_uri: https://github.com/zoolutions/importmap-plus
|
|
122
|
+
source_code_uri: https://github.com/zoolutions/importmap-plus/tree/main
|
|
103
123
|
changelog_uri: https://github.com/zoolutions/importmap-plus/blob/main/CHANGELOG.md
|
|
104
124
|
rdoc_options: []
|
|
105
125
|
require_paths:
|