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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec78b55ac1e82e265e1f5546a929ed3b12ab72585bc448d5f3c3d06c8804ec10
4
- data.tar.gz: 2df42517847826a826ebe32d90b8bbbe41fe242872e56db06672e5b5c1d4fb3b
3
+ metadata.gz: b6ac679135bef28a77e6bd0df80d65d9fff5af142fa5ed928a97355ed02e8488
4
+ data.tar.gz: 77d7593cc83a00dc06d6e4eda009837707f5912b0b6fff0ee43e4056193cfc27
5
5
  SHA512:
6
- metadata.gz: 49970a3c8e73e1a44f54ccca8bef6c31e55c56bd5c0b1a9194beef31ecc188ce6c54d225d02c68261e42f6c9a8fee15b160a492f0dd5bbba1be07097d7f6182f
7
- data.tar.gz: 109f67f9c64b8443d11404af38d33f154f94a3acef3a041b66499c3cfade22ad3516d6abb55cbb48c42ed2592e2a46575e4c52c6b9745f147f0af9fb84fbda4b
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 fast, lightweight, and straightforward web asset management library. Processed assets are all
4
- stored in and served directly from memory rather than being written to disk (though a dump to disk can be
5
- performed for upload to a CDN or proxy server in production environments); this keeps asset management
6
- simple and performant in development. Darkroom also supports asset bundling for CSS and JavaScript using
7
- each language's native import statement syntax.
8
-
9
- The following file types are supported out of the box, though support for others can be added (see the
10
- [Extending](#extending) section):
11
-
12
- | Name | Content Type | Extension(s) |
13
- |------------|------------------|--------------|
14
- | APNG | image/apng | .apng |
15
- | AVIF | image/avif | .avif |
16
- | CSS | text/css | .css |
17
- | GIF | image/gif | .gif |
18
- | HTML | text/html | .htm, .html |
19
- | HTX | text/javascript | .htx |
20
- | ICO | image/x-icon | .ico |
21
- | JavaScript | text/javascript | .js |
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 time that must elapse between process calls
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
- CSS and JavaScript assets specify their dependencies by way of each language's native import statement. Each
145
- import statement is replaced with the content of the imported asset. Example:
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
- By default, JavaScript files are concatenated in the same way that CSS files are. Example:
147
+ ### JavaScript - Single Scope
172
148
 
173
- ```javascript
174
- // Unprocessed /api.js
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
- ```javascript
179
- // Unprocessed /app.js
180
- import '/api.js'
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
- ```javascript
186
- // Processed /app.js
187
- function API() { console.log('API called!') }
170
+ API()
171
+ ```
188
172
 
189
- API()
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 (indentation is not quite as
194
- pretty as shown here, but has been altered here for readability):
195
-
196
- ```javascript
197
- // Unprocessed /api.js
198
- export function API() { console.log('API called!') }
199
- ```
200
-
201
- ```javascript
202
- // Unprocessed /app.js
203
- import {API} from '/api.js'
204
-
205
- API()
206
- ```
207
-
208
- ```javascript
209
- // Processed /app.js
210
- ((...bundle) => {
211
- const modules = {}
212
- const setters = []
213
- const $import = (name, setter) =>
214
- modules[name] ? setter(modules[name]) : setters.push([setter, name])
215
-
216
- for (const [name, def] of bundle)
217
- modules[name] = def($import)
218
-
219
- for (const [setter, name] of setters)
220
- setter(modules[name])
221
- })(
222
- ['/api.js', $import => {
223
- function API() { console.log('API called!') }
224
-
225
- return Object.seal({
226
- API: API,
227
- })
228
- }],
229
-
230
- ['/app.js', $import => {
231
- let API; $import('/api.js', m => API = m.API)
232
-
233
- API()
234
-
235
- return Object.seal({})
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
- Darkroom is extensible. Support for arbitrary file types can be added by specifying one or more extensions
294
- and a content type:
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, the DSL can be used one of three ways. With a block:
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 parse handlers.
360
- # match: - MatchData object from the match against the provided regex.
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 #{Asset::QUOTED_PATH_REGEX.source};/) do |parse_data:, match:, asset:|
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
- # parse_data: - Hash for storing data across calls to this and other parse handlers.
406
- # match: - MatchData object from the match against the provided regex.
407
- # asset: - Asset object of the asset being referenced.
408
- # format: - Format of the reference (see Asset::REFERENCE_FORMATS).
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
- # parse_data: - Hash for storing data across calls to this and other parse handlers.
441
- # match: - MatchData object from the match against the provided regex.
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
- # parse_data: - Hash of data collected during parsing.
475
- # path: - Path of the asset being compiled.
476
- # own_content: - Asset's own content (without imports).
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
- # parse_data: - Hash of data collected during parsing.
495
- # path: - Path of the asset being finalized.
496
- # content: - Asset's compiled content (with imports prepended).
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
- # parse_data: - Hash of data collected during parsing.
515
- # path: - Path of the asset being finalized.
516
- # content: - Asset's finalized content.
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.9
1
+ 0.0.10