assiette 0.6.1 → 0.7.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/lib/assiette/asset_handler.rb +111 -12
- data/lib/assiette/dependency_graph.rb +30 -17
- data/lib/assiette/helpers.rb +9 -2
- data/lib/assiette/rewriter.rb +26 -6
- data/lib/assiette/server.rb +4 -0
- data/lib/assiette/version.rb +1 -1
- data/lib/generators/assiette/install/templates/initializer.rb.tt +4 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0f3b02555b0eb18e84813e36095d9fbd3528e9d7a61ed529e00c5b3dd6ecd5a
|
|
4
|
+
data.tar.gz: 20a0ec6ce64278f556608150ba66bf9429bef13f8bc45d41bc71e360f3f0151f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6e01429228aefba47fc36242f8180b074c58c65297ed490ac6b94562988396d1a375acb8b6e31c6a3514f009960f5caf0dc7ffb56dea75d2247886aa89044ef
|
|
7
|
+
data.tar.gz: 5094d53275d9d5fc760f97c24905a840d55254e768aebfd979928d9afcb8c014819e443ee7e658600dd70aaa26825dcf4b95733581817cc7a9e98b33e91b7fc2
|
|
@@ -22,6 +22,9 @@ module Assiette
|
|
|
22
22
|
|
|
23
23
|
JS_EXTENSIONS = %w[.js .mjs].to_set.freeze
|
|
24
24
|
|
|
25
|
+
# What #expose takes as a handle: "@" and a name, as in "@task_progress"
|
|
26
|
+
HANDLE_RE = /\A@[a-z0-9][\w.-]*\z/i
|
|
27
|
+
|
|
25
28
|
attr_reader :dependency_graph
|
|
26
29
|
|
|
27
30
|
# Every extension this handler serves, as ".ext" => content type. This is
|
|
@@ -42,9 +45,46 @@ module Assiette
|
|
|
42
45
|
def initialize(root:, additional_directory_mappings: {}, content_types: {})
|
|
43
46
|
@content_types = CONTENT_TYPES.merge(content_types.to_h { |ext, type| [normalize_extension(ext), type] }).freeze
|
|
44
47
|
@mappings = build_mappings(root, additional_directory_mappings)
|
|
48
|
+
@exposed = {}
|
|
45
49
|
@dependency_graph = DependencyGraph.new(self)
|
|
46
50
|
end
|
|
47
51
|
|
|
52
|
+
# Makes a directory - a gem's, say - available as "@handle/...". Its files
|
|
53
|
+
# are served at /@handle/..., and an import of "@handle/file.js" from any
|
|
54
|
+
# module this handler serves is rewritten to that file, fingerprinted:
|
|
55
|
+
#
|
|
56
|
+
# handler.expose("@task_progress", TaskProgress.js_path)
|
|
57
|
+
# import { followProgress } from "@task_progress/task_tree.js"
|
|
58
|
+
#
|
|
59
|
+
# Several directories under one handle are searched in the order given, and
|
|
60
|
+
# the root still comes first, so the app's own app/assets/@handle/file.js
|
|
61
|
+
# wins over the one exposed.
|
|
62
|
+
#
|
|
63
|
+
# Exposing a handle again with the same directories does nothing - an
|
|
64
|
+
# initializer that runs twice is the likely cause, and harmless. With other
|
|
65
|
+
# directories, the new ones replace the old, with a warning.
|
|
66
|
+
#
|
|
67
|
+
# Call it while setting up, before the handler serves anything.
|
|
68
|
+
def expose(handle, directories)
|
|
69
|
+
handle = handle.to_s
|
|
70
|
+
raise ArgumentError, "#{handle.inspect} is not a handle - it needs to look like \"@name\"" unless handle.match?(HANDLE_RE)
|
|
71
|
+
roots = Array(directories).map { |dir| Pathname.new(dir).expand_path }
|
|
72
|
+
return self if @exposed[handle] == roots
|
|
73
|
+
if @exposed.key?(handle)
|
|
74
|
+
warn "Assiette: #{handle} was exposed from #{@exposed[handle].join(", ")}, and now from #{roots.join(", ")} instead"
|
|
75
|
+
end
|
|
76
|
+
@exposed[handle] = roots
|
|
77
|
+
# A new array rather than changing this one, so a thread walking it is not disturbed
|
|
78
|
+
@mappings = @mappings.reject { |prefix, _root| prefix == handle } + roots.map { |root| [handle, root] }
|
|
79
|
+
@watched_directories = nil
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The exposed handles, each with the directories behind it
|
|
84
|
+
def exposed
|
|
85
|
+
@exposed.dup
|
|
86
|
+
end
|
|
87
|
+
|
|
48
88
|
# The content type this handler serves `path` as, or nil if its extension
|
|
49
89
|
# is not one this handler knows.
|
|
50
90
|
def content_type_for(path)
|
|
@@ -91,8 +131,9 @@ module Assiette
|
|
|
91
131
|
def absolute_asset_url_path(path, script_name = "")
|
|
92
132
|
clean = path.sub(%r{\A/}, "")
|
|
93
133
|
return nil unless resolve_file(clean)
|
|
94
|
-
|
|
95
|
-
|
|
134
|
+
# No digest means the file went away mid-call. A constant tag would get cached like a real version
|
|
135
|
+
hash = @dependency_graph.tree_sha(clean)
|
|
136
|
+
hash ? "#{script_name}/#{clean}?s=#{hash}" : "#{script_name}/#{clean}"
|
|
96
137
|
end
|
|
97
138
|
|
|
98
139
|
def asset_integrity(path)
|
|
@@ -101,16 +142,37 @@ module Assiette
|
|
|
101
142
|
@dependency_graph.tree_integrity(clean)
|
|
102
143
|
end
|
|
103
144
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
145
|
+
# The modules to preload: every module under the handler's own roots, and
|
|
146
|
+
# of the exposed directories only what those modules reach through their
|
|
147
|
+
# imports. A static import and a literal `import("...")` count alike, since
|
|
148
|
+
# the graph records both, so the browser is handed the whole tree up front
|
|
149
|
+
# and discovers nothing on its own. An app that imports one file of a gem
|
|
150
|
+
# preloads that file and what it pulls in, and none of the gem's other
|
|
151
|
+
# modules.
|
|
152
|
+
#
|
|
153
|
+
# `exposed:` changes what the exposed directories contribute:
|
|
154
|
+
#
|
|
155
|
+
# :imported what the own modules reach (the default)
|
|
156
|
+
# :all every module of every exposed directory
|
|
157
|
+
# :none, [] nothing
|
|
158
|
+
# "@gem", ["@a", ..] every module of those handles, and in the other
|
|
159
|
+
# handles still what is reached - for an app whose
|
|
160
|
+
# `import()` of a gem file is computed at run time,
|
|
161
|
+
# which no scan can see
|
|
162
|
+
def js_modules(exposed: :imported)
|
|
163
|
+
own_mappings, exposed_mappings = @mappings.partition { |prefix, _root| !@exposed.key?(prefix) }
|
|
164
|
+
own = modules_under(own_mappings)
|
|
165
|
+
whole_handles, follow_imports = case exposed
|
|
166
|
+
when :imported then [[], true]
|
|
167
|
+
when :all, true then [@exposed.keys, false]
|
|
168
|
+
when :none, nil then [[], false]
|
|
169
|
+
else
|
|
170
|
+
handles = Array(exposed).map(&:to_s)
|
|
171
|
+
[handles, handles.any?]
|
|
172
|
+
end
|
|
173
|
+
listed = modules_under(exposed_mappings.select { |mapping| whole_handles.include?(mapping.first) })
|
|
174
|
+
reached = follow_imports ? exposed_modules_imported_by(own) : []
|
|
175
|
+
(own + listed + reached).uniq { |m| m[:path] }.sort_by { |m| m[:path] }
|
|
114
176
|
end
|
|
115
177
|
|
|
116
178
|
# A single 16-char hex hash covering every asset this handler can serve.
|
|
@@ -154,6 +216,43 @@ module Assiette
|
|
|
154
216
|
|
|
155
217
|
private
|
|
156
218
|
|
|
219
|
+
# Every file under `mappings` with a JavaScript extension that imports or
|
|
220
|
+
# exports something, as {path:, integrity:}
|
|
221
|
+
def modules_under(mappings)
|
|
222
|
+
js_glob = "**/*{#{javascript_extensions.map { |ext| case_insensitive(ext) }.join(",")}}"
|
|
223
|
+
mappings.flat_map { |prefix, root|
|
|
224
|
+
Dir.glob(File.join(root, js_glob)).filter_map { |abs|
|
|
225
|
+
next unless File.foreach(abs).any? { |line| line.match?(/\A\s*(import|export)\s/) }
|
|
226
|
+
relative = Pathname.new(abs).relative_path_from(root).to_s
|
|
227
|
+
mod_path = "/#{"#{prefix}/" unless prefix.empty?}#{relative}".squeeze("/")
|
|
228
|
+
{path: mod_path, integrity: asset_integrity(mod_path)}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# The exposed JavaScript modules reached from `modules` by walking the
|
|
234
|
+
# graph's deps. Listing `modules` resolved each of them already, so the
|
|
235
|
+
# walk is over nodes that are in memory, with their mtimes checked.
|
|
236
|
+
def exposed_modules_imported_by(modules)
|
|
237
|
+
seen = Set.new
|
|
238
|
+
queue = modules.map { |m| m[:path].delete_prefix("/") }
|
|
239
|
+
found = []
|
|
240
|
+
until queue.empty?
|
|
241
|
+
url_path = queue.shift
|
|
242
|
+
next unless seen.add?(url_path)
|
|
243
|
+
asset = @dependency_graph[url_path]
|
|
244
|
+
next unless asset
|
|
245
|
+
found << {path: "/#{url_path}", integrity: asset.sri_integrity} if exposed_javascript?(url_path)
|
|
246
|
+
queue.concat(asset.deps)
|
|
247
|
+
end
|
|
248
|
+
found
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def exposed_javascript?(url_path)
|
|
252
|
+
@exposed.each_key.any? { |handle| url_path.start_with?("#{handle}/") } &&
|
|
253
|
+
javascript_extensions.include?(normalize_extension(File.extname(url_path)))
|
|
254
|
+
end
|
|
255
|
+
|
|
157
256
|
# The extensions served as JavaScript: .js and .mjs, plus anything this
|
|
158
257
|
# handler had registered with a JavaScript content type.
|
|
159
258
|
def javascript_extensions
|
|
@@ -10,12 +10,15 @@ module Assiette
|
|
|
10
10
|
class Asset
|
|
11
11
|
attr_reader :url_path
|
|
12
12
|
attr_accessor :abs_path, :mtime, :digest, :deps, :dependents
|
|
13
|
+
# Set only for cycle members, whose served bytes differ from what the shared digest covers
|
|
14
|
+
attr_accessor :served_digest
|
|
13
15
|
|
|
14
16
|
def initialize(url_path:, abs_path:, mtime:)
|
|
15
17
|
@url_path = url_path.freeze
|
|
16
18
|
@abs_path = abs_path
|
|
17
19
|
@mtime = mtime
|
|
18
20
|
@digest = nil
|
|
21
|
+
@served_digest = nil
|
|
19
22
|
@deps = []
|
|
20
23
|
@dependents = Set.new
|
|
21
24
|
end
|
|
@@ -27,8 +30,9 @@ module Assiette
|
|
|
27
30
|
|
|
28
31
|
# SRI integrity string for integrity= attributes.
|
|
29
32
|
def sri_integrity
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
sri_digest = served_digest || digest
|
|
34
|
+
return nil unless sri_digest
|
|
35
|
+
"sha256-#{Base64.strict_encode64(sri_digest)}"
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
# Whether this asset's file has changed since last scan.
|
|
@@ -67,9 +71,12 @@ module Assiette
|
|
|
67
71
|
self[url_path]&.sri_integrity
|
|
68
72
|
end
|
|
69
73
|
|
|
70
|
-
# Resolves a relative import path to a URL path.
|
|
74
|
+
# Resolves a relative import path to a URL path. An "@handle/..." path is
|
|
75
|
+
# already one - see AssetHandler#expose
|
|
71
76
|
def resolve_import_for(from_url_path, relative_path)
|
|
72
|
-
if relative_path.start_with?("
|
|
77
|
+
if relative_path.start_with?("@")
|
|
78
|
+
relative_path
|
|
79
|
+
elsif relative_path.start_with?("/")
|
|
73
80
|
relative_path.sub(%r{\A/}, "")
|
|
74
81
|
else
|
|
75
82
|
dir = File.dirname(from_url_path)
|
|
@@ -239,7 +246,7 @@ module Assiette
|
|
|
239
246
|
end
|
|
240
247
|
|
|
241
248
|
# Re-read a stale asset, re-parse deps, recursively ensure deps fresh, recompute digest.
|
|
242
|
-
# Returns false if the file went away while we were looking at it.
|
|
249
|
+
# Returns true, or false if the file went away while we were looking at it.
|
|
243
250
|
def rescan_asset!(asset)
|
|
244
251
|
url_path = asset.url_path
|
|
245
252
|
raw, mtime = read_or_drop!(asset)
|
|
@@ -265,6 +272,9 @@ module Assiette
|
|
|
265
272
|
|
|
266
273
|
# Propagate to dependents already in the graph
|
|
267
274
|
propagate_to_dependents!(asset)
|
|
275
|
+
|
|
276
|
+
# Not the walk's value: a `while` evaluates to nil, which reads as "gone"
|
|
277
|
+
true
|
|
268
278
|
end
|
|
269
279
|
|
|
270
280
|
# Recompute digests for all transitive dependents of an asset.
|
|
@@ -275,7 +285,7 @@ module Assiette
|
|
|
275
285
|
# renamed away lingers in the graph as an importer, and the next edit to
|
|
276
286
|
# anything it used to import tries to read a path that is not there. Its
|
|
277
287
|
# own dependents still have to hear about it: they keep the vanished node
|
|
278
|
-
# in their deps until they are rescanned, and it rewrites
|
|
288
|
+
# in their deps until they are rescanned, and it rewrites without ?s= now.
|
|
279
289
|
def propagate_to_dependents!(asset)
|
|
280
290
|
queue = asset.dependents.to_a
|
|
281
291
|
visited = Set.new
|
|
@@ -322,6 +332,7 @@ module Assiette
|
|
|
322
332
|
|
|
323
333
|
raw, _mtime = read_or_drop!(asset)
|
|
324
334
|
return unless raw
|
|
335
|
+
asset.served_digest = nil
|
|
325
336
|
if asset.deps.empty?
|
|
326
337
|
asset.digest = Digest::SHA256.digest(raw)
|
|
327
338
|
else
|
|
@@ -331,17 +342,19 @@ module Assiette
|
|
|
331
342
|
end
|
|
332
343
|
|
|
333
344
|
# For cyclic dependencies: hash all members' raw contents together.
|
|
345
|
+
# That shared digest only fingerprints - each member is served rewritten with
|
|
346
|
+
# its partners' ?s=, so SRI has to be taken over those bytes or the browser refuses it
|
|
334
347
|
def compute_cycle_digests(scc)
|
|
335
|
-
|
|
348
|
+
raws = scc.sort.each_with_object({}) do |url_path, acc|
|
|
336
349
|
asset = @assets[url_path]
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
350
|
+
raw = asset && read_or_drop!(asset)&.first
|
|
351
|
+
acc[url_path] = raw if raw
|
|
352
|
+
end
|
|
340
353
|
|
|
341
|
-
digest = Digest::SHA256.digest(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
354
|
+
digest = Digest::SHA256.digest(raws.values.join("\0"))
|
|
355
|
+
raws.each_key { |url_path| @assets[url_path].digest = digest }
|
|
356
|
+
raws.each do |url_path, raw|
|
|
357
|
+
@assets[url_path].served_digest = Digest::SHA256.digest(rewrite_content_internal(url_path, raw))
|
|
345
358
|
end
|
|
346
359
|
end
|
|
347
360
|
|
|
@@ -349,14 +362,14 @@ module Assiette
|
|
|
349
362
|
ext = File.extname(url_path)
|
|
350
363
|
case ext
|
|
351
364
|
when ".js", ".mjs", ".es"
|
|
352
|
-
Rewriter.rewrite_js_imports(raw_content) do |import_path|
|
|
365
|
+
Rewriter.rewrite_js_imports(raw_content, from: url_path) do |import_path|
|
|
353
366
|
resolved = resolve_import_for(url_path, import_path)
|
|
354
|
-
@assets[resolved]&.checksum_tag
|
|
367
|
+
@assets[resolved]&.checksum_tag
|
|
355
368
|
end
|
|
356
369
|
when ".css"
|
|
357
370
|
Rewriter.rewrite_css_urls(raw_content) do |ref_path|
|
|
358
371
|
resolved = resolve_import_for(url_path, ref_path)
|
|
359
|
-
@assets[resolved]&.checksum_tag
|
|
372
|
+
@assets[resolved]&.checksum_tag
|
|
360
373
|
end
|
|
361
374
|
else
|
|
362
375
|
raw_content
|
data/lib/assiette/helpers.rb
CHANGED
|
@@ -32,10 +32,17 @@ module Assiette
|
|
|
32
32
|
# innermost Server in the stack — on purpose. It renders a listing of
|
|
33
33
|
# everything a handler holds, so walking the stack would put one tenant's
|
|
34
34
|
# file list on another tenant's page.
|
|
35
|
-
|
|
35
|
+
#
|
|
36
|
+
# Of the exposed directories (AssetHandler#expose) it lists what the app's
|
|
37
|
+
# own modules import, directly or down the chain, and nothing else: the
|
|
38
|
+
# import is the selection. `exposed: "@my_gem"` lists all of that handle
|
|
39
|
+
# instead, for an `import()` the app computes at run time; `exposed: :all`
|
|
40
|
+
# lists every exposed module and `exposed: []` none. See
|
|
41
|
+
# AssetHandler#js_modules.
|
|
42
|
+
def assiette_modulepreload_tags(exposed: :imported)
|
|
36
43
|
entry = assiette_stack.last
|
|
37
44
|
handler = entry[:handler]
|
|
38
|
-
safe_join(handler.js_modules.map { |mod|
|
|
45
|
+
safe_join(handler.js_modules(exposed: exposed).map { |mod|
|
|
39
46
|
tag.link(rel: "modulepreload",
|
|
40
47
|
href: handler.absolute_asset_url_path(mod[:path], entry[:script_name]),
|
|
41
48
|
integrity: mod[:integrity], crossorigin: "anonymous")
|
data/lib/assiette/rewriter.rb
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
3
5
|
module Assiette
|
|
4
6
|
module Rewriter
|
|
5
7
|
# Matches quoted strings that look like relative/absolute JS import paths.
|
|
6
|
-
# Handles ./ ../ / but NOT protocol-relative
|
|
8
|
+
# Handles ./ ../ / but NOT protocol-relative //, and "@handle/..." for a
|
|
9
|
+
# directory exposed with AssetHandler#expose
|
|
7
10
|
# $1 = quote char, $2 = path including extension
|
|
8
|
-
JS_IMPORT_RE = /(["'])(
|
|
11
|
+
JS_IMPORT_RE = /(["'])((?:\.{0,2}\/(?!\/)|@[\w.-]+\/)[^"']*\.(?:js|mjs|es))\1/
|
|
9
12
|
|
|
10
13
|
# Matches url() in CSS with relative/absolute paths.
|
|
11
14
|
# Handles url(./path), url("./path"), url('../path'), url(/path)
|
|
@@ -16,25 +19,42 @@ module Assiette
|
|
|
16
19
|
module_function
|
|
17
20
|
|
|
18
21
|
# Rewrites JS imports with per-import hashes via a block.
|
|
19
|
-
# The block receives the import path and
|
|
20
|
-
|
|
22
|
+
# The block receives the import path and returns the hash for that import,
|
|
23
|
+
# or nil to leave the import unversioned.
|
|
24
|
+
#
|
|
25
|
+
# An "@handle/..." import that gets a hash is also turned into a path
|
|
26
|
+
# relative to `from` - the URL path of the file being rewritten - since a
|
|
27
|
+
# browser cannot resolve a bare "@handle" without an import map. Relative,
|
|
28
|
+
# so it keeps working under whatever SCRIPT_NAME the handler is mounted at.
|
|
29
|
+
# One that gets no hash is left as written, for an import map to resolve.
|
|
30
|
+
def rewrite_js_imports(source, from: "", &block)
|
|
21
31
|
source.gsub(JS_IMPORT_RE) do
|
|
22
32
|
quote = $1
|
|
23
33
|
path = $2
|
|
24
34
|
hash = yield(path)
|
|
35
|
+
next "#{quote}#{path}#{quote}" unless hash
|
|
36
|
+
path = relative_to(from, path) if path.start_with?("@")
|
|
25
37
|
"#{quote}#{path}?s=#{hash}#{quote}"
|
|
26
38
|
end
|
|
27
39
|
end
|
|
28
40
|
|
|
41
|
+
# "@x/y.js" as seen from the directory of `from`, always starting with ./
|
|
42
|
+
# or ../ - "y.js" alone would be a bare specifier again
|
|
43
|
+
def relative_to(from, path)
|
|
44
|
+
relative = Pathname.new(path).relative_path_from(Pathname.new(File.dirname(from))).to_s
|
|
45
|
+
relative.start_with?("../") ? relative : "./#{relative}"
|
|
46
|
+
end
|
|
47
|
+
|
|
29
48
|
# Rewrites CSS url() references with per-url hashes via a block.
|
|
30
|
-
# The block receives the url path and
|
|
49
|
+
# The block receives the url path and returns the hash for that url,
|
|
50
|
+
# or nil to leave the url unversioned.
|
|
31
51
|
def rewrite_css_urls(source, &block)
|
|
32
52
|
source.gsub(CSS_URL_RE) do
|
|
33
53
|
open = $1
|
|
34
54
|
path = $2
|
|
35
55
|
close = $3
|
|
36
56
|
hash = yield(path)
|
|
37
|
-
"url(#{open}#{path}?s=#{hash}#{close})"
|
|
57
|
+
hash ? "url(#{open}#{path}?s=#{hash}#{close})" : "url(#{open}#{path}#{close})"
|
|
38
58
|
end
|
|
39
59
|
end
|
|
40
60
|
|
data/lib/assiette/server.rb
CHANGED
|
@@ -18,6 +18,10 @@ module Assiette
|
|
|
18
18
|
def initialize(app, handler = nil, root: nil, additional_directory_mappings: {})
|
|
19
19
|
@app = app
|
|
20
20
|
@handler = handler || AssetHandler.new(root: root, additional_directory_mappings: additional_directory_mappings)
|
|
21
|
+
# In Rails a gem exposes its directory from ActiveSupport.on_load(:assiette), which runs on
|
|
22
|
+
# every handler a Server is built with - whenever the gem's block was registered. A handler
|
|
23
|
+
# made per request is not announced: the hooks keep every handler they ran on, for ever
|
|
24
|
+
ActiveSupport.run_load_hooks(:assiette, @handler) if defined?(Assiette::Railtie) && @handler.is_a?(AssetHandler)
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
def call(env)
|
data/lib/assiette/version.rb
CHANGED
|
@@ -16,6 +16,10 @@ handler = Assiette::AssetHandler.new(
|
|
|
16
16
|
}
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
# Gems that ship JS or CSS for Assiette expose their directories on this
|
|
20
|
+
# handler themselves, as "@some_gem" - import "@some_gem/file.js" from your own
|
|
21
|
+
# modules. Add your own with handler.expose("@name", path).
|
|
22
|
+
|
|
19
23
|
# Serve files through the shared handler.
|
|
20
24
|
Rails.application.config.middleware.use Assiette::Server, handler
|
|
21
25
|
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: assiette
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julik Tarkhanov
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-08 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: actionpack
|
|
@@ -53,6 +54,7 @@ metadata:
|
|
|
53
54
|
homepage_uri: https://github.com/julik/assiette
|
|
54
55
|
source_code_uri: https://github.com/julik/assiette
|
|
55
56
|
changelog_uri: https://github.com/julik/assiette/blob/main/CHANGELOG.md
|
|
57
|
+
post_install_message:
|
|
56
58
|
rdoc_options: []
|
|
57
59
|
require_paths:
|
|
58
60
|
- lib
|
|
@@ -67,7 +69,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
67
69
|
- !ruby/object:Gem::Version
|
|
68
70
|
version: '0'
|
|
69
71
|
requirements: []
|
|
70
|
-
rubygems_version: 3.
|
|
72
|
+
rubygems_version: 3.4.10
|
|
73
|
+
signing_key:
|
|
71
74
|
specification_version: 4
|
|
72
75
|
summary: Zero-build asset serving for Rails engines
|
|
73
76
|
test_files: []
|