shakapacker 9.3.0.beta.1 → 9.3.0.beta.4

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: c503694d35261163ce77ef2efb38c85b5365c7e0c817f06d0c3a9b7e9cd8ce0e
4
- data.tar.gz: 6bea7b663bdb25fc2428bba429ec2f773a4298e91c2fdf2af9b8d0be5f089dd1
3
+ metadata.gz: 2f2fafddfa10d364b8e62946786c16d9206ecffadf82b719e3ffeb2100608da0
4
+ data.tar.gz: 0e0cdc08d53e64e8066d7232aba1022b6a7708067d56ff7ba738803bfbc49a78
5
5
  SHA512:
6
- metadata.gz: dca52dbb54ea99fd8ad7b3e5cc9de4f0c5446a5c7a2f855032355d5b224fc91e74055784e92733ac6b41548e9676b4ed76c1bf8f4e88d024d6984ddae79f49dd
7
- data.tar.gz: 702f88a69f5116ab0bc2bbfbff2d650cc5d542dd71a4716e4083e1ef79785a0a1a3eeb734924f1753ecafbe633a9bc21d29d86c60911caa6fe6e14acc4531d95
6
+ metadata.gz: dcbb4604f956d78acaea611bb760ecea97a1afcc15bc390b9c5a64c5d9654f5d08e83c57ed24da55aca0a1a98da074a0047a754cadf5f74ee8ba832fb6e93b3e
7
+ data.tar.gz: 47b7d7887f770f5712b5f020afd260fcefcb4b09a0c707e1b2bc921c38d2944bf6045b6b9752295790806018d98cd95d43313f9079719bcb3c808ff9f6e6c2ad
@@ -0,0 +1,46 @@
1
+ name: ESLint Validation
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - "eslint.config.js"
7
+ - "package.json"
8
+ - "package/**/*.js"
9
+ - "test/**/*.js"
10
+ - ".github/workflows/eslint-validation.yml"
11
+
12
+ jobs:
13
+ validate:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Setup Node
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "20"
22
+ cache: "yarn"
23
+
24
+ - name: Install dependencies
25
+ run: yarn install --frozen-lockfile
26
+
27
+ - name: Validate ESLint config
28
+ run: |
29
+ echo "Validating ESLint configuration..."
30
+ node -e "const config = require('./eslint.config.js'); console.log('✓ Config is valid with', config.length, 'rule sets')"
31
+
32
+ - name: Run ESLint
33
+ run: |
34
+ echo "Running ESLint on allowed files..."
35
+ yarn eslint . --max-warnings 5
36
+
37
+ - name: Check warning count
38
+ run: |
39
+ echo "Checking warning count..."
40
+ WARNING_COUNT=$(yarn eslint . 2>&1 | grep -E "^✖.*warning" | grep -oE "[0-9]+ warning" | cut -d' ' -f1)
41
+ echo "Current warning count: $WARNING_COUNT"
42
+ if [ "$WARNING_COUNT" -gt "5" ]; then
43
+ echo "❌ Too many warnings: $WARNING_COUNT (max allowed: 5)"
44
+ exit 1
45
+ fi
46
+ echo "✓ Warning count is acceptable"
@@ -0,0 +1,159 @@
1
+ # ESLint Technical Debt Documentation
2
+
3
+ This document tracks the ESLint errors currently suppressed in the codebase and outlines the plan to address them.
4
+
5
+ ## Current Approach
6
+
7
+ **As of 2025-10-14**: All TypeScript files in `package/` directory are temporarily excluded from linting via the ignore pattern `package/**/*.ts` in `eslint.config.js`. This allows the project to adopt ESLint configuration without requiring immediate fixes to all existing issues.
8
+
9
+ **Latest Update**: Fixed all `no-param-reassign` violations by refactoring to create new objects instead of mutating parameters (7 violations resolved).
10
+
11
+ ## Current Linting Status
12
+
13
+ **Files currently linted** (`test/**/*.js`, `scripts/*.js`):
14
+
15
+ - ✅ **0 errors** (CI passing)
16
+ - ⚠️ **3 warnings** (acceptable, won't block CI)
17
+ - 1x unused eslint-disable directive in `scripts/remove-use-strict.js`
18
+ - 2x jest/no-disabled-tests in test files (expected for conditional test skipping)
19
+
20
+ **TypeScript files** (currently ignored via `package/**/*.ts`):
21
+
22
+ - **Estimated suppressed errors: ~163** (from sample analysis)
23
+ - TypeScript type-safety issues: ~114 (70%)
24
+ - Style/convention issues: ~49 (30%)
25
+
26
+ **Target**: Reduce suppressed errors by 50% within Q1 2025
27
+ **Last Updated**: 2025-10-15
28
+
29
+ ## Priority Matrix
30
+
31
+ | Category | Impact | Effort | Priority | Count |
32
+ | ------------------------------------ | ------ | ------ | -------- | ----- |
33
+ | `@typescript-eslint/no-explicit-any` | High | High | P1 | 22 |
34
+ | `@typescript-eslint/no-unsafe-*` | High | High | P1 | 85 |
35
+ | `config.ts` type safety | High | Medium | P1 | 7 |
36
+ | `no-param-reassign` | Medium | Low | P2 | 0 |
37
+ | `class-methods-use-this` | Low | Low | P3 | 0 |
38
+ | `no-nested-ternary` | Low | Low | P3 | 0 |
39
+ | `import/prefer-default-export` | Low | Medium | P3 | 9 |
40
+ | `global-require` | Medium | High | P2 | 3 |
41
+ | Other style issues | Low | Low | P3 | 31 |
42
+
43
+ ## Categories of Suppressed Errors
44
+
45
+ ### 1. TypeScript Type Safety (Requires Major Refactoring)
46
+
47
+ #### `@typescript-eslint/no-explicit-any` (22 instances)
48
+
49
+ **Files affected:** `configExporter/`, `config.ts`, `utils/`
50
+ **Why suppressed:** These require careful type definitions and potentially breaking API changes
51
+ **Fix strategy:** Create proper type definitions for configuration objects and YAML parsing
52
+
53
+ #### `@typescript-eslint/no-unsafe-*` (85 instances)
54
+
55
+ - `no-unsafe-assignment`: 47 instances
56
+ - `no-unsafe-member-access`: 20 instances
57
+ - `no-unsafe-call`: 8 instances
58
+ - `no-unsafe-return`: 8 instances
59
+ - `no-unsafe-argument`: 7 instances
60
+ **Why suppressed:** These stem from `any` types and dynamic property access
61
+ **Fix strategy:** Requires comprehensive type refactoring alongside `no-explicit-any` fixes
62
+
63
+ ### 2. Module System (Potential Breaking Changes)
64
+
65
+ #### `global-require` (3 instances)
66
+
67
+ **Files affected:** `configExporter/cli.ts`
68
+ **Why suppressed:** Dynamic require calls are needed for conditional module loading
69
+ **Fix strategy:** Would require converting to ES modules with dynamic imports
70
+
71
+ #### `import/prefer-default-export` (9 instances)
72
+
73
+ **Files affected:** Multiple single-export modules
74
+ **Why suppressed:** Adding default exports alongside named exports could break consumers
75
+ **Fix strategy:** Can be fixed non-breaking by adding default exports that match named exports
76
+
77
+ ### 3. Code Style (Can Be Fixed)
78
+
79
+ #### `class-methods-use-this` (0 instances)
80
+
81
+ ✅ **FIXED** - All FileWriter methods that didn't use instance state have been converted to static methods
82
+
83
+ #### `no-nested-ternary` (0 instances)
84
+
85
+ ✅ **FIXED** - All nested ternary expressions have been refactored to if-else statements for better readability
86
+
87
+ #### `no-param-reassign` (0 instances)
88
+
89
+ ✅ **FIXED** - Refactored `applyDefaults` function to return new objects instead of mutating parameters
90
+
91
+ #### `no-underscore-dangle` (2 instances)
92
+
93
+ **Fix strategy:** Rename variables or add exceptions for Node internals
94
+
95
+ ### 4. Control Flow
96
+
97
+ #### `no-await-in-loop` (1 instance)
98
+
99
+ **Fix strategy:** Use `Promise.all()` for parallel execution
100
+
101
+ #### `no-continue` (1 instance)
102
+
103
+ **Fix strategy:** Refactor loop logic
104
+
105
+ ## Recommended Approach
106
+
107
+ ### Phase 1: Non-Breaking Fixes
108
+
109
+ ✅ Completed:
110
+
111
+ - Fixed `no-use-before-define` by reordering functions
112
+ - Fixed redundant type constituents with `string & {}` pattern
113
+ - Added proper type annotations for `requireOrError` calls
114
+ - Configured appropriate global rule disables (`no-console`, `no-restricted-syntax`)
115
+ - ✅ **Fixed `class-methods-use-this`** - Converted FileWriter methods to static methods
116
+ - ✅ **Fixed `no-nested-ternary`** - Refactored to if-else statements for better readability
117
+ - ✅ **Fixed `no-param-reassign`** - Refactored `applyDefaults` to return new objects instead of mutating parameters
118
+
119
+ 🔧 Could still fix (low risk):
120
+
121
+ - `no-useless-escape` - Remove unnecessary escapes
122
+ - Unused variables - Remove or prefix with underscore
123
+
124
+ ### Phase 2: Follow-up PRs (Non-Breaking)
125
+
126
+ - Systematic type safety improvements file by file
127
+ - Add explicit type definitions for configuration objects
128
+ - Replace `any` with `unknown` where possible
129
+
130
+ ### Phase 3: Future Major Version (Breaking Changes)
131
+
132
+ - Convert `export =` to `export default`
133
+ - Convert `require()` to ES6 imports
134
+ - Full TypeScript strict mode compliance
135
+ - Provide codemod for automatic migration
136
+
137
+ ## Configuration Strategy
138
+
139
+ The current approach uses file-specific overrides to suppress errors in affected files while maintaining strict checking elsewhere. This allows:
140
+
141
+ 1. New code to follow strict standards
142
+ 2. Gradual refactoring of existing code
143
+ 3. Clear visibility of technical debt
144
+
145
+ ## Issue Tracking
146
+
147
+ GitHub issues should be created for each category:
148
+
149
+ 1. [ ] Issue: Type safety refactoring for configExporter module
150
+ 2. [ ] Issue: Type safety for dynamic config loading
151
+ 3. [ ] Issue: Convert class methods to static where appropriate
152
+ 4. [ ] Issue: Module system modernization (ES6 modules)
153
+ 5. [ ] Issue: Create codemod for breaking changes migration
154
+
155
+ ## Notes
156
+
157
+ - All suppressed errors are documented in `eslint.config.js` with TODO comments
158
+ - The suppressions are scoped to specific files to prevent spreading technical debt
159
+ - New code should not add to these suppressions
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shakapacker (9.3.0.beta.1)
4
+ shakapacker (9.3.0.beta.4)
5
5
  activesupport (>= 5.2)
6
6
  package_json
7
7
  rack-proxy (>= 0.6.1)
data/README.md CHANGED
@@ -477,8 +477,8 @@ And the main layout has:
477
477
  is the same as using this in the main layout:
478
478
 
479
479
  ```erb
480
- <%= javascript_pack_tag 'calendar', 'map', application' %>
481
- <%= stylesheet_pack_tag 'calendar', 'map', application' %>
480
+ <%= javascript_pack_tag 'calendar', 'map', 'application' %>
481
+ <%= stylesheet_pack_tag 'calendar', 'map', 'application' %>
482
482
  ```
483
483
 
484
484
  However, you typically can't do that in the main layout, as the view and partial codes will depend on the route.
@@ -490,12 +490,13 @@ Thus, you can distribute the logic of what packs are needed for any route. All t
490
490
  The typical issue is that your layout might reference some partials that need to configure packs. A good way to solve this problem is to use `content_for` to ensure that the code to render your partial comes before the call to `javascript_pack_tag`.
491
491
 
492
492
  ```erb
493
- <% content_for :footer do
494
- render 'shared/footer' %>
493
+ <% content_for :footer do %>
494
+ <%= render 'shared/footer' %>
495
+ <% end %>
495
496
 
496
497
  <%= javascript_pack_tag %>
497
498
 
498
- <%= content_for :footer %>
499
+ <%= yield :footer %>
499
500
  ```
500
501
 
501
502
  There is also `prepend_javascript_pack_tag` that will put the entry at the front of the queue. This is handy when you want an entry in the main layout to go before the partial and main layout `append_javascript_pack_tag` entries.
@@ -522,11 +523,13 @@ And the main layout has:
522
523
  is the same as using this in the main layout:
523
524
 
524
525
  ```erb
525
- <%= javascript_pack_tag 'main', 'calendar', 'map', application' %>
526
+ <%= javascript_pack_tag 'main', 'calendar', 'map', 'application' %>
526
527
  ```
527
528
 
528
529
  For alternative options for setting the additional packs, [see this discussion](https://github.com/shakacode/shakapacker/issues/39).
529
530
 
531
+ **Important:** To prevent FOUC (Flash of Unstyled Content), always place `stylesheet_pack_tag` in the `<head>` section of your layout. When using `append_*` helpers with dynamic pack loading (e.g., React on Rails), use the `content_for` pattern to control execution order. See the [Preventing FOUC guide](./docs/preventing_fouc.md) for detailed examples.
532
+
530
533
  #### View Helper: `asset_pack_path`
531
534
 
532
535
  If you want to link a static asset for `<img />` tag, you can use the `asset_pack_path` helper:
@@ -6,6 +6,7 @@ This document provides step-by-step instructions for the most common upgrade sce
6
6
 
7
7
  ## Table of Contents
8
8
 
9
+ - [Upgrading Shakapacker](#upgrading-shakapacker)
9
10
  - [Migrating Package Managers](#migrating-package-managers)
10
11
  - [Yarn to npm](#yarn-to-npm)
11
12
  - [npm to Yarn](#npm-to-yarn)
@@ -15,6 +16,85 @@ This document provides step-by-step instructions for the most common upgrade sce
15
16
 
16
17
  ---
17
18
 
19
+ ## Upgrading Shakapacker
20
+
21
+ > **⚠️ Important:** Shakapacker is both a Ruby gem AND an npm package. **You must update BOTH** when upgrading.
22
+
23
+ Shakapacker consists of two components that must be updated together:
24
+
25
+ 1. **Ruby gem** - provides Rails integration and view helpers
26
+ 2. **npm package** - provides webpack/rspack configuration and build tools
27
+
28
+ ### Upgrade Steps
29
+
30
+ #### 1. Update `Gemfile`
31
+
32
+ ```ruby
33
+ gem "shakapacker", "9.3.0" # or the version you want to upgrade to
34
+ ```
35
+
36
+ **Pre-release versions:** Ruby gems use dot notation (e.g., `"9.3.0.beta.1"`)
37
+
38
+ #### 2. Update `package.json`
39
+
40
+ ```json
41
+ {
42
+ "dependencies": {
43
+ "shakapacker": "9.3.0"
44
+ }
45
+ }
46
+ ```
47
+
48
+ **Pre-release versions:** npm uses hyphen notation (e.g., `"9.3.0-beta.1"`)
49
+
50
+ #### 3. Run bundler and package manager
51
+
52
+ ```bash
53
+ bundle update shakapacker
54
+ yarn install # or npm install, pnpm install, bun install
55
+ ```
56
+
57
+ #### 4. Test your build
58
+
59
+ ```bash
60
+ bin/shakapacker
61
+ bin/shakapacker-dev-server
62
+ ```
63
+
64
+ ### Why Both Must Be Updated
65
+
66
+ - **Mismatched versions can cause build failures** - The Ruby gem expects specific configuration formats from the npm package
67
+ - **Feature compatibility** - New features in the gem require corresponding npm package updates
68
+ - **Bug fixes** - Fixes often span both Ruby and JavaScript code
69
+
70
+ ### Version Format Differences
71
+
72
+ Note that pre-release versions use different formats:
73
+
74
+ | Component | Stable Version | Pre-release Version |
75
+ | ------------ | -------------- | ------------------- |
76
+ | Gemfile | `"9.3.0"` | `"9.3.0.beta.1"` |
77
+ | package.json | `"9.3.0"` | `"9.3.0-beta.1"` |
78
+
79
+ ### Finding the Latest Version
80
+
81
+ - **Ruby gem:** Check [RubyGems.org](https://rubygems.org/gems/shakapacker)
82
+ - **npm package:** Check [npmjs.com](https://www.npmjs.com/package/shakapacker)
83
+ - **Releases:** See [GitHub Releases](https://github.com/shakacode/shakapacker/releases)
84
+
85
+ ### Major Version Upgrades
86
+
87
+ For major version upgrades, always consult the version-specific upgrade guides for breaking changes and new features:
88
+
89
+ - [V9 Upgrade Guide](./v9_upgrade.md) - Upgrading from v8 to v9 (includes CSS Modules changes, SWC defaults, and more)
90
+ - [V8 Upgrade Guide](./v8_upgrade.md) - Upgrading from v7 to v8
91
+ - [V7 Upgrade Guide](./v7_upgrade.md) - Upgrading from v6 to v7
92
+ - [V6 Upgrade Guide](./v6_upgrade.md) - Upgrading from v5 to v6
93
+
94
+ > **💡 Note:** Major version upgrades may include breaking changes. The steps above cover the basic gem/package updates that apply to all versions, but you should always review the version-specific guide for additional migration steps.
95
+
96
+ ---
97
+
18
98
  ## Migrating Package Managers
19
99
 
20
100
  ### Yarn to npm
@@ -12,6 +12,7 @@ This guide covers all configuration options available in `config/shakapacker.yml
12
12
  - [Compilation Options](#compilation-options)
13
13
  - [Advanced Options](#advanced-options)
14
14
  - [Environment-Specific Configuration](#environment-specific-configuration)
15
+ - [Build Configurations (config/shakapacker-builds.yml)](#build-configurations-configshakapacker-buildsyml)
15
16
 
16
17
  ## Basic Configuration
17
18
 
@@ -608,6 +609,106 @@ default: &default
608
609
  - packages/ui-components
609
610
  ```
610
611
 
612
+ ## Build Configurations (config/shakapacker-builds.yml)
613
+
614
+ Shakapacker supports defining reusable build configurations in `config/shakapacker-builds.yml`. This allows you to run predefined builds with a simple command, making it easy to switch between different build scenarios.
615
+
616
+ ### Creating a Build Configuration File
617
+
618
+ Generate `config/shakapacker-builds.yml` with example builds:
619
+
620
+ ```bash
621
+ bin/shakapacker --init # Creates config/shakapacker-builds.yml
622
+ ```
623
+
624
+ This generates a file with example builds for common scenarios (HMR development, standard development, and production).
625
+
626
+ ### Running Builds by Name
627
+
628
+ Once you have `config/shakapacker-builds.yml`, you can run builds by name:
629
+
630
+ ```bash
631
+ # List available builds
632
+ bin/shakapacker --list-builds
633
+
634
+ # Run a specific build
635
+ bin/shakapacker --build dev-hmr # Client bundle with HMR (automatically uses dev server)
636
+ bin/shakapacker --build prod # Client and server bundles for production
637
+ bin/shakapacker --build dev # Client bundle for development
638
+ ```
639
+
640
+ ### Build Configuration Format
641
+
642
+ Example `config/shakapacker-builds.yml`:
643
+
644
+ ```yaml
645
+ builds:
646
+ dev-hmr:
647
+ description: Client bundle with HMR (React Fast Refresh)
648
+ bundler: rspack # Optional: override assets_bundler from config/shakapacker.yml
649
+ environment:
650
+ NODE_ENV: development
651
+ RAILS_ENV: development
652
+ WEBPACK_SERVE: "true" # Automatically uses bin/shakapacker-dev-server
653
+ outputs:
654
+ - client
655
+ config: config/${BUNDLER}/custom.config.js # Optional: custom config file with variable substitution
656
+
657
+ prod:
658
+ description: Production client and server bundles
659
+ environment:
660
+ NODE_ENV: production
661
+ RAILS_ENV: production
662
+ outputs:
663
+ - client # Multiple outputs - builds both client and server bundles
664
+ - server
665
+ ```
666
+
667
+ ### Build Configuration Options
668
+
669
+ - **`description`** (optional): Human-readable description of the build
670
+ - **`bundler`** (optional): Override the default bundler from `config/shakapacker.yml` (`webpack` or `rspack`)
671
+ - **`dev_server`** (optional): Boolean flag to force routing to dev server (overrides environment variable detection)
672
+ - **`environment`**: Environment variables to set when running the build
673
+ - **`outputs`**: Array of output types - can include `client`, `server`, or both for multiple bundles in a single build
674
+ - **`config`** (optional): Custom config file path (supports `${BUNDLER}` variable substitution)
675
+ - **`bundler_env`** (optional): Key-value pairs passed as bundler `--env` flags (e.g., `{ analyze: true }` becomes `--env analyze=true`)
676
+
677
+ ### Automatic Dev Server Detection
678
+
679
+ Shakapacker automatically uses `bin/shakapacker-dev-server` instead of the regular build command when:
680
+
681
+ 1. The build has `dev_server: true` explicitly set (preferred method - takes precedence over environment variables), OR
682
+ 2. The build has `WEBPACK_SERVE=true` or `HMR=true` in its environment variables (fallback for backward compatibility)
683
+
684
+ Example:
685
+
686
+ ```bash
687
+ # These are equivalent:
688
+ bin/shakapacker --build dev-hmr
689
+ WEBPACK_SERVE=true bin/shakapacker-dev-server # (with dev-hmr environment vars)
690
+ ```
691
+
692
+ ### Variable Substitution
693
+
694
+ The `config` field supports `${BUNDLER}` substitution:
695
+
696
+ ```yaml
697
+ builds:
698
+ custom:
699
+ bundler: rspack
700
+ config: config/${BUNDLER}/custom.config.js # Becomes: config/rspack/custom.config.js
701
+ ```
702
+
703
+ ### When to Use Build Configurations
704
+
705
+ Build configurations are useful for:
706
+
707
+ - **Multiple build scenarios**: Use when you need different builds for HMR development, standard development, and production
708
+ - **CI/CD pipelines**: Use when you want predefined builds that can be referenced in deployment scripts
709
+ - **Team consistency**: Use to ensure all developers use the same build configurations
710
+ - **Complex setups**: Use to manage different bundler configs or environment variables for different scenarios
711
+
611
712
  ## Troubleshooting
612
713
 
613
714
  If you encounter configuration issues:
@@ -0,0 +1,132 @@
1
+ # Preventing FOUC (Flash of Unstyled Content)
2
+
3
+ ## Overview
4
+
5
+ FOUC (Flash of Unstyled Content) occurs when content is rendered before stylesheets load, causing a brief flash of unstyled content. This guide explains how to prevent FOUC when using Shakapacker's view helpers.
6
+
7
+ ## Basic Solution
8
+
9
+ Place `stylesheet_pack_tag` in the `<head>` section of your layout, not at the bottom of the `<body>`. This ensures styles load before content is rendered.
10
+
11
+ **Recommended layout structure:**
12
+
13
+ ```erb
14
+ <!DOCTYPE html>
15
+ <html>
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ <title>My App</title>
20
+
21
+ <%= stylesheet_pack_tag 'application', media: 'all' %>
22
+ <%= javascript_pack_tag 'application', defer: true %>
23
+ </head>
24
+ <body>
25
+ <%= yield %>
26
+ </body>
27
+ </html>
28
+ ```
29
+
30
+ ## Advanced: Using `content_for` with Dynamic Pack Loading
31
+
32
+ If you're using libraries that dynamically append packs during rendering (like React on Rails with `auto_load_bundle`), or if you need to append packs from views/partials, you must ensure that all `append_*` helpers execute before the pack tags render.
33
+
34
+ Rails' `content_for` pattern solves this execution order problem.
35
+
36
+ ### The `content_for :body_content` Pattern
37
+
38
+ This pattern renders the body content first, allowing all append calls to register before the pack tags in the head are rendered:
39
+
40
+ ```erb
41
+ <% content_for :body_content do %>
42
+ <%= render 'shared/header' %>
43
+ <%= yield %>
44
+ <%= render 'shared/footer' %>
45
+ <% end %>
46
+
47
+ <!DOCTYPE html>
48
+ <html>
49
+ <head>
50
+ <meta charset="UTF-8">
51
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
52
+ <title>My App</title>
53
+
54
+ <%= stylesheet_pack_tag 'application', media: 'all' %>
55
+ <%= javascript_pack_tag 'application', defer: true %>
56
+ </head>
57
+ <body>
58
+ <%= yield :body_content %>
59
+ </body>
60
+ </html>
61
+ ```
62
+
63
+ **How this works:**
64
+
65
+ 1. The `content_for :body_content` block executes first during template rendering
66
+ 2. Any `append_stylesheet_pack_tag` or `append_javascript_pack_tag` calls in your views/partials register their packs
67
+ 3. Libraries like React on Rails can auto-append component-specific packs during rendering
68
+ 4. The pack tags in `<head>` then render with all registered appends
69
+ 5. Finally, `yield :body_content` outputs the pre-rendered content
70
+
71
+ **Result:**
72
+
73
+ - ✅ All appends (explicit + auto) happen before pack tags
74
+ - ✅ Stylesheets load in head, eliminating FOUC
75
+ - ✅ Works with `auto_load_bundle` and similar features
76
+
77
+ ### Alternative: Using `yield :head` for Explicit Appends
78
+
79
+ For simpler cases where you know which packs you need upfront and can explicitly specify them, you can use `content_for :head` in your views and yield it in your layout.
80
+
81
+ **Layout (app/views/layouts/application.html.erb):**
82
+
83
+ ```erb
84
+ <!DOCTYPE html>
85
+ <html>
86
+ <head>
87
+ <meta charset="UTF-8">
88
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
89
+ <title>My App</title>
90
+
91
+ <%= yield :head %>
92
+ <%= stylesheet_pack_tag 'application', media: 'all' %>
93
+ <%= javascript_pack_tag 'application', defer: true %>
94
+ </head>
95
+ <body>
96
+ <%= yield %>
97
+ </body>
98
+ </html>
99
+ ```
100
+
101
+ **View (app/views/pages/show.html.erb):**
102
+
103
+ ```erb
104
+ <% content_for :head do %>
105
+ <%= append_stylesheet_pack_tag 'my-component' %>
106
+ <%= append_javascript_pack_tag 'my-component' %>
107
+ <% end %>
108
+
109
+ <h1>My Page</h1>
110
+ <p>Content goes here...</p>
111
+ ```
112
+
113
+ This approach works when:
114
+
115
+ - You're not using auto-appending libraries
116
+ - You can explicitly list all required packs in each view
117
+ - You don't need dynamic pack determination
118
+
119
+ ## Key Takeaways
120
+
121
+ - Always place `stylesheet_pack_tag` in `<head>` to prevent FOUC
122
+ - Use `content_for` to control execution order when using `append_*` helpers
123
+ - Ensure `append_*` helpers execute before the main pack tags
124
+ - JavaScript can use `defer: true` (default) or be placed at end of `<body>`
125
+ - The `content_for :body_content` pattern is essential when using auto-appending libraries
126
+
127
+ ## Related
128
+
129
+ - [View Helpers Documentation](../README.md#view-helpers)
130
+ - [Troubleshooting Guide](./troubleshooting.md)
131
+ - Original issue: [#720](https://github.com/shakacode/shakapacker/issues/720)
132
+ - Working implementation: [react-webpack-rails-tutorial PR #686](https://github.com/shakacode/react-webpack-rails-tutorial/pull/686)
@@ -1,5 +1,9 @@
1
1
  # Troubleshooting
2
2
 
3
+ ## Flash of Unstyled Content (FOUC)
4
+
5
+ If you're experiencing FOUC where content briefly appears unstyled before CSS loads, see the [Preventing FOUC guide](./preventing_fouc.md) for solutions including proper `stylesheet_pack_tag` placement and `content_for` patterns for dynamic pack loading.
6
+
3
7
  ## Debugging your webpack config
4
8
 
5
9
  1. Read the error message carefully. The error message will tell you the precise key value