importmap-plus 1.0.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.
@@ -0,0 +1,428 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "json"
4
+ require "importmap/minifier"
5
+ require "importmap/http_retries"
6
+
7
+ class Importmap::Packager
8
+ include Importmap::HttpRetries
9
+
10
+ PIN_REGEX = /#{Importmap::Map::PIN_REGEX}(.*)/.freeze # :nodoc:
11
+ PRELOAD_OPTION_REGEXP = /preload:\s*(\[[^\]]+\]|true|false|["'][^"']*["'])/.freeze # :nodoc:
12
+ TO_OPTION_REGEXP = /to:\s*["']([^"']*)["']/.freeze # :nodoc:
13
+ REMOTE_URL_REGEXP = %r{\Ahttps?://}.freeze # :nodoc:
14
+
15
+ PROVIDER_HOSTS = {
16
+ "ga.jspm.io" => "jspm.io",
17
+ "unpkg.com" => "unpkg",
18
+ "cdn.jsdelivr.net" => "jsdelivr",
19
+ "cdn.skypack.dev" => "skypack",
20
+ "esm.sh" => "esm.sh"
21
+ }.freeze # :nodoc:
22
+
23
+ # jsDelivr's bundling endpoint (https://www.jsdelivr.com/esm): one minified
24
+ # ESM file per package, with its dependencies referenced as /npm/dep@ver/+esm.
25
+ ESM_RUN_PROVIDER = "esm.run".freeze # :nodoc:
26
+ ESM_RUN_CDN = "https://cdn.jsdelivr.net/npm/".freeze # :nodoc:
27
+ ESM_RUN_URL_REGEXP = %r{\Ahttps://cdn\.jsdelivr\.net/npm/.+/\+esm\z}.freeze # :nodoc:
28
+ # An esm.run bundle's own imports: `from"/npm/dep@1.2.3/+esm"`, `import"…"`,
29
+ # `import("…")`, `export … from"…"`. Anchored on the keyword so an ordinary
30
+ # string that happens to look like a bundle URL is left alone. What it does
31
+ # not do is parse JavaScript, so the same text inside a string or a comment
32
+ # would still be rewritten — a jsDelivr bundle is esbuild output whose only
33
+ # surviving comment is the banner, and a root-relative /npm/ URL is
34
+ # meaningless anywhere but in one of its own imports.
35
+ ESM_RUN_IMPORT_REGEXP =
36
+ %r{((?:\bfrom|\bimport)\s*\(?\s*)(["'])/npm/((?:@[^/"'@]+/)?[^/"'@]+)@([^/"']+)((?:/[^"']*?)?)/\+esm\2}.freeze # :nodoc:
37
+ # name[@version][/subpath] — a leading "@" distinguishes a scoped name
38
+ # (@scope/pkg) from an unscoped name with a subpath (apexcharts/core).
39
+ PACKAGE_SPEC_REGEXP = %r{\A(@[^@/]+/[^@/]+|[^@/]+)(?:@([^/]+))?(/.+)?\z}.freeze # :nodoc:
40
+ # The version comment on a vendored pin, plus what it was built with:
41
+ # pin "luxon" # @3.7.2
42
+ # pin "luxon" # @3.7.2 (esm.run, minified)
43
+ PIN_PROVENANCE_REGEXP = /#\s*@([^\s(]+)(?:\s+\(([^)]*)\))?/.freeze # :nodoc:
44
+ DEFAULT_PROVIDER = "jspm.io".freeze # :nodoc:
45
+
46
+ Error = Class.new(StandardError)
47
+ HTTPError = Class.new(Error)
48
+ ServiceError = Error.new(Error)
49
+
50
+ singleton_class.attr_accessor :endpoint
51
+ self.endpoint = URI("https://api.jspm.io/generate")
52
+
53
+ singleton_class.attr_accessor :esm_run_resolver
54
+ self.esm_run_resolver = URI("https://data.jsdelivr.com/v1/packages/npm/")
55
+
56
+ # CDNs reset connections and rate-limit bursts. Each request is tried this
57
+ # many times, pausing retry_wait × attempt between tries, before it fails.
58
+ # Shared with Importmap::Npm, which talks to the registry the same way.
59
+ class << self
60
+ def retry_attempts = Importmap::HttpRetries.attempts
61
+ def retry_attempts=(value)
62
+ Importmap::HttpRetries.attempts = value
63
+ end
64
+
65
+ def retry_wait = Importmap::HttpRetries.wait
66
+ def retry_wait=(value)
67
+ Importmap::HttpRetries.wait = value
68
+ end
69
+ end
70
+
71
+ # Anything responding to #call(source) => String. Defaults to the first of
72
+ # bun, esbuild or terser found on the machine.
73
+ singleton_class.attr_writer :minifier
74
+
75
+ def self.minifier
76
+ @minifier ||= Importmap::Minifier.new
77
+ end
78
+
79
+ attr_reader :vendor_path
80
+
81
+ def initialize(importmap_path = "config/importmap.rb", vendor_path: "vendor/javascript")
82
+ @importmap_path = Pathname.new(importmap_path)
83
+ @vendor_path = Pathname.new(vendor_path)
84
+ end
85
+
86
+ def import(*packages, env: "production", from: "jspm")
87
+ return import_from_esm_run(packages) if esm_run?(from)
88
+
89
+ response = post_json({
90
+ "install" => Array(packages),
91
+ "flattenScope" => true,
92
+ "env" => [ "browser", "module", env ],
93
+ "provider" => normalize_provider(from),
94
+ })
95
+
96
+ case response.code
97
+ when "200"
98
+ extract_parsed_response(response)
99
+ when "404", "401"
100
+ nil
101
+ else
102
+ handle_failure_response(response)
103
+ end
104
+ end
105
+
106
+ def pin_for(package, url = nil, preloads: nil)
107
+ to = url ? %(, to: "#{url}") : ""
108
+ preload_param = preload(preloads)
109
+
110
+ %(pin "#{package}") + to + preload_param
111
+ end
112
+
113
+ # The pin line for a vendored download. The version comment also records
114
+ # the CDN when it isn't jspm and whether the file was minified, so a later
115
+ # update or pristine can do the same again:
116
+ #
117
+ # pin "luxon" # @3.7.2
118
+ # pin "luxon" # @3.7.2 (esm.run, minified)
119
+ #
120
+ def vendored_pin_for(package, url, preloads = nil, minify: false)
121
+ filename = package_filename(package)
122
+ version = extract_package_version_from(url)
123
+ to = "#{package}.js" != filename ? filename : nil
124
+
125
+ provenance = []
126
+ provenance << provider_for_url(url) if provider_for_url(url) && provider_for_url(url) != DEFAULT_PROVIDER
127
+ provenance << "minified" if minify
128
+
129
+ pin_for(package, to, preloads: preloads) + %( # #{version}) + (provenance.any? ? %( (#{provenance.join(", ")})) : "")
130
+ end
131
+
132
+ # What the pin's version comment says a vendored package was built with:
133
+ # { version:, provider:, minified: }, or nil for a pin without one.
134
+ def pin_provenance(package)
135
+ return unless @importmap_path.exist?
136
+
137
+ line = importmap.lines.find { |candidate| candidate.match?(Importmap::Map.pin_line_regexp_for(package)) }
138
+ match = line&.match(PIN_PROVENANCE_REGEXP)
139
+ return unless match
140
+
141
+ details = match[2].to_s.split(",").map(&:strip)
142
+ minified = details.delete("minified") ? true : false
143
+
144
+ { version: match[1], provider: details.first, minified: minified }
145
+ end
146
+
147
+ def packaged?(package)
148
+ importmap.match(Importmap::Map.pin_line_regexp_for(package))
149
+ end
150
+
151
+ # Downloads +url+ into vendor/javascript. With +minify: true+ the source is
152
+ # run through .minifier first and the file header records it, so later
153
+ # updates keep minifying. Returns the dependencies an esm.run bundle imports
154
+ # as [package, url] pairs (empty for every other provider), with the bundle's
155
+ # absolute /npm/... imports rewritten to bare specifiers on the way in.
156
+ def download(package, url, minify: false)
157
+ ensure_vendor_directory_exists
158
+ remove_existing_package_file(package)
159
+ download_package_file(package, url, minify: minify)
160
+ end
161
+
162
+ def remove(package)
163
+ remove_existing_package_file(package)
164
+ remove_package_from_importmap(package)
165
+ end
166
+
167
+ def extract_existing_pin_options(packages)
168
+ return {} unless @importmap_path.exist?
169
+
170
+ packages = Array(packages)
171
+
172
+ all_package_options = build_package_options_lookup(importmap.lines)
173
+
174
+ packages.to_h do |package|
175
+ [package, all_package_options[package] || {}]
176
+ end
177
+ end
178
+
179
+ # Drops the cached import map so a read after a write sees the new file:
180
+ # pinning an esm.run bundle appends pins and then asks the map what its
181
+ # dependencies still need.
182
+ def reload!
183
+ @importmap = nil
184
+ self
185
+ end
186
+
187
+ # The provenance a pin would record for +url+, shaped like #pin_provenance
188
+ # returns, so a caller can tell whether the existing pin already says this.
189
+ def provenance_for(url, minify: false)
190
+ provider = provider_for_url(url)
191
+
192
+ { provider: provider == DEFAULT_PROVIDER ? nil : provider, minified: minify ? true : false }
193
+ end
194
+
195
+ def remote_pin?(package)
196
+ options = extract_existing_pin_options(package)[package] || {}
197
+ options[:to].to_s.match?(REMOTE_URL_REGEXP)
198
+ end
199
+
200
+ def provider_for_url(url)
201
+ return ESM_RUN_PROVIDER if url.to_s.match?(ESM_RUN_URL_REGEXP)
202
+
203
+ PROVIDER_HOSTS[URI(url.to_s).host]
204
+ rescue URI::InvalidURIError
205
+ nil
206
+ end
207
+
208
+ def esm_run?(provider)
209
+ provider.to_s == ESM_RUN_PROVIDER
210
+ end
211
+
212
+ # The import-map key a package spec pins: "apexcharts@7.1.0/core" pins
213
+ # "apexcharts/core", "@hotwired/stimulus@3" pins "@hotwired/stimulus".
214
+ def package_key_for(spec)
215
+ name, _version, subpath = spec.to_s.match(PACKAGE_SPEC_REGEXP)&.captures
216
+ name ? "#{name}#{subpath}" : spec.to_s
217
+ end
218
+
219
+ def remove_existing_package_file(package)
220
+ FileUtils.rm_rf vendored_package_path(package)
221
+ end
222
+
223
+ def extract_package_version_from(url)
224
+ url.match(/@\d+\.\d+\.\d+[^\/\s"']*/)&.to_a&.first
225
+ end
226
+
227
+ private
228
+ def build_package_options_lookup(lines)
229
+ lines.each_with_object({}) do |line, package_options|
230
+ match = line.strip.match(PIN_REGEX)
231
+
232
+ if match
233
+ package_name = match[1]
234
+ options_part = match[2]
235
+
236
+ options = {}
237
+
238
+ if (preload_match = options_part.match(PRELOAD_OPTION_REGEXP))
239
+ options[:preload] = preload_from_string(preload_match[1])
240
+ end
241
+
242
+ if (to_match = options_part.match(TO_OPTION_REGEXP))
243
+ options[:to] = to_match[1]
244
+ end
245
+
246
+ package_options[package_name] = options if options.any?
247
+ end
248
+ end
249
+ end
250
+
251
+ def preload_from_string(value)
252
+ case value
253
+ when "true"
254
+ true
255
+ when "false"
256
+ false
257
+ when /^\[.*\]$/
258
+ JSON.parse(value)
259
+ else
260
+ value.gsub(/["']/, "")
261
+ end
262
+ end
263
+
264
+ def preload(preloads)
265
+ case Array(preloads)
266
+ in []
267
+ ""
268
+ in ["true"] | [true]
269
+ %(, preload: true)
270
+ in ["false"] | [false]
271
+ %(, preload: false)
272
+ in [string]
273
+ %(, preload: "#{string}")
274
+ else
275
+ %(, preload: #{preloads})
276
+ end
277
+ end
278
+
279
+ def post_json(body)
280
+ with_retries("posting to #{self.class.endpoint}") do
281
+ Net::HTTP.post(self.class.endpoint, body.to_json, "Content-Type" => "application/json")
282
+ end
283
+ rescue HTTPError
284
+ raise
285
+ rescue => error
286
+ raise HTTPError, "Unexpected transport error (#{error.class}: #{error.message})"
287
+ end
288
+
289
+ def normalize_provider(name)
290
+ name.to_s == "jspm" ? "jspm.io" : name.to_s
291
+ end
292
+
293
+ def extract_parsed_response(response)
294
+ parsed = JSON.parse(response.body)
295
+ imports = parsed.dig("map", "imports")
296
+
297
+ {
298
+ imports: imports,
299
+ }
300
+ end
301
+
302
+ def handle_failure_response(response)
303
+ if error_message = parse_service_error(response)
304
+ raise ServiceError, error_message
305
+ else
306
+ raise HTTPError, "Unexpected response code (#{response.code})"
307
+ end
308
+ end
309
+
310
+ def parse_service_error(response)
311
+ JSON.parse(response.body.to_s)["error"]
312
+ rescue JSON::ParserError
313
+ nil
314
+ end
315
+
316
+ def importmap
317
+ @importmap ||= File.read(@importmap_path)
318
+ end
319
+
320
+
321
+ def ensure_vendor_directory_exists
322
+ FileUtils.mkdir_p @vendor_path
323
+ end
324
+
325
+ def remove_package_from_importmap(package)
326
+ all_lines = File.readlines(@importmap_path)
327
+ with_lines_removed = all_lines.grep_v(Importmap::Map.pin_line_regexp_for(package))
328
+
329
+ File.open(@importmap_path, "w") do |file|
330
+ with_lines_removed.each { |line| file.write(line) }
331
+ end
332
+ end
333
+
334
+ def download_package_file(package, url, minify: false)
335
+ response = with_retries("downloading #{url}") { Net::HTTP.get_response(URI(url)) }
336
+
337
+ if response.code == "200"
338
+ source = response.body.dup.force_encoding("UTF-8")
339
+ source, dependencies = rewrite_esm_run_imports(source) if url.match?(ESM_RUN_URL_REGEXP)
340
+ source = self.class.minifier.call(source) if minify
341
+
342
+ save_vendored_package(package, url, source, minified: minify)
343
+
344
+ dependencies || []
345
+ else
346
+ handle_failure_response(response)
347
+ end
348
+ end
349
+
350
+ def save_vendored_package(package, url, source, minified: false)
351
+ File.open(vendored_package_path(package), "w+") do |vendored_package|
352
+ vendored_package.write "// #{package}#{extract_package_version_from(url)} downloaded from #{url}#{" (minified)" if minified}\n\n"
353
+
354
+ vendored_package.write remove_sourcemap_comment_from(source).force_encoding("UTF-8")
355
+ end
356
+ end
357
+
358
+ # Turns import "/npm/dep@1.2.3/+esm" into import "dep" so the bundle
359
+ # resolves through the import map, and lists what it needs pinned.
360
+ def rewrite_esm_run_imports(source)
361
+ dependencies = {}
362
+ versions = Hash.new { |hash, key| hash[key] = [] }
363
+
364
+ rewritten = source.gsub(ESM_RUN_IMPORT_REGEXP) do
365
+ keyword, quote, name, version, subpath = $1, $2, $3, $4, $5.to_s
366
+ key = "#{name}#{subpath}"
367
+ dependencies[key] ||= "#{ESM_RUN_CDN}#{name}@#{version}#{subpath}/+esm"
368
+ versions[key] << version unless versions[key].include?(version)
369
+ "#{keyword}#{quote}#{name}#{subpath}#{quote}"
370
+ end
371
+
372
+ # An import map maps a bare specifier to one file, so a bundle that
373
+ # imports the same package at two versions can only get the first one
374
+ # it asked for. Say so rather than pick silently.
375
+ versions.each do |key, seen|
376
+ next if seen.one?
377
+
378
+ warn %(#{key} is imported at #{seen.join(", ")} by this bundle; pinning @#{seen.first}, an import map holds one version)
379
+ end
380
+
381
+ [rewritten, dependencies.to_a]
382
+ end
383
+
384
+ def import_from_esm_run(packages)
385
+ imports = packages.to_h do |spec|
386
+ name, requested, subpath = spec.match(PACKAGE_SPEC_REGEXP)&.captures
387
+ raise Error, "Can't parse package spec #{spec.inspect}" unless name
388
+
389
+ version = resolve_esm_run_version(name, requested)
390
+ return nil unless version
391
+
392
+ ["#{name}#{subpath}", "#{ESM_RUN_CDN}#{name}@#{version}#{subpath}/+esm"]
393
+ end
394
+
395
+ { imports: imports }
396
+ end
397
+
398
+ def resolve_esm_run_version(name, requested)
399
+ uri = self.class.esm_run_resolver.dup
400
+ uri.path += "#{name}/resolved"
401
+ uri.query = "specifier=#{URI.encode_www_form_component(requested)}" if requested
402
+
403
+ response = with_retries("resolving #{uri}") { Net::HTTP.get_response(uri) }
404
+
405
+ case response.code
406
+ when "200"
407
+ JSON.parse(response.body)["version"]
408
+ when "404"
409
+ nil
410
+ else
411
+ handle_failure_response(response)
412
+ end
413
+ rescue JSON::ParserError
414
+ raise HTTPError, "Unexpected response from #{uri}"
415
+ end
416
+
417
+ def remove_sourcemap_comment_from(source)
418
+ source.gsub(/^\/\/# sourceMappingURL=.*/, "")
419
+ end
420
+
421
+ def vendored_package_path(package)
422
+ @vendor_path.join(package_filename(package))
423
+ end
424
+
425
+ def package_filename(package)
426
+ package.gsub("/", "--") + ".js"
427
+ end
428
+ end
@@ -0,0 +1,23 @@
1
+ require "active_support"
2
+ require "active_support/core_ext/module/delegation"
3
+
4
+ class Importmap::Reloader
5
+ delegate :execute_if_updated, :execute, :updated?, to: :updater
6
+
7
+ def reload!
8
+ import_map_paths.each { |path| Rails.application.importmap.draw(path) }
9
+ end
10
+
11
+ private
12
+ def updater
13
+ @updater ||= config.file_watcher.new(import_map_paths) { reload! }
14
+ end
15
+
16
+ def import_map_paths
17
+ config.importmap.paths
18
+ end
19
+
20
+ def config
21
+ Rails.application.config
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module Importmap
2
+ # importmap-plus versions independently of the importmap-rails it forks.
3
+ # UPSTREAM_VERSION is the importmap-rails release this tracks.
4
+ VERSION = "1.0.0"
5
+ UPSTREAM_VERSION = "2.2.3"
6
+ end
@@ -0,0 +1,4 @@
1
+ # The entry point Bundler requires for `gem "importmap-plus"`. The constants
2
+ # stay under Importmap:: so this gem drops into any app that used
3
+ # importmap-rails without touching a single call site.
4
+ require "importmap-rails"
@@ -0,0 +1,6 @@
1
+ module Importmap
2
+ end
3
+
4
+ require "importmap/version"
5
+ require "importmap/reloader"
6
+ require "importmap/engine" if defined?(Rails::Railtie)
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../config/application"
4
+ require "importmap/commands"
@@ -0,0 +1,3 @@
1
+ # Pin npm packages by running ./bin/importmap
2
+
3
+ pin "application"
@@ -0,0 +1,32 @@
1
+ APPLICATION_LAYOUT_PATH = Rails.root.join("app/views/layouts/application.html.erb")
2
+
3
+ if APPLICATION_LAYOUT_PATH.exist?
4
+ say "Add Importmap include tags in application layout"
5
+ insert_into_file APPLICATION_LAYOUT_PATH.to_s, "\n <%= javascript_importmap_tags %>", before: /\s*<\/head>/
6
+ else
7
+ say "Default application.html.erb is missing!", :red
8
+ say " Add <%= javascript_importmap_tags %> within the <head> tag in your custom layout."
9
+ end
10
+
11
+ say "Create application.js module as entrypoint"
12
+ create_file Rails.root.join("app/javascript/application.js") do <<-JS
13
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/zoolutions/importmap-plus
14
+ JS
15
+ end
16
+
17
+ say "Use vendor/javascript for downloaded pins"
18
+ empty_directory "vendor/javascript"
19
+ keep_file "vendor/javascript"
20
+
21
+ if (sprockets_manifest_path = Rails.root.join("app/assets/config/manifest.js")).exist?
22
+ say "Ensure JavaScript files are in the Sprocket manifest"
23
+ append_to_file sprockets_manifest_path,
24
+ %(//= link_tree ../../javascript .js\n//= link_tree ../../../vendor/javascript .js\n)
25
+ end
26
+
27
+ say "Configure importmap paths in config/importmap.rb"
28
+ copy_file "#{__dir__}/config/importmap.rb", "config/importmap.rb"
29
+
30
+ say "Copying binstub"
31
+ copy_file "#{__dir__}/bin/importmap", "bin/importmap"
32
+ chmod "bin", 0755 & ~File.umask, verbose: false
@@ -0,0 +1,9 @@
1
+ namespace :importmap do
2
+ desc "Setup Importmap for the app"
3
+ task :install do
4
+ previous_location = ENV["LOCATION"]
5
+ ENV["LOCATION"] = File.expand_path("../install/install.rb", __dir__)
6
+ Rake::Task["app:template"].invoke
7
+ ENV["LOCATION"] = previous_location
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: importmap-plus
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Heinemeier Hansson
8
+ - Mikael Henriksson
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 6.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 6.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: actionpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 6.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 6.0.0
55
+ description: 'A drop-in replacement for importmap-rails. Same Importmap:: API, same
56
+ pins, same import map, plus `bin/importmap pin --minify`, `--from esm.run` for jsDelivr''s
57
+ bundled builds, and a pin comment that records the CDN and minification so later
58
+ updates keep them. Use this gem or importmap-rails, never both.'
59
+ email: mikael@mhenrixon.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - CHANGELOG.md
65
+ - MIT-LICENSE
66
+ - README.md
67
+ - Rakefile
68
+ - app/controllers/importmap/freshness.rb
69
+ - app/helpers/importmap/importmap_tags_helper.rb
70
+ - lib/importmap-plus.rb
71
+ - lib/importmap-rails.rb
72
+ - lib/importmap/commands.rb
73
+ - lib/importmap/engine.rb
74
+ - lib/importmap/http_retries.rb
75
+ - lib/importmap/map.rb
76
+ - lib/importmap/minifier.rb
77
+ - lib/importmap/npm.rb
78
+ - lib/importmap/packager.rb
79
+ - lib/importmap/reloader.rb
80
+ - lib/importmap/version.rb
81
+ - lib/install/bin/importmap
82
+ - lib/install/config/importmap.rb
83
+ - lib/install/install.rb
84
+ - lib/tasks/importmap_tasks.rake
85
+ homepage: https://github.com/zoolutions/importmap-plus
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ homepage_uri: https://github.com/zoolutions/importmap-plus
90
+ source_code_uri: https://github.com/zoolutions/importmap-plus
91
+ changelog_uri: https://github.com/zoolutions/importmap-plus/blob/main/CHANGELOG.md
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 3.1.0
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 4.0.19
107
+ specification_version: 4
108
+ summary: importmap-rails with vendoring that minifies, bundles from esm.run, and remembers
109
+ where each package came from.
110
+ test_files: []