darkroom 0.0.9 → 0.0.10
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 +9 -0
- data/README.md +175 -131
- data/VERSION +1 -1
- data/lib/darkroom/asset.rb +173 -151
- data/lib/darkroom/darkroom.rb +140 -124
- data/lib/darkroom/delegate.rb +208 -101
- data/lib/darkroom/delegates/{css.rb → css_delegate.rb} +1 -0
- data/lib/darkroom/delegates/{html.rb → html_delegate.rb} +4 -3
- data/lib/darkroom/delegates/{htx.rb → htx_delegate.rb} +3 -2
- data/lib/darkroom/delegates/{javascript.rb → javascript_delegate.rb} +9 -8
- data/lib/darkroom/errors/asset_error.rb +6 -17
- data/lib/darkroom/errors/asset_not_found_error.rb +4 -8
- data/lib/darkroom/errors/circular_reference_error.rb +4 -8
- data/lib/darkroom/errors/duplicate_asset_error.rb +7 -16
- data/lib/darkroom/errors/invalid_path_error.rb +5 -14
- data/lib/darkroom/errors/missing_library_error.rb +7 -16
- data/lib/darkroom/errors/processing_error.rb +13 -20
- data/lib/darkroom/errors/unrecognized_extension_error.rb +4 -8
- data/lib/darkroom/version.rb +1 -1
- data/lib/darkroom.rb +4 -6
- metadata +17 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6ac679135bef28a77e6bd0df80d65d9fff5af142fa5ed928a97355ed02e8488
|
4
|
+
data.tar.gz: 77d7593cc83a00dc06d6e4eda009837707f5912b0b6fff0ee43e4056193cfc27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb0eda914cb475f656410c2d25188726c4e3697ba11e5877810c14dcf2639ae3340a77ba8982a1b0ebf86d2f9570ebb1cf01e756983fdd591fa6bf23eb3ad65b
|
7
|
+
data.tar.gz: bef29d0cfea183be2350772684c64baea0d2ee2751f46759d7ea540a2a9e46c61eef1a4c441c80cff201f46e10c85a25f602da18b486bde6b9127ed878f21864
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,15 @@
|
|
4
4
|
|
5
5
|
* Nothing yet
|
6
6
|
|
7
|
+
## 0.0.10 (2025 February 19)
|
8
|
+
|
9
|
+
* Include a few more instance variables in `Asset#inspect`
|
10
|
+
* **Require Ruby 3.0.0 or later**
|
11
|
+
* Make ProcessingError enumerable
|
12
|
+
* **Ensure import and reference regexes have the required named captures**
|
13
|
+
* Gracefully handle invalid reference entities
|
14
|
+
* **Fix delegate finalize library not getting required**
|
15
|
+
|
7
16
|
## 0.0.9 (2025 January 21)
|
8
17
|
|
9
18
|
* **Remove deprecated delegate creation via hash and associated accessors**
|
data/README.md
CHANGED
@@ -1,32 +1,24 @@
|
|
1
1
|
# Darkroom
|
2
2
|
|
3
|
-
Darkroom is a
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
each language's native import statement
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
| JPEG | image/jpeg | .jpg, .jpeg |
|
23
|
-
| JSON | application/json | .json |
|
24
|
-
| PNG | image/png | .png |
|
25
|
-
| SVG | image/svg+xml | .svg |
|
26
|
-
| Text | text/plain | .txt |
|
27
|
-
| WebP | image/webp | .webp |
|
28
|
-
| WOFF | font/woff | .woff |
|
29
|
-
| WOFF2 | font/woff2 | .woff2 |
|
3
|
+
Darkroom is a simple and straightforward web asset management and bundling library written entirely in Ruby.
|
4
|
+
It is designed to be used directly within a Ruby web server process—no external dependencies or process
|
5
|
+
management is needed.
|
6
|
+
|
7
|
+
* **Asset Bundling** — CSS and JavaScript assets are bundled using each language's native import statement
|
8
|
+
syntax.
|
9
|
+
* `@import '/header.css';` includes the contents of header.css in a CSS asset.
|
10
|
+
* `import '/api.js'` includes the contents of api.js in a JavaScript asset.
|
11
|
+
* **Asset References** — Asset paths are versioned with a URL query parameter.
|
12
|
+
* `<img src='/logo.svg?asset-path'>` gets replaced with `<img src='/logo-[fingerprint].svg'>`.
|
13
|
+
* **Asset Inlining** — Certain kinds of assets (e.g. images in HTML documents) can be inlined using a URL
|
14
|
+
query parameter.
|
15
|
+
* `<img src='/logo.svg?asset-content=utf8'>` gets replaced with
|
16
|
+
`<img src='data:image/svg+xml;utf8,<svg>[...]</svg>'>`.
|
17
|
+
* **JavaScript Modules** — JavaScript bundles can (optionally) encapsulate the content of each file with
|
18
|
+
imports defined as local variables to mimic native ES6 modules within a single file.
|
19
|
+
* **In-Memory** — Processed assets are all stored in and served directly from memory to avoid the issues
|
20
|
+
that generally ensue from writing to and managing files on disk. Assets can however be dumped to disk for
|
21
|
+
upload to a CDN or proxy server in production environments.
|
30
22
|
|
31
23
|
## Installation
|
32
24
|
|
@@ -59,6 +51,8 @@ To create and start using a Darkroom instance, specify one or more load paths (a
|
|
59
51
|
optional):
|
60
52
|
|
61
53
|
```ruby
|
54
|
+
Darkroom.javascript_iife = true # Use IIFEs to emulate ES6-style JavaScript modules
|
55
|
+
|
62
56
|
darkroom = Darkroom.new('app/assets', 'vendor/assets', '...',
|
63
57
|
hosts: [ # Hosts to prepend to asset paths (useful in production
|
64
58
|
'https://cname1.cdn.com', # when assets are served from a CDN with multiple
|
@@ -75,7 +69,8 @@ darkroom = Darkroom.new('app/assets', 'vendor/assets', '...',
|
|
75
69
|
minify: true, # Minify assets that can be minified
|
76
70
|
minified: /(\.|-)min\.\w+$/, # Files to skip minification on when minify: true; can
|
77
71
|
# be a string, regex, or array of such
|
78
|
-
min_process_interval: 1, # Minimum
|
72
|
+
min_process_interval: 1, # Minimum seconds that must elapse between process
|
73
|
+
# calls (otherwise processing is skipped)
|
79
74
|
)
|
80
75
|
```
|
81
76
|
|
@@ -141,101 +136,120 @@ asset.integrity(:sha512) # => "sha512-[hash]"
|
|
141
136
|
|
142
137
|
## Asset Bundling
|
143
138
|
|
144
|
-
|
145
|
-
import statement is replaced with the content of the imported asset.
|
146
|
-
|
147
|
-
```css
|
148
|
-
/* Unprocessed /header.css */
|
149
|
-
header { background: #f1f1f1; }
|
150
|
-
```
|
151
|
-
|
152
|
-
```css
|
153
|
-
/* Unprocessed /app.css */
|
154
|
-
@import '/header.css';
|
155
|
-
|
156
|
-
body { background: #fff; }
|
157
|
-
```
|
158
|
-
|
159
|
-
```css
|
160
|
-
/* Processed /app.css */
|
161
|
-
header { background: #f1f1f1; }
|
162
|
-
|
163
|
-
body { background: #fff; }
|
164
|
-
```
|
139
|
+
JavaScript and CSS assets specify their dependencies by way of each language's native import statement. Each
|
140
|
+
import statement is replaced with the content of the imported asset.
|
165
141
|
|
166
142
|
Imported assets can also contain import statements, and those assets are all included in the base asset.
|
167
143
|
Imports can even be cyclical. If `asset-a.css` imports `asset-b.css` and vice-versa, each asset will simply
|
168
144
|
contain the content of both of those assets (though order will be different as an asset's own content always
|
169
145
|
comes after any imported assets' contents).
|
170
146
|
|
171
|
-
|
147
|
+
### JavaScript - Single Scope
|
172
148
|
|
173
|
-
|
174
|
-
|
175
|
-
function API() { console.log('API called!') }
|
176
|
-
```
|
149
|
+
By default, JavaScript bundles are a simple concatenation of files. Imports should all be "side effect"
|
150
|
+
style and result in all code using one shared scope:
|
177
151
|
|
178
|
-
|
179
|
-
|
180
|
-
|
152
|
+
* **/api.js** (raw file)
|
153
|
+
```javascript
|
154
|
+
function API() {
|
155
|
+
console.log('API called!')
|
156
|
+
}
|
157
|
+
```
|
158
|
+
* **/app.js** (raw file)
|
159
|
+
```javascript
|
160
|
+
import '/api.js'
|
181
161
|
|
182
|
-
API()
|
183
|
-
```
|
162
|
+
API()
|
163
|
+
```
|
164
|
+
* **/app.js** (processed)
|
165
|
+
```javascript
|
166
|
+
function API() {
|
167
|
+
console.log('API called!')
|
168
|
+
}
|
184
169
|
|
185
|
-
|
186
|
-
|
187
|
-
function API() { console.log('API called!') }
|
170
|
+
API()
|
171
|
+
```
|
188
172
|
|
189
|
-
|
190
|
-
```
|
173
|
+
### JavaScript - Modules
|
191
174
|
|
192
175
|
Alternatively, setting `Darkroom.javascript_iife = true` will cause JavaScript assets to be compiled to a
|
193
|
-
series of IIFEs that provide the same encapsulation as native ES6 modules
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
export function API() {
|
199
|
-
|
200
|
-
|
201
|
-
```
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
((...bundle) => {
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
})(
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
)
|
238
|
-
|
176
|
+
series of IIFEs that provide the same encapsulation as native ES6 modules. In this case, objects must be
|
177
|
+
explicitly exported to be importable and named (rather than side effect) imports used:
|
178
|
+
|
179
|
+
* **/api.js** (raw file)
|
180
|
+
```javascript
|
181
|
+
export function API() {
|
182
|
+
console.log('API called!')
|
183
|
+
}
|
184
|
+
```
|
185
|
+
* **/app.js** (raw file)
|
186
|
+
```javascript
|
187
|
+
import {API} from '/api.js'
|
188
|
+
|
189
|
+
API()
|
190
|
+
```
|
191
|
+
* **/app.js** (processed)
|
192
|
+
```javascript
|
193
|
+
((...bundle) => {
|
194
|
+
const modules = {}
|
195
|
+
const setters = []
|
196
|
+
const $import = (name, setter) =>
|
197
|
+
modules[name] ? setter(modules[name]) : setters.push([setter, name])
|
198
|
+
|
199
|
+
for (const [name, def] of bundle)
|
200
|
+
modules[name] = def($import)
|
201
|
+
|
202
|
+
for (const [setter, name] of setters)
|
203
|
+
setter(modules[name])
|
204
|
+
})(
|
205
|
+
['/api.js', $import => {
|
206
|
+
function API() {
|
207
|
+
console.log('API called!')
|
208
|
+
}
|
209
|
+
|
210
|
+
return Object.seal({
|
211
|
+
API: API,
|
212
|
+
})
|
213
|
+
}],
|
214
|
+
|
215
|
+
['/app.js', $import => {
|
216
|
+
let API; $import('/api.js', m => API = m.API)
|
217
|
+
|
218
|
+
API()
|
219
|
+
|
220
|
+
return Object.seal({})
|
221
|
+
}],
|
222
|
+
)
|
223
|
+
```
|
224
|
+
|
225
|
+
### CSS
|
226
|
+
|
227
|
+
CSS imports are always just a simple concatenation.
|
228
|
+
|
229
|
+
* **/header.css** (raw file)
|
230
|
+
```css
|
231
|
+
header {
|
232
|
+
background: #f1f1f1;
|
233
|
+
}
|
234
|
+
```
|
235
|
+
* **/app.css** (raw file)
|
236
|
+
```css
|
237
|
+
@import '/header.css';
|
238
|
+
|
239
|
+
body {
|
240
|
+
background: #fff;
|
241
|
+
}
|
242
|
+
```
|
243
|
+
* **/app.css** (processed)
|
244
|
+
```css
|
245
|
+
header {
|
246
|
+
background: #f1f1f1;
|
247
|
+
}
|
248
|
+
|
249
|
+
body {
|
250
|
+
background: #fff;
|
251
|
+
}
|
252
|
+
```
|
239
253
|
|
240
254
|
## Asset References
|
241
255
|
|
@@ -290,8 +304,29 @@ replaced appropriately.
|
|
290
304
|
|
291
305
|
## Extending
|
292
306
|
|
293
|
-
|
294
|
-
|
307
|
+
The following file types are supported out of the box:
|
308
|
+
|
309
|
+
| Name | Content Type | Extension(s) |
|
310
|
+
|------------|------------------|--------------|
|
311
|
+
| APNG | image/apng | .apng |
|
312
|
+
| AVIF | image/avif | .avif |
|
313
|
+
| CSS | text/css | .css |
|
314
|
+
| GIF | image/gif | .gif |
|
315
|
+
| HTML | text/html | .htm, .html |
|
316
|
+
| HTX | text/javascript | .htx |
|
317
|
+
| ICO | image/x-icon | .ico |
|
318
|
+
| JavaScript | text/javascript | .js |
|
319
|
+
| JPEG | image/jpeg | .jpg, .jpeg |
|
320
|
+
| JSON | application/json | .json |
|
321
|
+
| PNG | image/png | .png |
|
322
|
+
| SVG | image/svg+xml | .svg |
|
323
|
+
| Text | text/plain | .txt |
|
324
|
+
| WebP | image/webp | .webp |
|
325
|
+
| WOFF | font/woff | .woff |
|
326
|
+
| WOFF2 | font/woff2 | .woff2 |
|
327
|
+
|
328
|
+
But Darkroom is extensible, allowing support for any kind of asset to be added. This is done most simply by
|
329
|
+
specifying one or more extensions and a content type:
|
295
330
|
|
296
331
|
```ruby
|
297
332
|
Darkroom.register('.ext1', '.ext2', '...', 'content/type')
|
@@ -299,7 +334,7 @@ Darkroom.register('.ext1', '.ext2', '...', 'content/type')
|
|
299
334
|
|
300
335
|
### DSL
|
301
336
|
|
302
|
-
For more advanced functionality,
|
337
|
+
For more advanced functionality, a DSL is provided which can be used one of three ways. With a block:
|
303
338
|
|
304
339
|
```ruby
|
305
340
|
Darkroom.register('.ext1', '.ext2', '...') do
|
@@ -356,10 +391,10 @@ Darkroom.register('.ext1', '.ext2', '...') do
|
|
356
391
|
# ...
|
357
392
|
|
358
393
|
# The (optional) block is passed three keyword arguments:
|
359
|
-
# parse_data: - Hash for storing data across calls to this and other
|
360
|
-
# match: - MatchData object from the match against the
|
394
|
+
# parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
|
395
|
+
# match: - MatchData object from the match against the regex.
|
361
396
|
# asset: - Asset object of the asset being imported.
|
362
|
-
import(/import
|
397
|
+
import(/import '(?<path>[^']+)';/) do |parse_data:, match:, asset:|
|
363
398
|
parse_data[:imports] ||= [] # Accumulate and use arbitrary parse data.
|
364
399
|
parse_data[:imports] << match[:path] # Use the MatchData object of the regex match.
|
365
400
|
|
@@ -402,10 +437,11 @@ Darkroom.register('.ext1', '.ext2', '...') do
|
|
402
437
|
reference_regex = /ref=#{Asset::REFERENCE_REGEX.source}/x
|
403
438
|
|
404
439
|
# The (optional) block is passed four keyword arguments:
|
405
|
-
#
|
406
|
-
#
|
407
|
-
#
|
408
|
-
#
|
440
|
+
#
|
441
|
+
# parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
|
442
|
+
# match: - MatchData object from the match against the regex.
|
443
|
+
# asset: - Asset object of the asset being referenced.
|
444
|
+
# format: - String format of the reference (see Asset::REFERENCE_FORMATS).
|
409
445
|
reference(reference_regex) do |parse_data:, match:, asset:, format:|
|
410
446
|
parse_data[:refs] ||= [] # Accumulate and use arbitrary parse data.
|
411
447
|
parse_data[:refs] << match[:path] # Use the MatchData object of the regex match.
|
@@ -437,8 +473,9 @@ Darkroom.register('.ext1', '.ext2', '...') do
|
|
437
473
|
# ...
|
438
474
|
|
439
475
|
# The block is passed two keyword arguments:
|
440
|
-
#
|
441
|
-
#
|
476
|
+
#
|
477
|
+
# parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
|
478
|
+
# match: - MatchData object from the match against the regex.
|
442
479
|
parse(:exports, /export (?<name>.+)/) do |parse_data:, match:|
|
443
480
|
parse_data[:exports] ||= [] # Accumulate and use arbitrary parse data.
|
444
481
|
parse_data[:exports] << match[:name] # Use the MatchData object of the regex match.
|
@@ -466,14 +503,19 @@ end
|
|
466
503
|
Compilation allows for a library to require (optional), a delegate to use after compilation (optional), and
|
467
504
|
a block that returns the compiled version of the asset's own content.
|
468
505
|
|
506
|
+
Passing a delegate will cause the asset to be treated as if it is that type once it has been compiled. For
|
507
|
+
example, HTX templates use `delegate: JavaScriptDelegate` because they get compiled to JavaScript and thus
|
508
|
+
should be treated as JavaScript files post compilation.
|
509
|
+
|
469
510
|
```ruby
|
470
511
|
Darkroom.register('.ext1', '.ext2', '...') do
|
471
512
|
# ...
|
472
513
|
|
473
514
|
# The block is passed three keyword arguments:
|
474
|
-
#
|
475
|
-
#
|
476
|
-
#
|
515
|
+
#
|
516
|
+
# parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
|
517
|
+
# path: - String path of the asset.
|
518
|
+
# own_content: - String own content (without imports) of the asset.
|
477
519
|
compile(lib: 'compile_lib', delegate: SomeDelegate) do |parse_data:, path:, own_content:|
|
478
520
|
CompileLib.compile(own_content)
|
479
521
|
end
|
@@ -491,9 +533,10 @@ Darkroom.register('.ext1', '.ext2', '...') do
|
|
491
533
|
# ...
|
492
534
|
|
493
535
|
# The block is passed three keyword arguments:
|
494
|
-
#
|
495
|
-
#
|
496
|
-
#
|
536
|
+
#
|
537
|
+
# parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
|
538
|
+
# path: - String path of the asset.
|
539
|
+
# content: - String content of the compiled asset (with imports prepended).
|
497
540
|
finalize(lib: 'finalize_lib') do |parse_data:, path:, content:|
|
498
541
|
FinalizeLib.finalize(content)
|
499
542
|
end
|
@@ -511,9 +554,10 @@ Darkroom.register('.ext1', '.ext2', '...') do
|
|
511
554
|
# ...
|
512
555
|
|
513
556
|
# The block is passed three keyword arguments:
|
514
|
-
#
|
515
|
-
#
|
516
|
-
#
|
557
|
+
#
|
558
|
+
# parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
|
559
|
+
# path: - String oath of the asset being minified.
|
560
|
+
# content: - string content of the finalized asset.
|
517
561
|
minify(lib: 'minify_lib') do |parse_data:, path:, content:|
|
518
562
|
MinifyLib.compress(content)
|
519
563
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.10
|