shakapacker 9.3.0.beta.5 → 9.3.0.beta.6

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: 9db05ecb02b4491f31eb4ec2374057933be5cf90a09b6dded781c036ff8e494e
4
- data.tar.gz: a8f9b04812aff8a73275044f61a1ecbc096c5131a3bca69d5b75e292c1c31064
3
+ metadata.gz: 9b7929f32b14b49a2fde4893b66809a37147796c685be9161c12631369105624
4
+ data.tar.gz: 2601ae85b072d8286700558a5d92f424d6e5989f7e4d990caf2b57c939b1962c
5
5
  SHA512:
6
- metadata.gz: 88f63fdaaed2cfad42f000d97e08fec78dddaa24b304dd23ae515ba24b5b68845e33115adf13994f0a59f85b6be4003a442e8d5f88a1a9295484ca0b31e07fc1
7
- data.tar.gz: 59069f26686a609a9df9994ec63a1cbc5905d18abbb7379c0460913f16ac58247e760d97eecc544a31a86e212d6af1d171b99f462f4b60b937d0da81d3415a7f
6
+ metadata.gz: 9459cd0ec016ad83df86dec2ae879be51d1dc0dbd74fa9047b401ea0c0cb5d264438e43445487974d94bb4c82db5d5116ed9b3f5c3219e0d1f74cea65c7b46bc
7
+ data.tar.gz: 2f77e883a93ff0c80a2f22871d53646b16d94e1ffa648af78f167b699431cd026e8e9c5707a3379d57fd22e588ad7e526673afb324f4997cff5924bb5f2dceef
data/CHANGELOG.md CHANGED
@@ -13,6 +13,18 @@ Changes since the last non-beta release.
13
13
 
14
14
  ### Added
15
15
 
