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 +4 -4
- data/CHANGELOG.md +30 -3
- data/Gemfile.lock +1 -1
- data/docs/early_hints.md +7 -21
- data/docs/{early_hints_new_api.md → early_hints_manual_api.md} +53 -299
- data/lib/shakapacker/version.rb +1 -1
- data/package/configExporter/cli.ts +192 -27
- data/package/configExporter/fileWriter.ts +17 -8
- data/package/configExporter/types.ts +6 -1
- data/package/utils/errorCodes.ts +1 -0
- data/package/utils/errorHelpers.ts +16 -12
- data/package-lock.json +2 -2
- data/package.json +2 -2
- data/test/package/configExporter.test.js +45 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b7929f32b14b49a2fde4893b66809a37147796c685be9161c12631369105624
|
4
|
+
data.tar.gz: 2601ae85b072d8286700558a5d92f424d6e5989f7e4d990caf2b57c939b1962c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
756
|
-
[v9.3.0-beta.
|
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
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.
|
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
|
-
|
326
|
-
|
327
|
-
|
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
|
-
|
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 -
|
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 [
|
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
|
-
|
31
|
+
## When to Use the Manual API
|
48
32
|
|
49
|
-
|
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
|
-
**
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 (
|
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
|
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
|
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,
|
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
|
-
<%#
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
443
|
+
Quick debugging steps:
|
684
444
|
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
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)
|
data/lib/shakapacker/version.rb
CHANGED
@@ -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❌
|
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
|
-
`
|
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
|
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
|
-
|
1204
|
-
|
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:
|
1353
|
+
let configType: string = "all"
|
1217
1354
|
|
1218
1355
|
// Use outputs from build config if available
|
1219
|
-
if (
|
1220
|
-
|
1221
|
-
index
|
1222
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
*
|
50
|
-
*
|
51
|
-
*
|
52
|
-
*
|
53
|
-
*
|
54
|
-
*
|
55
|
-
*
|
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:
|
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
|
-
|
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: {
|
data/package/utils/errorCodes.ts
CHANGED
@@ -36,12 +36,14 @@ export function createFileOperationError(
|
|
36
36
|
filePath: string,
|
37
37
|
details?: string
|
38
38
|
): ShakapackerError {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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.
|
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.
|
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.
|
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.
|
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/
|
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.
|
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
|