shakapacker 9.3.0.beta.4 → 9.3.0.beta.5
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 +20 -0
- data/CLAUDE.md +13 -0
- data/Gemfile.lock +1 -1
- data/README.md +19 -0
- data/docs/configuration.md +35 -0
- data/docs/early_hints.md +440 -0
- data/docs/early_hints_new_api.md +700 -0
- data/docs/feature_testing.md +492 -0
- data/lib/install/config/shakapacker.yml +27 -0
- data/lib/shakapacker/configuration.rb +4 -0
- data/lib/shakapacker/helper.rb +409 -14
- data/lib/shakapacker/railtie.rb +4 -0
- data/lib/shakapacker/version.rb +1 -1
- data/package/configExporter/buildValidator.ts +1 -2
- data/package/configExporter/cli.ts +2 -1
- data/package-lock.json +2 -2
- data/package.json +1 -1
- data/scripts/remove-use-strict.js +0 -1
- data/test/package/rules/babel.test.js +1 -0
- data/test/package/rules/swc.test.js +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9db05ecb02b4491f31eb4ec2374057933be5cf90a09b6dded781c036ff8e494e
|
4
|
+
data.tar.gz: a8f9b04812aff8a73275044f61a1ecbc096c5131a3bca69d5b75e292c1c31064
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88f63fdaaed2cfad42f000d97e08fec78dddaa24b304dd23ae515ba24b5b68845e33115adf13994f0a59f85b6be4003a442e8d5f88a1a9295484ca0b31e07fc1
|
7
|
+
data.tar.gz: 59069f26686a609a9df9994ec63a1cbc5905d18abbb7379c0460913f16ac58247e760d97eecc544a31a86e212d6af1d171b99f462f4b60b937d0da81d3415a7f
|
data/CHANGELOG.md
CHANGED
@@ -11,6 +11,26 @@
|
|
11
11
|
|
12
12
|
Changes since the last non-beta release.
|
13
13
|
|
14
|
+
### Added
|
15
|
+
|
16
|
+
- **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
|
+
- **Automatic sending**: Early hints are sent automatically when `early_hints: enabled: true` in `shakapacker.yml`
|
18
|
+
- **Per-page configuration**: Configure hints per-controller/action with `preload`/`prefetch`/`none` options
|
19
|
+
- **Hero image/video preloading**: Use Rails' built-in `preload_link_tag` (automatically sends early hints)
|
20
|
+
- **Zero-config upgrade**: No changes to layouts or views needed - just enable the config!
|
21
|
+
- Works seamlessly with existing `append_javascript_pack_tag` / `append_stylesheet_pack_tag` pattern
|
22
|
+
- Automatically discovers packs from queues populated by views/partials
|
23
|
+
- New `configure_pack_early_hints` class method for controller-level configuration
|
24
|
+
- New `skip_send_pack_early_hints` helper to opt-out for specific controllers (e.g., JSON APIs)
|
25
|
+
- Optional manual control: `configure_early_hints` can be called in controllers or views
|
26
|
+
- Added `early_hints:` option to `javascript_pack_tag` for per-tag control
|
27
|
+
- New `early_hints` configuration in `shakapacker.yml` with per-environment settings (`enabled: false` by default)
|
28
|
+
- Requires Rails 5.2+ and HTTP/2-capable server (e.g., Puma 5+)
|
29
|
+
- Browser support: All modern browsers (Chrome/Edge/Firefox 103+, Safari 16.4+)
|
30
|
+
- Gracefully degrades if not supported
|
31
|
+
- **Performance note**: May improve or hurt page load performance depending on content - careful testing advised
|
32
|
+
- See [Early Hints Guide](docs/early_hints.md) for detailed usage and advanced patterns
|
33
|
+
|
14
34
|
## [v9.3.0-beta.0] - October 13, 2025
|
15
35
|
|
16
36
|
### Added
|
data/CLAUDE.md
CHANGED
@@ -27,6 +27,19 @@
|
|
27
27
|
- Create small, focused PRs that are easy to review
|
28
28
|
- Always create a PR immediately after pushing changes
|
29
29
|
|
30
|
+
## Changelog
|
31
|
+
|
32
|
+
- **Update CHANGELOG.md for user-visible changes only**
|
33
|
+
- User-visible changes include: new features, bug fixes, breaking changes, deprecations, performance improvements
|
34
|
+
- **Do NOT add changelog entries for**: linting fixes, code formatting, internal refactoring, test updates, documentation fixes
|
35
|
+
- Non-user-visible changes don't need changelog entries even if they modify code
|
36
|
+
- **Format requirements**:
|
37
|
+
- Always link to the PR: `[PR #123](https://github.com/shakacode/shakapacker/pull/123)`
|
38
|
+
- Always link to the author: `by [username](https://github.com/username)`
|
39
|
+
- Keep formatting consistent with existing entries
|
40
|
+
- When releasing a version, update the version diff links at the bottom of CHANGELOG.md
|
41
|
+
- **For breaking changes**: Use bold formatting and link to migration documentation (e.g., `**Breaking**: Description. See [Migration Guide](docs/vX_upgrade.md)`)
|
42
|
+
|
30
43
|
## Shakapacker-Specific
|
31
44
|
|
32
45
|
- This gem supports both webpack and rspack configurations
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -78,6 +78,7 @@ Read the [full review here](https://clutch.co/profile/shakacode#reviews?sort_by=
|
|
78
78
|
- [View Helper: `image_pack_tag`](#view-helper-image_pack_tag)
|
79
79
|
- [View Helper: `favicon_pack_tag`](#view-helper-favicon_pack_tag)
|
80
80
|
- [View Helper: `preload_pack_asset`](#view-helper-preload_pack_asset)
|
81
|
+
- [View Helper: `send_pack_early_hints`](#view-helper-send_pack_early_hints)
|
81
82
|
- [Images in Stylesheets](#images-in-stylesheets)
|
82
83
|
- [Server-Side Rendering (SSR)](#server-side-rendering-ssr)
|
83
84
|
- [Development](#development)
|
@@ -563,6 +564,24 @@ If you want to preload a static asset in your `<head>`, you can use the `preload
|
|
563
564
|
<%= preload_pack_asset 'fonts/fa-regular-400.woff2' %>
|
564
565
|
```
|
565
566
|
|
567
|
+
#### HTTP 103 Early Hints
|
568
|
+
|
569
|
+
Automatically send early hints to browsers for faster asset loading. Supports `preload`/`prefetch`/`none` configuration per-page.
|
570
|
+
|
571
|
+
```yaml
|
572
|
+
# config/shakapacker.yml
|
573
|
+
production:
|
574
|
+
early_hints:
|
575
|
+
enabled: true
|
576
|
+
debug: false # Enable to see what hints are sent (as HTML comments)
|
577
|
+
```
|
578
|
+
|
579
|
+
⚠️ **Important**: May improve or hurt performance depending on content. See the [Early Hints Guide](./docs/early_hints.md) for configuration, performance guidance, and examples.
|
580
|
+
|
581
|
+
**Troubleshooting**: Enable `debug: true` to see HTML comments showing what hints were sent or why they were skipped.
|
582
|
+
|
583
|
+
**Requirements:** Rails 5.2+, HTTP/2 server, modern browsers. Gracefully degrades if not supported.
|
584
|
+
|
566
585
|
### Images in Stylesheets
|
567
586
|
|
568
587
|
If you want to use images in your stylesheets:
|
data/docs/configuration.md
CHANGED
@@ -11,6 +11,8 @@ This guide covers all configuration options available in `config/shakapacker.yml
|
|
11
11
|
- [Development Server](#development-server)
|
12
12
|
- [Compilation Options](#compilation-options)
|
13
13
|
- [Advanced Options](#advanced-options)
|
14
|
+
- [Subresource Integrity](#integrity)
|
15
|
+
- [Early Hints](#early_hints)
|
14
16
|
- [Environment-Specific Configuration](#environment-specific-configuration)
|
15
17
|
- [Build Configurations (config/shakapacker-builds.yml)](#build-configurations-configshakapacker-buildsyml)
|
16
18
|
|
@@ -485,6 +487,37 @@ integrity:
|
|
485
487
|
|
486
488
|
See [Subresource Integrity Guide](subresource_integrity.md) for details.
|
487
489
|
|
490
|
+
### `early_hints`
|
491
|
+
|
492
|
+
**Type:** `object`
|
493
|
+
**Default:** `{ enabled: false, css: "preload", js: "preload" }`
|
494
|
+
**Requires:** Rails 5.2+, HTTP/2 server
|
495
|
+
|
496
|
+
Automatically send HTTP 103 Early Hints for faster asset loading.
|
497
|
+
|
498
|
+
```yaml
|
499
|
+
early_hints:
|
500
|
+
enabled: true # Master switch (default: false)
|
501
|
+
css: "preload" # 'preload' | 'prefetch' | 'none' (default: 'preload')
|
502
|
+
js: "preload" # 'preload' | 'prefetch' | 'none' (default: 'preload')
|
503
|
+
```
|
504
|
+
|
505
|
+
**Options:**
|
506
|
+
|
507
|
+
- `enabled`: Enable/disable early hints (default: `false`)
|
508
|
+
- `css`: Hint type for CSS - `'preload'`, `'prefetch'`, or `'none'` (default: `'preload'`)
|
509
|
+
- `js`: Hint type for JS - `'preload'`, `'prefetch'`, or `'none'` (default: `'preload'`)
|
510
|
+
|
511
|
+
⚠️ **Performance note**: May improve or hurt page load depending on content. Configure per-page for best results.
|
512
|
+
|
513
|
+
See the [Early Hints Guide](early_hints.md) for:
|
514
|
+
|
515
|
+
- Performance considerations and warnings
|
516
|
+
- Per-page configuration (`configure_pack_early_hints`)
|
517
|
+
- Dynamic configuration examples
|
518
|
+
- Hero image preloading with `preload_link_tag`
|
519
|
+
- Troubleshooting and testing recommendations
|
520
|
+
|
488
521
|
## Environment-Specific Configuration
|
489
522
|
|
490
523
|
Shakapacker supports per-environment configuration with fallback logic:
|
@@ -728,4 +761,6 @@ See [Troubleshooting Guide](troubleshooting.md) for more help.
|
|
728
761
|
- [Deployment Guide](deployment.md)
|
729
762
|
- [CDN Setup Guide](cdn_setup.md)
|
730
763
|
- [Precompile Hook Guide](precompile_hook.md)
|
764
|
+
- [Early Hints Guide](early_hints.md)
|
765
|
+
- [Subresource Integrity Guide](subresource_integrity.md)
|
731
766
|
- [Troubleshooting Guide](troubleshooting.md)
|
data/docs/early_hints.md
ADDED
@@ -0,0 +1,440 @@
|
|
1
|
+
# HTTP 103 Early Hints Guide
|
2
|
+
|
3
|
+
This guide shows you how to use HTTP 103 Early Hints with Shakapacker to optimize page load performance.
|
4
|
+
|
5
|
+
## What are Early Hints?
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
⚠️ **Critical**: Preloading JavaScript may hurt your LCP (Largest Contentful Paint) metric if you have large images, videos, or other content that should load first. **Careful experimentation and performance measurement is required.**
|
10
|
+
|
11
|
+
### Priority Levels: Preload vs Prefetch vs None
|
12
|
+
|
13
|
+
Early hints let you control browser download priority for assets:
|
14
|
+
|
15
|
+
- **`preload`** - **Prioritize**: High priority, browser downloads immediately before HTML parsing. Use for critical assets needed for initial render.
|
16
|
+
- **`prefetch`** - **De-prioritize**: Low priority, browser downloads when idle (doesn't compete for bandwidth). Use for non-critical assets or future navigation.
|
17
|
+
- **`none`** - **Default behavior**: No hint sent. Browser discovers asset when parsing HTML (normal page load behavior).
|
18
|
+
|
19
|
+
### Performance Considerations
|
20
|
+
|
21
|
+
⚠️ **Important**: Different pages have different performance characteristics:
|
22
|
+
|
23
|
+
- **LCP Impact**: Preloading JS/CSS competes for bandwidth with images/videos, potentially delaying LCP
|
24
|
+
- **Hero Images**: Pages with large hero images usually perform **worse** with JS/CSS preload
|
25
|
+
- **Interactive Apps**: Dashboards and SPAs may benefit from aggressive JS preload
|
26
|
+
- **Content Sites**: Blogs and marketing sites often need conservative hints (prefetch or none)
|
27
|
+
- **Recommendation**: Configure hints **per-page** based on content, measure with real user data
|
28
|
+
|
29
|
+
## Quick Start
|
30
|
+
|
31
|
+
### 1. Global Configuration
|
32
|
+
|
33
|
+
```yaml
|
34
|
+
# config/shakapacker.yml
|
35
|
+
production:
|
36
|
+
early_hints:
|
37
|
+
enabled: true # Master switch
|
38
|
+
css: "preload" # 'preload' | 'prefetch' | 'none'
|
39
|
+
js: "preload" # 'preload' | 'prefetch' | 'none'
|
40
|
+
```
|
41
|
+
|
42
|
+
**Defaults**: When `enabled: true`, both `css` and `js` default to `'preload'` if not specified.
|
43
|
+
|
44
|
+
**Testing**: See the [Feature Testing Guide](feature_testing.md#http-103-early-hints) for detailed instructions on verifying early hints are working using browser DevTools or curl.
|
45
|
+
|
46
|
+
### 2. Per-Page Configuration (Recommended)
|
47
|
+
|
48
|
+
Configure hints based on your page content:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class PostsController < ApplicationController
|
52
|
+
# Image-heavy landing page - don't compete with images
|
53
|
+
configure_pack_early_hints only: [:index], css: 'none', js: 'prefetch'
|
54
|
+
|
55
|
+
# Interactive post editor - JS is critical
|
56
|
+
configure_pack_early_hints only: [:edit], css: 'preload', js: 'preload'
|
57
|
+
|
58
|
+
# API endpoints - no hints needed
|
59
|
+
skip_send_pack_early_hints only: [:api_data]
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
### 3. Dynamic Configuration
|
64
|
+
|
65
|
+
Configure based on content:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class PostsController < ApplicationController
|
69
|
+
def show
|
70
|
+
@post = Post.find(params[:id])
|
71
|
+
|
72
|
+
if @post.has_hero_video?
|
73
|
+
# Video is LCP - don't compete
|
74
|
+
configure_pack_early_hints all: 'none'
|
75
|
+
elsif @post.interactive?
|
76
|
+
# JS needed for interactivity
|
77
|
+
configure_pack_early_hints css: 'prefetch', js: 'preload'
|
78
|
+
else
|
79
|
+
# Standard blog post
|
80
|
+
configure_pack_early_hints css: 'preload', js: 'prefetch'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### 4. Preloading Hero Images and Videos
|
87
|
+
|
88
|
+
Use Rails' built-in `preload_link_tag` to preload hero images, videos, and other LCP resources. Rails automatically sends these as early hints:
|
89
|
+
|
90
|
+
```erb
|
91
|
+
<%# app/views/layouts/application.html.erb %>
|
92
|
+
<!DOCTYPE html>
|
93
|
+
<html>
|
94
|
+
<head>
|
95
|
+
<%= preload_link_tag image_path('hero.jpg'), as: 'image', type: 'image/jpeg' %>
|
96
|
+
<%= preload_link_tag video_path('intro.mp4'), as: 'video', type: 'video/mp4' %>
|
97
|
+
</head>
|
98
|
+
<body>
|
99
|
+
<%= yield %>
|
100
|
+
</body>
|
101
|
+
</html>
|
102
|
+
```
|
103
|
+
|
104
|
+
**Dynamic preloading in views:**
|
105
|
+
|
106
|
+
```erb
|
107
|
+
<%# app/views/posts/show.html.erb %>
|
108
|
+
<% if @post.hero_image_url.present? %>
|
109
|
+
<%= preload_link_tag @post.hero_image_url, as: 'image' %>
|
110
|
+
<% end %>
|
111
|
+
```
|
112
|
+
|
113
|
+
**Benefits:**
|
114
|
+
|
115
|
+
- ✅ Standard Rails API - no custom Shakapacker code needed
|
116
|
+
- ✅ Automatically sends early hints when server supports it
|
117
|
+
- ✅ Works with `image_path`, `video_path`, `asset_path` helpers
|
118
|
+
- ✅ Supports all standard attributes: `as`, `type`, `crossorigin`, `integrity`
|
119
|
+
|
120
|
+
**When to preload images/videos:**
|
121
|
+
|
122
|
+
- Hero images that are LCP (Largest Contentful Paint) elements
|
123
|
+
- Above-the-fold images critical for initial render
|
124
|
+
- Background videos that play on page load
|
125
|
+
|
126
|
+
**Performance tip:** Don't over-preload! Each preload competes for bandwidth. Focus only on critical resources that improve LCP.
|
127
|
+
|
128
|
+
See [Rails preload_link_tag docs](https://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-preload_link_tag) for full API.
|
129
|
+
|
130
|
+
## Controller Configuration
|
131
|
+
|
132
|
+
#### Skip Early Hints Entirely
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class ApiController < ApplicationController
|
136
|
+
# Skip for entire controller
|
137
|
+
skip_send_pack_early_hints
|
138
|
+
end
|
139
|
+
|
140
|
+
class PostsController < ApplicationController
|
141
|
+
# Skip for specific actions
|
142
|
+
skip_send_pack_early_hints only: [:api_endpoint, :feed]
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
#### Configure Per Action (Class Method)
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class PostsController < ApplicationController
|
150
|
+
# Configure specific actions
|
151
|
+
configure_pack_early_hints only: [:show], css: 'prefetch', js: 'preload'
|
152
|
+
configure_pack_early_hints only: [:gallery], css: 'none', js: 'none'
|
153
|
+
|
154
|
+
# Use 'all' shortcut
|
155
|
+
configure_pack_early_hints only: [:about], all: 'prefetch'
|
156
|
+
|
157
|
+
# Mix general and specific (specific wins)
|
158
|
+
configure_pack_early_hints only: [:dashboard], all: 'preload', css: 'prefetch'
|
159
|
+
# Result: css='prefetch', js='preload'
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
#### Configure in Action Method
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
class PostsController < ApplicationController
|
167
|
+
def show
|
168
|
+
@post = Post.find(params[:id])
|
169
|
+
|
170
|
+
# Configure based on runtime logic
|
171
|
+
if @post.video_content?
|
172
|
+
configure_pack_early_hints css: 'none', js: 'none'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
#### Configure in Before Action
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
class PostsController < ApplicationController
|
182
|
+
before_action :optimize_for_images, only: [:gallery, :portfolio]
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def optimize_for_images
|
187
|
+
configure_pack_early_hints css: 'prefetch', js: 'prefetch'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
## Configuration Precedence
|
193
|
+
|
194
|
+
Settings are applied in this order (later overrides earlier):
|
195
|
+
|
196
|
+
1. **Global** (shakapacker.yml) - project defaults
|
197
|
+
2. **Controller class** (configure_pack_early_hints) - per-action defaults
|
198
|
+
3. **Manual call** (send_pack_early_hints in view) - explicit override
|
199
|
+
|
200
|
+
Within a single configuration, `all:` is applied first, then specific `css:` and `js:` values override it.
|
201
|
+
|
202
|
+
## Usage Examples by Scenario
|
203
|
+
|
204
|
+
### Scenario 1: Image-Heavy Landing Page
|
205
|
+
|
206
|
+
**Problem**: Large hero image is LCP, JS/CSS hints compete for bandwidth and delay image loading
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
class HomeController < ApplicationController
|
210
|
+
def index
|
211
|
+
# Save bandwidth for hero image
|
212
|
+
configure_pack_early_hints css: 'none', js: 'prefetch'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
**Why**:
|
218
|
+
|
219
|
+
- `css: 'none'` - No hint sent, CSS discovered normally (saves bandwidth)
|
220
|
+
- `js: 'prefetch'` - Low priority hint, JS downloads when idle (doesn't compete)
|
221
|
+
- **Result**: Hero image gets full bandwidth priority for better LCP
|
222
|
+
|
223
|
+
### Scenario 2: Interactive Dashboard
|
224
|
+
|
225
|
+
**Problem**: App is useless without JavaScript
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
class DashboardController < ApplicationController
|
229
|
+
# JS is critical for all actions
|
230
|
+
configure_pack_early_hints all: 'preload'
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
**Why**: Fast JS load is more important than LCP
|
235
|
+
|
236
|
+
### Scenario 3: Blog with Varied Content
|
237
|
+
|
238
|
+
**Problem**: Article pages have images, index doesn't
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
class ArticlesController < ApplicationController
|
242
|
+
# Index: no large images
|
243
|
+
configure_pack_early_hints only: [:index], css: 'preload', js: 'preload'
|
244
|
+
|
245
|
+
# Show: featured images
|
246
|
+
configure_pack_early_hints only: [:show], css: 'prefetch', js: 'prefetch'
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
250
|
+
**Why**: Different pages have different performance needs
|
251
|
+
|
252
|
+
### Scenario 4: Mixed Content Types
|
253
|
+
|
254
|
+
**Problem**: Posts contain videos, images, or interactive content
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
class PostsController < ApplicationController
|
258
|
+
def show
|
259
|
+
@post = Post.find(params[:id])
|
260
|
+
|
261
|
+
case @post.content_type
|
262
|
+
when 'video'
|
263
|
+
# Video is LCP
|
264
|
+
configure_pack_early_hints all: 'none'
|
265
|
+
when 'interactive'
|
266
|
+
# JS needed immediately
|
267
|
+
configure_pack_early_hints css: 'prefetch', js: 'preload'
|
268
|
+
when 'image_gallery'
|
269
|
+
# Images are LCP
|
270
|
+
configure_pack_early_hints all: 'prefetch'
|
271
|
+
else
|
272
|
+
# Standard text post
|
273
|
+
configure_pack_early_hints css: 'preload', js: 'prefetch'
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
**Why**: Dynamic configuration based on actual content
|
280
|
+
|
281
|
+
### Scenario 5: E-commerce Product Pages
|
282
|
+
|
283
|
+
**Problem**: Product images are critical, but checkout needs JS
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
class ProductsController < ApplicationController
|
287
|
+
# Product page: images are critical
|
288
|
+
configure_pack_early_hints only: [:show], css: 'prefetch', js: 'prefetch'
|
289
|
+
|
290
|
+
# Checkout: form validation needs JS
|
291
|
+
configure_pack_early_hints only: [:checkout], css: 'preload', js: 'preload'
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
**Why**: Shopping vs checkout have different needs
|
296
|
+
|
297
|
+
## How It Works
|
298
|
+
|
299
|
+
Shakapacker automatically sends early hints after your views render:
|
300
|
+
|
301
|
+
```text
|
302
|
+
1. Request arrives
|
303
|
+
2. Controller action runs → Database queries, business logic
|
304
|
+
3. Views render → append_javascript_pack_tag('admin')
|
305
|
+
4. Layout renders → javascript_pack_tag, stylesheet_pack_tag
|
306
|
+
5. after_action hook → Reads configuration and queues
|
307
|
+
6. HTTP 103 sent → rel=preload or rel=prefetch based on config
|
308
|
+
7. HTTP 200 sent → Full HTML response
|
309
|
+
```
|
310
|
+
|
311
|
+
**Important timing note**: HTTP 103 is sent after rendering completes but before the final HTTP 200 response. This means:
|
312
|
+
|
313
|
+
- ✅ **Benefits**: Browser starts downloading assets while the server transmits the final HTML response
|
314
|
+
- ❌ **Limitations**: Does NOT help during database queries or view rendering—only helps with network transfer time
|
315
|
+
- 💡 **Best for**: Pages with large HTML responses where asset downloads can happen in parallel with HTML transmission
|
316
|
+
|
317
|
+
## Advanced: Manual Control
|
318
|
+
|
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' %>
|
324
|
+
|
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
|
+
```
|
340
|
+
|
341
|
+
**Use cases:** Layout-specific optimizations, conditional hints based on view variables, A/B testing
|
342
|
+
|
343
|
+
## Requirements
|
344
|
+
|
345
|
+
- **Rails 5.2+** (for `request.send_early_hints` support)
|
346
|
+
- **Web server with HTTP/2 and early hints:**
|
347
|
+
- Puma 5+ ✅
|
348
|
+
- nginx 1.13+ with ngx_http_v2_module ✅
|
349
|
+
- Other HTTP/2 servers with early hints support
|
350
|
+
- **Modern browsers:**
|
351
|
+
- Chrome/Edge/Firefox 103+ ✅
|
352
|
+
- Safari 16.4+ ✅
|
353
|
+
|
354
|
+
If requirements not met, feature gracefully degrades with no errors.
|
355
|
+
|
356
|
+
## Quick Reference
|
357
|
+
|
358
|
+
### Priority levels and when to use each:
|
359
|
+
|
360
|
+
- **`preload`** (Prioritize): Critical assets on text-heavy pages, SPAs, pages without large images
|
361
|
+
- **`prefetch`** (De-prioritize): Non-critical assets, pages with large LCP images/videos (downloads when idle)
|
362
|
+
- **`none`** (Default behavior): Image/video-heavy pages, API endpoints, SSR pages (no hint sent)
|
363
|
+
|
364
|
+
### Testing checklist:
|
365
|
+
|
366
|
+
1. Measure LCP with Chrome DevTools Performance tab
|
367
|
+
2. Test on real mobile devices
|
368
|
+
3. A/B test configurations with real user data
|
369
|
+
4. Monitor field data with RUM tools
|
370
|
+
5. Test each page type separately
|
371
|
+
|
372
|
+
## Troubleshooting
|
373
|
+
|
374
|
+
For comprehensive testing instructions including browser DevTools and curl methods, see the [Feature Testing Guide: HTTP 103 Early Hints](feature_testing.md#http-103-early-hints).
|
375
|
+
|
376
|
+
### Debug Mode
|
377
|
+
|
378
|
+
Enable debug mode to see what early hints are being sent (or why they weren't sent):
|
379
|
+
|
380
|
+
```yaml
|
381
|
+
# config/shakapacker.yml
|
382
|
+
production:
|
383
|
+
early_hints:
|
384
|
+
enabled: true
|
385
|
+
debug: true # Outputs debug info as HTML comments
|
386
|
+
```
|
387
|
+
|
388
|
+
Debug mode adds HTML comments to your page showing:
|
389
|
+
|
390
|
+
- Whether hints were sent or skipped
|
391
|
+
- What pack names were processed
|
392
|
+
- What Link headers were sent
|
393
|
+
- HTTP/2 support status
|
394
|
+
|
395
|
+
View page source and look for `<!-- Shakapacker Early Hints Debug -->` comments.
|
396
|
+
|
397
|
+
**Early hints not appearing:**
|
398
|
+
|
399
|
+
- **Enable debug mode first** to see what's happening
|
400
|
+
- **Check for proxy stripping**: If debug shows hints sent but curl/DevTools don't show `HTTP/2 103`, your reverse proxy or CDN (Control Plane, Cloudflare, AWS ALB/ELB, nginx) is likely stripping 103 responses. This is the **most common cause** of "missing" early hints
|
401
|
+
- Check `early_hints: enabled: true` in shakapacker.yml
|
402
|
+
- Verify HTTP/2 server (Puma 5+, nginx 1.13+)
|
403
|
+
- Check Network tab shows "h2" protocol and 103 status
|
404
|
+
|
405
|
+
**Reverse proxy stripping 103 responses:**
|
406
|
+
|
407
|
+
If debug mode shows hints are sent but they're not reaching clients, configure your proxy:
|
408
|
+
|
409
|
+
- **nginx**: Add `proxy_pass_header Link;` to pass through early hints (nginx 1.13+)
|
410
|
+
- **Cloudflare**: Enable "Early Hints" in Speed > Optimization (paid plans only)
|
411
|
+
- **AWS ALB/ELB**: Not supported - ALBs strip 103 responses. Workaround: skip ALB or use CloudFront
|
412
|
+
- **Control Plane**: Appears to strip 103 - Contact their support if you need early hints
|
413
|
+
|
414
|
+
See the [Feature Testing Guide](feature_testing.md#troubleshooting-early-hints) for detailed proxy configuration examples.
|
415
|
+
|
416
|
+
**Performance got worse:**
|
417
|
+
|
418
|
+
- Page likely has large images/videos as LCP
|
419
|
+
- Try `css: 'prefetch', js: 'prefetch'` or `all: 'none'`
|
420
|
+
- Measure LCP before and after changes
|
421
|
+
|
422
|
+
**Wrong hints sent:**
|
423
|
+
|
424
|
+
- Check configuration precedence (global → controller → manual)
|
425
|
+
- Verify values are strings: `'preload'` not `:preload`
|
426
|
+
- Check for typos (case-sensitive)
|
427
|
+
|
428
|
+
## References
|
429
|
+
|
430
|
+
### Shakapacker Documentation
|
431
|
+
|
432
|
+
- [Feature Testing Guide: HTTP 103 Early Hints](feature_testing.md#http-103-early-hints) - Detailed testing instructions with browser DevTools and curl
|
433
|
+
|
434
|
+
### External Resources
|
435
|
+
|
436
|
+
- [Rails API: send_early_hints](https://api.rubyonrails.org/classes/ActionDispatch/Request.html#method-i-send_early_hints)
|
437
|
+
- [RFC 8297: HTTP Early Hints](https://datatracker.ietf.org/doc/html/rfc8297)
|
438
|
+
- [MDN: rel=preload vs rel=prefetch](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel)
|
439
|
+
- [Web.dev: Optimize LCP](https://web.dev/optimize-lcp/)
|
440
|
+
- [HTTP 103 Explained](https://http.dev/103)
|