16
+ - **Support for arbitrary output names in build configurations**. [PR #752](https://github.com/shakacode/shakapacker/pull/752) by [justin808](https://github.com/justin808).
17
+ - `outputs` array now accepts any custom names (e.g., `client-modern`, `client-legacy`, `server-bundle`)
18
+ - Previously limited to only `client`, `server`, and `all`
19
+ - Enables better organization of multi-config webpack builds
20
+ - **Enhanced error reporting in config exporter**. [PR #752](https://github.com/shakacode/shakapacker/pull/752) by [justin808](https://github.com/justin808).
21
+ - Shows detailed environment variable state when config functions fail
22
+ - Provides actionable suggestions based on error patterns (e.g., missing NODE_ENV)
23
+ - Improved formatting with clear sections for easier debugging
24
+ - **Config count validation for build outputs**. [PR #752](https://github.com/shakacode/shakapacker/pull/752) by [justin808](https://github.com/justin808).
25
+ - Validates webpack/rspack config array length matches `outputs` array
26
+ - Clear error messages when mismatch detected
27
+ - Suggests fixes with example configuration
16
28
  - **HTTP 103 Early Hints support** for faster asset loading. [PR #722](https://github.com/shakacode/shakapacker/pull/722) by [justin808](https://github.com/justin808). Fixes [#721](https://github.com/shakacode/shakapacker/issues/721).
17
29
  - **Automatic sending**: Early hints are sent automatically when `early_hints: enabled: true` in `shakapacker.yml`
18
30
  - **Per-page configuration**: Configure hints per-controller/action with `preload`/`prefetch`/`none` options
@@ -31,7 +43,7 @@ Changes since the last non-beta release.
31
43
  - **Performance note**: May improve or hurt page load performance depending on content - careful testing advised
32
44
  - See [Early Hints Guide](docs/early_hints.md) for detailed usage and advanced patterns
33
45
 
34
- ## [v9.3.0-beta.0] - October 13, 2025
46
+ ## [v9.3.0-beta.5] - October 18, 2025
35
47
 
36
48
  ### Added
37
49
 
@@ -53,6 +65,15 @@ Changes since the last non-beta release.
53
65
  - **Build timing logs** for webpack and rspack. [PR #706](https://github.com/shakacode/shakapacker/pull/706) by [justin808](https://github.com/justin808).
54
66
  - Shows duration of build operations
55
67
  - Helps identify performance bottlenecks
68
+ - **Named build configurations with `--build` flag**. [PR #728](https://github.com/shakacode/shakapacker/pull/728) by [justin808](https://github.com/justin808).
69
+ - Allows specifying custom build configurations: `bin/shakapacker --build=production` or `bin/shakapacker --build=test`
70
+ - Useful for creating specialized build configurations for different deployment environments
71
+ - **Build validation in `bin/export-bundler-config`**. [PR #717](https://github.com/shakacode/shakapacker/pull/717) by [justin808](https://github.com/justin808).
72
+ - Validates webpack/rspack configuration before export to catch errors early
73
+ - Provides clear error messages for configuration issues
74
+ - **Backward compatibility for rspack config in `config/webpack/`**. [PR #734](https://github.com/shakacode/shakapacker/pull/734) by [justin808](https://github.com/justin808).
75
+ - Rspack configurations can now be placed in `config/webpack/` directory for easier migration
76
+ - Maintains compatibility with existing project structures
56
77
  - Added Knip for detecting dead code to CI. [PR #675](https://github.com/shakacode/shakapacker/pull/675) by [justin808](https://github.com/justin808).
57
78
 
58
79
  ### Changed
@@ -80,6 +101,12 @@ Changes since the last non-beta release.
80
101
  - **Improved doctor command output** clarity and accuracy. [PR #682](https://github.com/shakacode/shakapacker/pull/682) by [justin808](https://github.com/justin808).
81
102
  - Better formatting and organization of diagnostic information
82
103
  - More actionable recommendations
104
+ - **Documented `content_for` pattern to prevent FOUC** with `stylesheet_pack_tag`. [PR #737](https://github.com/shakacode/shakapacker/pull/737) by [justin808](https://github.com/justin808).
105
+ - Added documentation on using `content_for` to prevent Flash of Unstyled Content
106
+ - Best practice patterns for managing stylesheet loading order
107
+ - **Improved upgrade documentation** to clarify dual Gemfile and package.json updates. [PR #731](https://github.com/shakacode/shakapacker/pull/731) by [justin808](https://github.com/justin808).
108
+ - Clearer instructions for upgrading both Ruby gem and NPM package
109
+ - Helps prevent version mismatch issues
83
110
  - Formatted all markdown files with prettier. [PR #673](https://github.com/shakacode/shakapacker/pull/673) by [justin808](https://github.com/justin808).
84
111
 
85
112
  ### Fixed
@@ -752,8 +779,8 @@ Note: [Rubygem is 6.3.0.pre.rc.1](https://rubygems.org/gems/shakapacker/versions
752
779
 
753
780
  See [CHANGELOG.md in rails/webpacker (up to v5.4.3)](https://github.com/rails/webpacker/blob/master/CHANGELOG.md)
754
781
 
755
- [Unreleased]: https://github.com/shakacode/shakapacker/compare/v9.3.0-beta.0...main
756
- [v9.3.0-beta.0]: https://github.com/shakacode/shakapacker/compare/v9.2.0...v9.3.0-beta.0
782
+ [Unreleased]: https://github.com/shakacode/shakapacker/compare/v9.3.0-beta.5...main
783
+ [v9.3.0-beta.5]: https://github.com/shakacode/shakapacker/compare/v9.2.0...v9.3.0-beta.5
757
784
  [v9.2.0]: https://github.com/shakacode/shakapacker/compare/v9.1.0...v9.2.0
758
785
  [v9.1.0]: https://github.com/shakacode/shakapacker/compare/v9.0.0...v9.1.0
759
786
  [v9.0.0]: https://github.com/shakacode/shakapacker/compare/v8.4.0...v9.0.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shakapacker (9.3.0.beta.5)
4
+ shakapacker (9.3.0.beta.6)
5
5
  activesupport (>= 5.2)
6
6
  package_json
7
7
  rack-proxy (>= 0.6.1)
data/docs/early_hints.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  This guide shows you how to use HTTP 103 Early Hints with Shakapacker to optimize page load performance.
4
4
 
5
+ > **📚 Related Documentation:** For advanced manual control using `send_pack_early_hints` in controllers before expensive work, see [early_hints_manual_api.md](early_hints_manual_api.md).
6
+
5
7
  ## What are Early Hints?
6
8
 
7
9
  HTTP 103 Early Hints is emitted **after** Rails has finished rendering but **before** the final response is sent, allowing browsers to begin fetching resources (JS, CSS) prior to receiving the full HTML response. This may significantly improve page load performance or cause an equally significant regression, depending on the page's content.
@@ -316,29 +318,13 @@ Shakapacker automatically sends early hints after your views render:
316
318
 
317
319
  ## Advanced: Manual Control
318
320
 
319
- Most apps should use controller configuration. Use manual control for view-specific logic:
320
-
321
- ```erb
322
- <%# app/views/layouts/application.html.erb %>
323
- <% send_pack_early_hints css: 'prefetch', js: 'preload' %>
321
+ Most apps should use controller configuration. For advanced use cases including:
324
322
 
325
- <!DOCTYPE html>
326
- <html>
327
- <body>
328
- <%= yield %>
329
- <%= javascript_pack_tag 'application' %>
330
- </body>
331
- </html>
332
- ```
333
-
334
- **Per-tag override:**
335
-
336
- ```erb
337
- <%= javascript_pack_tag 'application',
338
- early_hints: { css: 'preload', js: 'prefetch' } %>
339
- ```
323
+ - Sending hints **before** expensive controller work for maximum parallelism
324
+ - Per-pack customization in layouts
325
+ - View-specific logic
340
326
 
341
- **Use cases:** Layout-specific optimizations, conditional hints based on view variables, A/B testing
327
+ See the [Manual API Guide](early_hints_manual_api.md) for detailed examples and patterns.
342
328
 
343
329
  ## Requirements
344
330
 
@@ -1,4 +1,16 @@
1
- # HTTP 103 Early Hints - New API
1
+ # HTTP 103 Early Hints - Manual API Guide
2
+
3
+ > **📚 Main Documentation:** This guide covers the manual `send_pack_early_hints` API for advanced use cases. For the recommended controller-based API (`configure_pack_early_hints`, `skip_send_pack_early_hints`) and comprehensive setup instructions, see [early_hints.md](early_hints.md).
4
+
5
+ This guide focuses on **manual control** of early hints for advanced scenarios where you need to send hints before expensive controller work or customize hints per-pack in layouts.
6
+
7
+ ## Automatic vs Manual API
8
+
9
+ By default, Shakapacker automatically sends early hints when `javascript_pack_tag` and `stylesheet_pack_tag` are called (after views render). The manual API allows you to:
10
+
11
+ - **Send hints earlier** - Before controller work starts, maximizing parallelism
12
+ - **Customize per-pack** - Different strategies for different packs in the same layout
13
+ - **Override automatic behavior** - When you need fine-grained control
2
14
 
3
15
  ## ⚠️ IMPORTANT: Performance Testing Required
4
16
 
@@ -14,73 +26,21 @@
14
26
  2. Measure Core Web Vitals (LCP, FCP, TTI) for both groups
15
27
  3. Only keep enabled if metrics improve
16
28
 
17
- See [Troubleshooting](#performance-got-worse) if early hints decrease performance.
18
-
19
- ---
20
-
21
- ## Prerequisites
22
-
23
- Before implementing early hints, verify you have:
24
-
25
- 1. **Puma 5+** with `--early-hints` flag (REQUIRED)
26
- 2. **HTTP/2-capable proxy** in front of Puma:
27
- - ✅ Thruster (Rails 8 default)
28
- - ✅ nginx 1.13+
29
- - ✅ Cloudflare (paid plans)
30
- - ❌ Control Plane (strips 103 responses)
31
- - ❌ AWS ALB/ELB (strips 103 responses)
32
- 3. **Rails 5.2+** (for `request.send_early_hints` API)
33
-
34
- **Critical**: Puma requires the `--early-hints` flag to send HTTP 103:
35
-
36
- ```bash
37
- # Procfile / Dockerfile
38
- web: bundle exec puma --early-hints -C config/puma.rb
39
- ```
40
-
41
- Without this flag, early hints will NOT work. See [Setup](#production-setup) for details.
42
-
43
- ---
44
-
45
- ## Quick Start
29
+ See the [Feature Testing Guide](feature_testing.md#http-103-early-hints) for testing instructions and the [main documentation](early_hints.md) for comprehensive troubleshooting.
46
30
 
47
- ### Pattern 1: Automatic (Default)
31
+ ## When to Use the Manual API
48
32
 
49
- By default, `javascript_pack_tag` and `stylesheet_pack_tag` automatically send early hints when early hints are enabled in config:
50
-
51
- ```yaml
52
- # config/shakapacker.yml
53
- production:
54
- early_hints:
55
- enabled: true
56
- ```
57
-
58
- ```erb
59
- <%# app/views/layouts/application.html.erb %>
60
- <!DOCTYPE html>
61
- <html>
62
- <head>
63
- <%# Automatically sends early hints for application pack CSS %>
64
- <%= stylesheet_pack_tag 'application' %>
65
- </head>
66
- <body>
67
- <%= yield %>
68
- <%# Automatically sends early hints for application pack JS %>
69
- <%= javascript_pack_tag 'application' %>
70
- </body>
71
- </html>
72
- ```
33
+ Use `send_pack_early_hints` when you need:
73
34
 
74
- **How it works:**
35
+ 1. **Maximum parallelism** - Send hints BEFORE expensive controller work (database queries, API calls)
36
+ 2. **Per-pack customization** - Different hint strategies for different packs in layouts
37
+ 3. **Dynamic control** - Runtime decisions about which packs to hint
75
38
 
76
- - When `stylesheet_pack_tag` is called, it automatically sends CSS early hints
77
- - When `javascript_pack_tag` is called, it automatically sends JS early hints
78
- - Combines queue (from `append_*_pack_tag`) + direct args
79
- - Default: `rel=preload` for all packs
39
+ For most applications, use the [controller-based API](early_hints.md#controller-configuration) instead (`configure_pack_early_hints`, `skip_send_pack_early_hints`).
80
40
 
81
- ---
41
+ ## Manual API Patterns
82
42
 
83
- ## Pattern 2: Per-Pack Customization in Layout
43
+ ### Pattern 1: Per-Pack Customization in Layout
84
44
 
85
45
  Customize hint handling per pack using a hash:
86
46
 
@@ -109,7 +69,7 @@ Customize hint handling per pack using a hash:
109
69
 
110
70
  ---
111
71
 
112
- ## Pattern 3: Controller Override (Before Expensive Work)
72
+ ### Pattern 2: Controller Override (Before Expensive Work)
113
73
 
114
74
  Send hints manually in controller BEFORE expensive work to maximize parallelism:
115
75
 
@@ -148,7 +108,7 @@ end
148
108
 
149
109
  ---
150
110
 
151
- ## Pattern 4: View Override
111
+ ### Pattern 3: View Override
152
112
 
153
113
  Views can use `append_*_pack_tag` to add packs dynamically:
154
114
 
@@ -188,13 +148,7 @@ Views can use `append_*_pack_tag` to add packs dynamically:
188
148
 
189
149
  ## Configuration
190
150
 
191
- ```yaml
192
- # config/shakapacker.yml
193
- production:
194
- early_hints:
195
- enabled: true # Master switch (default: false)
196
- debug: true # Show HTML comments with debug info (default: false)
197
- ```
151
+ > **📚 Configuration:** See the [main documentation](early_hints.md#quick-start) for all configuration options including global settings, priority levels (preload/prefetch/none), and per-controller configuration.
198
152
 
199
153
  ---
200
154
 
@@ -226,26 +180,20 @@ end
226
180
 
227
181
  ## When to Use Each Pattern
228
182
 
229
- ### Pattern 1 (Automatic) - Best for:
230
-
231
- - Simple apps with consistent performance
232
- - Small/medium JS bundles (<500KB)
233
- - Fast controllers (<100ms)
234
-
235
- ### Pattern 2 (Per-Pack) - Best for:
183
+ ### Pattern 1 (Per-Pack) - Best for:
236
184
 
237
185
  - Mixed vendor bundles (preload critical, prefetch non-critical)
238
186
  - Different handling for different packs
239
187
  - Layout-specific optimizations
240
188
 
241
- ### Pattern 3 (Controller) - Best for:
189
+ ### Pattern 2 (Controller) - Best for:
242
190
 
243
191
  - Slow controllers with expensive queries (>300ms)
244
192
  - Large JS bundles (>500KB)
245
193
  - APIs calls in controller
246
194
  - Maximum parallelism needed
247
195
 
248
- ### Pattern 4 (View Override) - Best for:
196
+ ### Pattern 3 (View Override) - Best for:
249
197
 
250
198
  - Admin sections with extra packs
251
199
  - Feature flags determining packs
@@ -259,11 +207,11 @@ end
259
207
  # app/controllers/posts_controller.rb
260
208
  class PostsController < ApplicationController
261
209
  def index
262
- # Fast controller, use automatic hints
210
+ # Fast controller, automatic hints work fine (Pattern 1)
263
211
  end
264
212
 
265
213
  def show
266
- # Slow controller, send hints early
214
+ # Slow controller, send hints early for parallelism (Pattern 2)
267
215
  send_pack_early_hints({
268
216
  "application" => { js: "preload", css: "preload" }
269
217
  })
@@ -277,6 +225,7 @@ end
277
225
  ```erb
278
226
  <%# app/views/posts/show.html.erb %>
279
227
  <% if current_user&.admin? %>
228
+ <%# Pattern 3: Dynamic pack loading based on user role %>
280
229
  <% append_javascript_pack_tag 'admin_tools' %>
281
230
  <% end %>
282
231
  ```
@@ -286,13 +235,12 @@ end
286
235
  <!DOCTYPE html>
287
236
  <html>
288
237
  <head>
289
- <%# Automatic CSS hints for application %>
290
238
  <%= stylesheet_pack_tag 'application' %>
291
239
  </head>
292
240
  <body>
293
241
  <%= yield %>
294
242
 
295
- <%# Automatic JS hints for application + admin_tools (if appended) %>
243
+ <%# Sends hints for application + admin_tools (if appended) %>
296
244
  <%# Won't duplicate hints already sent in controller %>
297
245
  <%= javascript_pack_tag 'application' %>
298
246
  </body>
@@ -303,7 +251,9 @@ end
303
251
 
304
252
  ## Preloading Non-Pack Assets (Images, Videos, Fonts)
305
253
 
306
- **Shakapacker's early hints are for pack assets (JS/CSS bundles).** For non-pack assets like hero images, videos, and fonts, you have two options:
254
+ **Shakapacker's early hints are for pack assets (JS/CSS bundles).** For non-pack assets like hero images, videos, and fonts, you have two options.
255
+
256
+ > **Note:** The [main documentation](early_hints.md#4-preloading-hero-images-and-videos) covers using Rails' built-in `preload_link_tag` for images and videos, which is simpler than the manual approach below.
307
257
 
308
258
  ### Option 1: Manual Early Hints (For LCP/Critical Assets)
309
259
 
@@ -379,7 +329,9 @@ Use Rails' `preload_link_tag` to add `<link rel="preload">` in the HTML:
379
329
 
380
330
  ## Requirements & Limitations
381
331
 
382
- **IMPORTANT:** Before implementing Early Hints, understand these limitations:
332
+ > **📚 Full Requirements:** See the [main documentation](early_hints.md#requirements) for complete browser and server requirements. This section covers limitations specific to the manual API.
333
+
334
+ **IMPORTANT:** Understand these limitations when using the manual API:
383
335
 
384
336
  ### Architecture: Proxy Required for HTTP/2
385
337
 
@@ -426,14 +378,10 @@ Browser (HTTP/2 103) ✅
426
378
  - Subsequent 103 responses are ignored by browsers
427
379
  - This is by design per the HTTP 103 spec
428
380
 
429
- ### Browser Support
430
-
431
- - Chrome/Firefox 103+
432
- - Safari 16.4+
433
- - Gracefully degrades if not supported
434
-
435
381
  ### Testing Locally
436
382
 
383
+ > **📚 Full Testing Guide:** See the [Feature Testing Guide](feature_testing.md#http-103-early-hints) for comprehensive testing instructions with browser DevTools and curl.
384
+
437
385
  **Step 1: Enable early hints in your test environment**
438
386
 
439
387
  ```yaml
@@ -478,223 +426,29 @@ curl -v http://localhost:3000/
478
426
 
479
427
  ### Production Setup
480
428
 
481
- #### Thruster (Rails 8+ Default)
482
-
483
- **Recommended**: Use [Thruster](https://github.com/basecamp/thruster) in front of Puma (Rails 8 default).
484
-
485
- Thruster handles HTTP/2 → HTTP/1.1 translation automatically. No configuration needed - Early Hints just work.
486
-
487
- ```dockerfile
488
- # Dockerfile (Rails 8 default)
489
- CMD ["bundle", "exec", "thrust", "./bin/rails", "server"]
490
- ```
491
-
492
- Thruster will:
493
-
494
- 1. Receive HTTP/2 requests from browsers
495
- 2. Translate to HTTP/1.1 for Puma
496
- 3. Pass through HTTP/1.1 103 Early Hints from Puma
497
- 4. Translate to HTTP/2 103 for browsers
498
-
499
- #### Control Plane
500
-
501
- **Status: Early Hints NOT supported** ❌
502
-
503
- Control Plane's load balancer appears to strip HTTP 103 responses, even with correct configuration:
504
-
505
- - Puma sends HTTP/1.1 103 ✅ (verified locally with curl)
506
- - Control Plane LB strips 103 before reaching browser ❌
507
- - Workload protocol set to `HTTP` (not `HTTP2`) ✅
508
- - Puma started with `--early-hints` flag ✅
509
-
510
- **Recommendation**: If you need early hints, consider:
511
-
512
- - **Thruster** (Rails 8 default, supports early hints)
513
- - **Self-hosted nginx** (supports early hints)
514
- - **Cloudflare** (paid plans only)
515
- - Contact Control Plane support to request early hints support
516
-
517
- #### nginx (Self-Hosted)
518
-
519
- If you want HTTP/2 in production with self-hosted nginx:
520
-
521
- ```nginx
522
- # /etc/nginx/sites-available/myapp
523
- upstream puma {
524
- server unix:///var/www/myapp/tmp/sockets/puma.sock;
525
- }
526
-
527
- server {
528
- listen 443 ssl http2;
529
- server_name example.com;
530
-
531
- # SSL certificates
532
- ssl_certificate /path/to/cert.pem;
533
- ssl_certificate_key /path/to/key.pem;
534
-
535
- location / {
536
- proxy_pass http://puma; # Puma uses HTTP/1.1
537
- proxy_set_header Host $host;
538
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
539
- proxy_set_header X-Forwarded-Proto $scheme;
540
-
541
- # CRITICAL: Pass through Early Hints from Puma
542
- proxy_pass_header Link;
543
- }
544
- }
545
- ```
546
-
547
- nginx will:
429
+ > **📚 Production Setup:** See the [main documentation](early_hints.md#requirements) for complete production setup instructions including Puma configuration, proxy setup (Thruster, nginx, Cloudflare), and troubleshooting proxy issues.
548
430
 
549
- 1. Receive HTTP/2 request from browser
550
- 2. Forward as HTTP/1.1 to Puma
551
- 3. Receive HTTP/1.1 103 from Puma
552
- 4. Translate to HTTP/2 103 for browser
431
+ **Quick checklist:**
553
432
 
554
- ---
555
-
556
- ## Troubleshooting
557
-
558
- ### Early Hints Not Appearing
559
-
560
- **Step 1: Enable debug mode to see what Puma is sending**
561
-
562
- ```yaml
563
- # config/shakapacker.yml
564
- development:
565
- early_hints:
566
- enabled: true
567
- debug: true # Shows hints in HTML comments
568
- ```
569
-
570
- Reload your page and check the HTML source for comments like:
571
-
572
- ```html
573
- <!-- Early hints sent (JS): application=preload -->
574
- <!-- Early hints sent (CSS): application=preload -->
575
- ```
576
-
577
- **If debug shows hints are sent:**
578
-
579
- The issue is with your **proxy/infrastructure**, not Shakapacker. Proceed to Step 2.
580
-
581
- **If debug shows NO hints sent:**
582
-
583
- Check your config:
584
-
585
- - `early_hints.enabled: true` in `config/shakapacker.yml`
433
+ - Puma 5+ with `--early-hints` flag (REQUIRED)
434
+ - HTTP/2-capable proxy (Thruster ✅, nginx ✅, Cloudflare ✅, Control Plane ❌, AWS ALB ❌)
586
435
  - Rails 5.2+
587
- - Puma 5+
588
- - **Puma started with `--early-hints` flag** (REQUIRED!)
589
-
590
- ---
591
-
592
- **Step 2: Check if your proxy is stripping 103 responses**
593
-
594
- This is the **most common cause** of missing early hints.
595
-
596
- Test with curl against your local Puma (HTTP/1.1):
597
-
598
- ```bash
599
- # Direct to Puma (should work)
600
- curl -v http://localhost:3000/
601
-
602
- # Look for:
603
- < HTTP/1.1 103 Early Hints
604
- < link: </packs/application.js>; rel=preload; as=script
605
- <
606
- < HTTP/1.1 200 OK
607
- ```
608
-
609
- If you see the 103 response, Puma is working correctly.
610
-
611
- ---
612
-
613
- **Step 3: Common proxy issues**
614
-
615
- #### Control Plane
616
-
617
- **Status: NOT supported** ❌
618
-
619
- Control Plane strips HTTP 103 responses. No known workaround. Consider switching to Thruster, nginx, or Cloudflare if you need early hints.
620
-
621
- #### AWS ALB/ELB
622
-
623
- **Not supported** - ALBs strip 103 responses entirely. No workaround except:
624
-
625
- - Skip ALB (not recommended)
626
- - Use CloudFront in front (CloudFront supports early hints)
627
-
628
- #### Cloudflare
629
-
630
- Enable "Early Hints" in dashboard:
631
-
632
- ```
633
- Speed > Optimization > Early Hints: ON
634
- ```
635
-
636
- **Note:** Paid plans only (Pro/Business/Enterprise).
637
-
638
- #### nginx
639
-
640
- nginx 1.13+ passes 103 responses automatically. Ensure you're using HTTP/2:
641
-
642
- ```nginx
643
- server {
644
- listen 443 ssl http2; # Enable HTTP/2
645
-
646
- location / {
647
- proxy_pass http://puma; # Puma uses HTTP/1.1
648
- proxy_http_version 1.1; # Required for Puma
649
- }
650
- }
651
- ```
652
-
653
- No special configuration needed - nginx automatically translates HTTP/1.1 103 to HTTP/2 103.
654
-
655
- #### Thruster (Rails 8+)
656
-
657
- Thruster handles HTTP/2 → HTTP/1.1 translation automatically. Early hints just work. No configuration needed.
658
436
 
659
437
  ---
660
438
 
661
- ### Debugging Checklist
662
-
663
- 1. ✅ **Config enabled:** `early_hints.enabled: true` in `shakapacker.yml`
664
- 2. ✅ **Puma `--early-hints` flag:** Puma started with this flag (REQUIRED!)
665
- 3. ✅ **Debug mode on:** See HTML comments confirming hints sent
666
- 4. ✅ **Puma 5+:** Early hints require Puma 5+
667
- 5. ✅ **Rails 5.2+:** `request.send_early_hints` API available
668
- 6. ✅ **Architecture:** Proxy in front of Puma (Thruster, nginx - NOT Control Plane or AWS ALB)
669
- 7. ✅ **Puma protocol:** Always HTTP/1.1 (never HTTP/2)
670
- 8. ✅ **Proxy protocol:** HTTP/2 to browser, HTTP/1.1 to Puma
671
- 9. ✅ **Browser support:** Chrome 103+, Firefox 103+, Safari 16.4+
672
-
673
- ---
674
-
675
- ### Performance Got Worse?
676
-
677
- If enabling early hints **decreased** performance:
678
-
679
- **Likely cause:** Page has large images/videos as LCP (Largest Contentful Paint).
439
+ ## Troubleshooting
680
440
 
681
- Preloading large JS bundles can delay image downloads, hurting LCP.
441
+ > **📚 Complete Troubleshooting:** See the [main documentation](early_hints.md#troubleshooting) for comprehensive troubleshooting including debug mode, proxy configuration, and performance optimization.
682
442
 
683
- **Fix:**
443
+ Quick debugging steps:
684
444
 
685
- ```yaml
686
- # config/shakapacker.yml
687
- production:
688
- early_hints:
689
- enabled: true
690
- css: "prefetch" # Lower priority
691
- js: "prefetch" # Lower priority
692
- ```
693
-
694
- Or disable entirely and use HTML `preload_link_tag` for images instead.
695
-
696
- ---
445
+ 1. Enable `debug: true` in shakapacker.yml to see hints in HTML comments
446
+ 2. Verify Puma started with `--early-hints` flag
447
+ 3. Test with `curl -v http://localhost:3000/` to see if Puma sends 103 responses
448
+ 4. Check if your proxy strips 103 responses (Control Plane ❌, AWS ALB ❌)
697
449
 
698
450
  ### Reference
699
451
 
452
+ - [Main Early Hints Documentation](early_hints.md)
453
+ - [Feature Testing Guide](feature_testing.md#http-103-early-hints)
700
454
  - [Rails 103 Early Hints Analysis](https://island94.org/2025/10/rails-103-early-hints-could-be-better-maybe-doesn-t-matter)
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.3.0.beta.5".freeze
3
+ VERSION = "9.3.0.beta.6".freeze
4
4
  end
@@ -596,6 +596,9 @@ async function runValidateCommand(options: ExportOptions): Promise<number> {
596
596
  clearBuildEnvironmentVariables()
597
597
  restoreBuildEnvironmentVariables(savedEnv)
598
598
 
599
+ // Clear shakapacker config cache between builds
600
+ shakapackerConfigCache = null
601
+
599
602
  // Get the build's environment to use for auto-detection
600
603
  const buildConfig = config.builds[buildName]
601
604
  const buildEnv =
@@ -692,6 +695,9 @@ async function runAllBuildsCommand(options: ExportOptions): Promise<number> {
692
695
  clearBuildEnvironmentVariables()
693
696
  restoreBuildEnvironmentVariables(savedEnv)
694
697
 
698
+ // Clear shakapacker config cache between builds
699
+ shakapackerConfigCache = null
700
+
695
701
  // Create a modified options object for this build
696
702
  const buildOptions = { ...resolvedOptions, build: buildName }
697
703
  const configs = await loadConfigsForEnv(undefined, buildOptions, appRoot)
@@ -770,6 +776,9 @@ async function runDoctorMode(
770
776
  clearBuildEnvironmentVariables()
771
777
  restoreBuildEnvironmentVariables(savedEnv)
772
778
 
779
+ // Clear shakapacker config cache between builds
780
+ shakapackerConfigCache = null
781
+
773
782
  const configs = await loadConfigsForEnv(
774
783
  undefined,
775
784
  { ...options, build: buildName },
@@ -798,9 +807,13 @@ async function runDoctorMode(
798
807
  // If config file exists but is invalid, show error and exit
799
808
  const errorMessage =
800
809
  error instanceof Error ? error.message : String(error)
801
- console.error(`\n❌ Config file found but invalid: ${errorMessage}`)
810
+ console.error(`\n❌ Error loading build configuration:`)
811
+ console.error(`\n${errorMessage}`)
812
+ console.error(
813
+ `\n💡 To fix this issue, check your build config in ${configFilePath}`
814
+ )
802
815
  console.error(
803
- `Fix the config file or run: bin/shakapacker-config --init\n`
816
+ ` or run: bin/shakapacker-config --init to regenerate it.\n`
804
817
  )
805
818
  throw error
806
819
  }
@@ -825,6 +838,9 @@ async function runDoctorMode(
825
838
  clearBuildEnvironmentVariables()
826
839
  restoreBuildEnvironmentVariables(savedEnv)
827
840
 
841
+ // Clear shakapacker config cache between builds
842
+ shakapackerConfigCache = null
843
+
828
844
  // Set WEBPACK_SERVE for HMR config
829
845
  if (hmr) {
830
846
  process.env.WEBPACK_SERVE = "true"
@@ -1039,6 +1055,12 @@ async function loadConfigsForEnv(
1039
1055
  "DYLD_INSERT_LIBRARIES"
1040
1056
  ]
1041
1057
 
1058
+ if (process.env.VERBOSE) {
1059
+ console.log(
1060
+ `[Config Exporter] Setting environment variables from build config...`
1061
+ )
1062
+ }
1063
+
1042
1064
  for (const [key, value] of Object.entries(resolvedBuild.environment)) {
1043
1065
  if (DANGEROUS_ENV_VARS.includes(key)) {
1044
1066
  console.warn(
@@ -1053,6 +1075,9 @@ async function loadConfigsForEnv(
1053
1075
  )
1054
1076
  continue
1055
1077
  }
1078
+ if (process.env.VERBOSE) {
1079
+ console.log(`[Config Exporter] ${key}=${value}`)
1080
+ }
1056
1081
  process.env[key] = value
1057
1082
  }
1058
1083
 
@@ -1080,6 +1105,21 @@ async function loadConfigsForEnv(
1080
1105
  const railsEnv =
1081
1106
  options.env || resolvedBuild.environment.RAILS_ENV || finalEnv
1082
1107
  process.env.RAILS_ENV = railsEnv
1108
+
1109
+ // Auto-set CLIENT_BUNDLE_ONLY/SERVER_BUNDLE_ONLY from outputs if not already in environment
1110
+ // This allows webpack configs to return the correct number of bundles
1111
+ if (
1112
+ !resolvedBuild.environment.CLIENT_BUNDLE_ONLY &&
1113
+ !resolvedBuild.environment.SERVER_BUNDLE_ONLY
1114
+ ) {
1115
+ if (buildOutputs.length === 1) {
1116
+ if (buildOutputs[0] === "client") {
1117
+ process.env.CLIENT_BUNDLE_ONLY = "yes"
1118
+ } else if (buildOutputs[0] === "server") {
1119
+ process.env.SERVER_BUNDLE_ONLY = "yes"
1120
+ }
1121
+ }
1122
+ }
1083
1123
  } else {
1084
1124
  // No build config - use CLI env or default
1085
1125
  finalEnv = env || "development"
@@ -1092,6 +1132,7 @@ async function loadConfigsForEnv(
1092
1132
  process.env.RAILS_ENV = finalEnv
1093
1133
  }
1094
1134
 
1135
+ // Handle CLI flags for client/server only
1095
1136
  if (options.clientOnly) {
1096
1137
  process.env.CLIENT_BUNDLE_ONLY = "yes"
1097
1138
  } else if (options.serverOnly) {
@@ -1165,7 +1206,19 @@ async function loadConfigsForEnv(
1165
1206
  })
1166
1207
 
1167
1208
  // eslint-disable-next-line @typescript-eslint/no-var-requires
1168
- let loadedConfig = require(configFile)
1209
+ let loadedConfig: any
1210
+ try {
1211
+ loadedConfig = require(configFile)
1212
+ } catch (error: unknown) {
1213
+ const errorMessage = error instanceof Error ? error.message : String(error)
1214
+ throw new Error(
1215
+ `Failed to load webpack/rspack config file.\n\n` +
1216
+ `Config file: ${configFile}\n` +
1217
+ `Build: ${buildName || "default"}\n` +
1218
+ `Error: ${errorMessage}\n\n` +
1219
+ `Tip: Check that the config file is valid and doesn't have syntax errors.`
1220
+ )
1221
+ }
1169
1222
 
1170
1223
  // Handle ES module default export
1171
1224
  if (typeof loadedConfig === "object" && "default" in loadedConfig) {
@@ -1198,10 +1251,41 @@ async function loadConfigsForEnv(
1198
1251
  } catch (error: unknown) {
1199
1252
  const errorMessage =
1200
1253
  error instanceof Error ? error.message : String(error)
1254
+
1255
+ // Build detailed environment information for debugging
1256
+ const envDetails = [
1257
+ `Config file: ${configFile}`,
1258
+ `Build: ${buildName || "default"}`,
1259
+ ``,
1260
+ `Current Environment Variables:`,
1261
+ ` NODE_ENV: ${process.env.NODE_ENV || "(not set)"}`,
1262
+ ` RAILS_ENV: ${process.env.RAILS_ENV || "(not set)"}`,
1263
+ ` CLIENT_BUNDLE_ONLY: ${process.env.CLIENT_BUNDLE_ONLY || "(not set)"}`,
1264
+ ` SERVER_BUNDLE_ONLY: ${process.env.SERVER_BUNDLE_ONLY || "(not set)"}`,
1265
+ ` WEBPACK_SERVE: ${process.env.WEBPACK_SERVE || "(not set)"}`,
1266
+ ``,
1267
+ `Bundler env args: ${JSON.stringify(envObject)}`,
1268
+ `Mode: ${finalEnv}`,
1269
+ ``,
1270
+ `Error: ${errorMessage}`,
1271
+ ``
1272
+ ]
1273
+
1274
+ // Add suggestion based on common error patterns
1275
+ let suggestion = `Check your webpack/rspack config for errors. The config function threw an exception when called.`
1276
+ if (errorMessage.includes("NODE_ENV") && !process.env.NODE_ENV) {
1277
+ suggestion =
1278
+ `NODE_ENV is not set. ` +
1279
+ `Your build config should set NODE_ENV in the 'environment' section.\n` +
1280
+ `Example:\n` +
1281
+ ` environment:\n` +
1282
+ ` NODE_ENV: "development"`
1283
+ }
1284
+
1201
1285
  throw new Error(
1202
1286
  `Failed to execute config function: ${errorMessage}\n` +
1203
- `Config file: ${configFile}\n` +
1204
- `Environment: ${JSON.stringify(envObject)}`
1287
+ envDetails.join("\n") +
1288
+ `\nTip: ${suggestion}`
1205
1289
  )
1206
1290
  }
1207
1291
  }
@@ -1212,29 +1296,81 @@ async function loadConfigsForEnv(
1212
1296
  : [loadedConfig]
1213
1297
  const results: Array<{ config: any; metadata: ConfigMetadata }> = []
1214
1298
 
1299
+ // Validate config count matches expected outputs
1300
+ if (buildOutputs.length > 0 && configs.length !== buildOutputs.length) {
1301
+ const errorLines = [
1302
+ `Webpack config returned ${configs.length} config(s) but outputs array specifies ${buildOutputs.length}.`,
1303
+ ``,
1304
+ `Build: ${buildName || "default"}`,
1305
+ `Config file: ${configFile}`,
1306
+ `Expected outputs: [${buildOutputs.join(", ")}]`,
1307
+ `Actual configs returned: ${configs.length}`,
1308
+ ``,
1309
+ `This mismatch means:`
1310
+ ]
1311
+
1312
+ if (configs.length < buildOutputs.length) {
1313
+ errorLines.push(
1314
+ ` - Your webpack config is returning FEWER configs than expected.`,
1315
+ ` - Either update your webpack config to return ${buildOutputs.length} config(s),`,
1316
+ ` - Or update the 'outputs' array in your build config to match what webpack returns.`
1317
+ )
1318
+ } else {
1319
+ errorLines.push(
1320
+ ` - Your webpack config is returning MORE configs than expected.`,
1321
+ ` - Either update the 'outputs' array to include all ${configs.length} outputs,`,
1322
+ ` - Or update your webpack config to return only ${buildOutputs.length} config(s).`
1323
+ )
1324
+ }
1325
+
1326
+ errorLines.push(
1327
+ ``,
1328
+ `Example fix in build config:`,
1329
+ ` outputs:`,
1330
+ ...Array.from({ length: configs.length }, (_, i) =>
1331
+ i < buildOutputs.length
1332
+ ? ` - ${buildOutputs[i]}`
1333
+ : ` - config-${i + 1} # Add a name for this config`
1334
+ )
1335
+ )
1336
+
1337
+ throw new Error(errorLines.join("\n"))
1338
+ }
1339
+
1340
+ // Debug logging
1341
+ if (process.env.VERBOSE || buildOutputs.length > 0) {
1342
+ console.log(
1343
+ `[Config Exporter] Webpack returned ${configs.length} config(s), buildOutputs: [${buildOutputs.join(", ")}]`
1344
+ )
1345
+ if (buildOutputs.length > 0 && configs.length === buildOutputs.length) {
1346
+ console.log(
1347
+ `[Config Exporter] ✓ Config count matches outputs array (${configs.length})`
1348
+ )
1349
+ }
1350
+ }
1351
+
1215
1352
  configs.forEach((cfg, index) => {
1216
- let configType: "client" | "server" | "all" = "all"
1353
+ let configType: string = "all"
1217
1354
 
1218
1355
  // Use outputs from build config if available
1219
- if (
1220
- buildOutputs.length > 0 &&
1221
- index < buildOutputs.length &&
1222
- buildOutputs[index]
1223
- ) {
1224
- const outputValue = buildOutputs[index]
1225
- // Validate the output value is a valid config type
1226
- if (
1227
- outputValue === "client" ||
1228
- outputValue === "server" ||
1229
- outputValue === "all"
1230
- ) {
1231
- configType = outputValue
1232
- } else {
1233
- throw new Error(
1234
- `Invalid output type '${outputValue}' at index ${index} in build '${buildName}'. ` +
1235
- `Allowed values are: client, server, all`
1356
+ if (buildOutputs.length > 0) {
1357
+ // If outputs are specified, skip configs beyond the outputs array
1358
+ if (index >= buildOutputs.length) {
1359
+ console.log(
1360
+ `[Config Exporter] Skipping config[${index}] - beyond outputs array`
1236
1361
  )
1362
+ return // Skip this config
1237
1363
  }
1364
+
1365
+ const outputValue = buildOutputs[index]
1366
+ if (!outputValue || outputValue.trim() === "") {
1367
+ return // Skip null/undefined/empty string entries
1368
+ }
1369
+
1370
+ // Accept any string as a valid output name
1371
+ // Built-in types: client, server, all, client-hmr
1372
+ // Custom types: client-modern, client-legacy, server-bundle, etc.
1373
+ configType = outputValue
1238
1374
  } else if (configs.length === 2) {
1239
1375
  // Likely client and server configs
1240
1376
  configType = index === 0 ? "client" : "server"
@@ -1398,10 +1534,30 @@ function cleanConfig(obj: any, rootPath: string): any {
1398
1534
  /**
1399
1535
  * Loads and returns shakapacker.yml configuration
1400
1536
  */
1537
+ // Cache to avoid duplicate loading and logging
1538
+ let shakapackerConfigCache: {
1539
+ env: string
1540
+ result: { bundler: "webpack" | "rspack"; configPath: string }
1541
+ } | null = null
1542
+
1401
1543
  function loadShakapackerConfig(
1402
1544
  env: string,
1403
1545
  appRoot: string
1404
1546
  ): { bundler: "webpack" | "rspack"; configPath: string } {
1547
+ // Return cached result if same environment
1548
+ if (shakapackerConfigCache && shakapackerConfigCache.env === env) {
1549
+ if (process.env.VERBOSE) {
1550
+ console.log(
1551
+ `[Config Exporter] Using cached bundler config for env: ${env}`
1552
+ )
1553
+ }
1554
+ return shakapackerConfigCache.result
1555
+ }
1556
+
1557
+ if (process.env.VERBOSE) {
1558
+ console.log(`[Config Exporter] Loading shakapacker config for env: ${env}`)
1559
+ }
1560
+
1405
1561
  try {
1406
1562
  const configFilePath =
1407
1563
  process.env.SHAKAPACKER_CONFIG ||
@@ -1417,10 +1573,12 @@ function loadShakapackerConfig(
1417
1573
  console.warn(
1418
1574
  `[Config Exporter] Invalid bundler '${bundler}' in shakapacker.yml, defaulting to webpack`
1419
1575
  )
1420
- return {
1421
- bundler: "webpack",
1576
+ const result = {
1577
+ bundler: "webpack" as const,
1422
1578
  configPath: bundler === "rspack" ? "config/rspack" : "config/webpack"
1423
1579
  }
1580
+ shakapackerConfigCache = { env, result }
1581
+ return result
1424
1582
  }
1425
1583
 
1426
1584
  // Get config path
@@ -1429,10 +1587,15 @@ function loadShakapackerConfig(
1429
1587
  customConfigPath ||
1430
1588
  (bundler === "rspack" ? "config/rspack" : "config/webpack")
1431
1589
 
1590
+ const result = { bundler, configPath }
1591
+ shakapackerConfigCache = { env, result }
1592
+
1593
+ // Only log on first call (when cache was empty)
1432
1594
  console.log(
1433
1595
  `[Config Exporter] Auto-detected bundler: ${bundler}, config path: ${configPath}`
1434
1596
  )
1435
- return { bundler, configPath }
1597
+
1598
+ return result
1436
1599
  }
1437
1600
  } catch (error: unknown) {
1438
1601
  console.warn(
@@ -1440,7 +1603,9 @@ function loadShakapackerConfig(
1440
1603
  )
1441
1604
  }
1442
1605
 
1443
- return { bundler: "webpack", configPath: "config/webpack" }
1606
+ const result = { bundler: "webpack" as const, configPath: "config/webpack" }
1607
+ shakapackerConfigCache = { env, result }
1608
+ return result
1444
1609
  }
1445
1610
 
1446
1611
  /**
@@ -46,18 +46,27 @@ export class FileWriter {
46
46
  * Generate filename for a config export
47
47
  * Format without build: {bundler}-{env}-{type}.{ext}
48
48
  * Format with build: {bundler}-{build}-{type}.{ext}
49
- * Examples:
50
- * webpack-development-client.yaml
51
- * rspack-production-server.yaml
52
- * webpack-test-all.json
53
- * webpack-development-client-hmr.yaml
54
- * webpack-dev-client.yaml (with build name)
55
- * rspack-cypress-dev-server.yaml (with build name)
49
+ *
50
+ * @param bundler - The bundler type (webpack, rspack)
51
+ * @param env - The environment (development, production, test)
52
+ * @param configType - Type of config. Built-in: "client", "server", "all", "client-hmr". Custom: any string from outputs array
53
+ * @param format - Output format (yaml, json, inspect)
54
+ * @param buildName - Optional build name that overrides env in filename
55
+ *
56
+ * @example
57
+ * // Built-in types
58
+ * generateFilename("webpack", "development", "client", "yaml")
59
+ * // => "webpack-development-client.yaml"
60
+ *
61
+ * @example
62
+ * // Custom output names
63
+ * generateFilename("webpack", "development", "client-modern", "yaml", "dev-hmr")
64
+ * // => "webpack-dev-hmr-client-modern.yaml"
56
65
  */
57
66
  static generateFilename(
58
67
  bundler: string,
59
68
  env: string,
60
- configType: "client" | "server" | "all" | "client-hmr",
69
+ configType: string,
61
70
  format: "yaml" | "json" | "inspect",
62
71
  buildName?: string
63
72
  ): string {
@@ -29,7 +29,12 @@ export interface ConfigMetadata {
29
29
  bundler: string
30
30
  environment: string
31
31
  configFile: string
32
- configType: "client" | "server" | "all" | "client-hmr"
32
+ /**
33
+ * Type of webpack/rspack config output.
34
+ * Built-in types: "client", "server", "all", "client-hmr"
35
+ * Custom types: Any string matching your outputs array (e.g., "client-modern", "client-legacy", "server-bundle")
36
+ */
37
+ configType: string
33
38
  configCount: number
34
39
  buildName?: string // New: name of the build from config file
35
40
  environmentVariables: {
@@ -165,6 +165,7 @@ export const ErrorMessages: Record<ErrorCode, string> = {
165
165
  */
166
166
  export class ShakapackerError extends Error {
167
167
  public readonly code: ErrorCode
168
+
168
169
  public readonly details?: Record<string, any>
169
170
 
170
171
  constructor(
@@ -36,12 +36,14 @@ export function createFileOperationError(
36
36
  filePath: string,
37
37
  details?: string
38
38
  ): ShakapackerError {
39
- const errorCode =
40
- operation === "read"
41
- ? ErrorCode.FILE_READ_ERROR
42
- : operation === "write"
43
- ? ErrorCode.FILE_WRITE_ERROR
44
- : ErrorCode.FILE_NOT_FOUND
39
+ let errorCode: ErrorCode
40
+ if (operation === "read") {
41
+ errorCode = ErrorCode.FILE_READ_ERROR
42
+ } else if (operation === "write") {
43
+ errorCode = ErrorCode.FILE_WRITE_ERROR
44
+ } else {
45
+ errorCode = ErrorCode.FILE_NOT_FOUND
46
+ }
45
47
 
46
48
  return new ShakapackerError(errorCode, {
47
49
  path: filePath,
@@ -60,12 +62,14 @@ export function createFileOperationErrorLegacy(
60
62
  ): Error {
61
63
  const baseMessage = `Failed to ${operation} file at path '${filePath}'`
62
64
  const errorDetails = details ? ` - ${details}` : ""
63
- const suggestion =
64
- operation === "read"
65
- ? " (check if file exists and permissions are correct)"
66
- : operation === "write"
67
- ? " (check write permissions and disk space)"
68
- : " (check permissions)"
65
+ let suggestion: string
66
+ if (operation === "read") {
67
+ suggestion = " (check if file exists and permissions are correct)"
68
+ } else if (operation === "write") {
69
+ suggestion = " (check write permissions and disk space)"
70
+ } else {
71
+ suggestion = " (check permissions)"
72
+ }
69
73
  return new Error(`${baseMessage}${errorDetails}${suggestion}`)
70
74
  }
71
75
 
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.3.0-beta.5",
3
+ "version": "9.3.0-beta.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "shakapacker",
9
- "version": "9.3.0-beta.5",
9
+ "version": "9.3.0-beta.6",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "js-yaml": "^4.1.0",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.3.0-beta.5",
3
+ "version": "9.3.0-beta.6",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -32,7 +32,7 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "clean:ts": "find package -name '*.ts' -not -name '*.d.ts' | sed 's/\\.ts$//' | xargs -I {} rm -f {}.js {}.d.ts {}.d.ts.map {}.js.map",
35
- "build": "tsc && cp package/index.d.ts.template package/index.d.ts && node scripts/remove-use-strict.js && yarn prettier --write 'package/**/*.js'",
35
+ "build": "tsc && cp package/index.d.ts.template package/index.d.ts && node scripts/remove-use-strict.js && yarn prettier --write 'package/**/*.js' 'package/**/*.ts'",
36
36
  "build:types": "tsc",
37
37
  "knip": "knip",
38
38
  "knip:production": "knip --production",
@@ -58,6 +58,51 @@ describe("configExporter", () => {
58
58
  )
59
59
  expect(filename).toBe("rspack-production-client.json")
60
60
  })
61
+
62
+ test("generates correct filename for custom output name client-modern", () => {
63
+ const { FileWriter } = require("../../package/configExporter/fileWriter")
64
+ const filename = FileWriter.generateFilename(
65
+ "webpack",
66
+ "development",
67
+ "client-modern",
68
+ "yaml"
69
+ )
70
+ expect(filename).toBe("webpack-development-client-modern.yaml")
71
+ })
72
+
73
+ test("generates correct filename for custom output name client-legacy", () => {
74
+ const { FileWriter } = require("../../package/configExporter/fileWriter")
75
+ const filename = FileWriter.generateFilename(
76
+ "webpack",
77
+ "production",
78
+ "client-legacy",
79
+ "yaml"
80
+ )
81
+ expect(filename).toBe("webpack-production-client-legacy.yaml")
82
+ })
83
+
84
+ test("generates correct filename for custom output name server-bundle", () => {
85
+ const { FileWriter } = require("../../package/configExporter/fileWriter")
86
+ const filename = FileWriter.generateFilename(
87
+ "rspack",
88
+ "development",
89
+ "server-bundle",
90
+ "yaml"
91
+ )
92
+ expect(filename).toBe("rspack-development-server-bundle.yaml")
93
+ })
94
+
95
+ test("generates correct filename with buildName override", () => {
96
+ const { FileWriter } = require("../../package/configExporter/fileWriter")
97
+ const filename = FileWriter.generateFilename(
98
+ "webpack",
99
+ "development",
100
+ "client-modern",
101
+ "yaml",
102
+ "dev-hmr"
103
+ )
104
+ expect(filename).toBe("webpack-dev-hmr-client-modern.yaml")
105
+ })
61
106
  })
62
107
 
63
108
  describe("environment variable preservation in runDoctorMode", () => {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shakapacker
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.3.0.beta.5
4
+ version: 9.3.0.beta.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -181,7 +181,7 @@ files:
181
181
  - docs/deployment.md
182
182
  - docs/developing_shakapacker.md
183
183
  - docs/early_hints.md
184
- - docs/early_hints_new_api.md
184
+ - docs/early_hints_manual_api.md
185
185
  - docs/feature_testing.md
186
186
  - docs/optional-peer-dependencies.md
187
187
  - docs/peer-dependencies.md
@@ -385,7 +385,7 @@ homepage: https://github.com/shakacode/shakapacker
385
385
  licenses:
386
386
  - MIT
387
387
  metadata:
388
- source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.3.0.beta.5
388
+ source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.3.0.beta.6
389
389
  rdoc_options: []
390
390
  require_paths:
391
391
  - lib