shakapacker 8.0.2 → 9.2.0

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.
Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintignore +1 -0
  3. data/.eslintrc.fast.js +40 -0
  4. data/.eslintrc.js +48 -0
  5. data/.github/STATUS.md +1 -0
  6. data/.github/workflows/claude-code-review.yml +54 -0
  7. data/.github/workflows/claude.yml +50 -0
  8. data/.github/workflows/dummy.yml +9 -4
  9. data/.github/workflows/generator.yml +32 -10
  10. data/.github/workflows/node.yml +23 -1
  11. data/.github/workflows/ruby.yml +33 -2
  12. data/.github/workflows/test-bundlers.yml +170 -0
  13. data/.gitignore +20 -0
  14. data/.husky/pre-commit +2 -0
  15. data/.npmignore +56 -0
  16. data/.prettierignore +3 -0
  17. data/.rubocop.yml +1 -0
  18. data/.yalcignore +26 -0
  19. data/CHANGELOG.md +302 -16
  20. data/CLAUDE.md +29 -0
  21. data/CONTRIBUTING.md +138 -20
  22. data/Gemfile.lock +83 -89
  23. data/README.md +343 -105
  24. data/Rakefile +39 -4
  25. data/TODO.md +50 -0
  26. data/TODO_v9.md +87 -0
  27. data/bin/export-bundler-config +11 -0
  28. data/conductor-setup.sh +70 -0
  29. data/conductor.json +7 -0
  30. data/docs/cdn_setup.md +379 -0
  31. data/docs/common-upgrades.md +615 -0
  32. data/docs/css-modules-export-mode.md +512 -0
  33. data/docs/deployment.md +62 -9
  34. data/docs/optional-peer-dependencies.md +198 -0
  35. data/docs/peer-dependencies.md +60 -0
  36. data/docs/react.md +6 -14
  37. data/docs/releasing.md +197 -0
  38. data/docs/rspack.md +190 -0
  39. data/docs/rspack_migration_guide.md +305 -0
  40. data/docs/subresource_integrity.md +54 -0
  41. data/docs/transpiler-migration.md +209 -0
  42. data/docs/transpiler-performance.md +179 -0
  43. data/docs/troubleshooting.md +157 -22
  44. data/docs/typescript-migration.md +379 -0
  45. data/docs/typescript.md +99 -0
  46. data/docs/using_esbuild_loader.md +3 -3
  47. data/docs/using_swc_loader.md +112 -10
  48. data/docs/v6_upgrade.md +10 -0
  49. data/docs/v8_upgrade.md +3 -5
  50. data/docs/v9_upgrade.md +458 -0
  51. data/gemfiles/Gemfile-rails.6.0.x +2 -1
  52. data/gemfiles/Gemfile-rails.6.1.x +1 -1
  53. data/gemfiles/Gemfile-rails.7.0.x +2 -2
  54. data/gemfiles/Gemfile-rails.7.1.x +1 -2
  55. data/gemfiles/Gemfile-rails.7.2.x +11 -0
  56. data/gemfiles/Gemfile-rails.8.0.x +11 -0
  57. data/lib/install/bin/export-bundler-config +11 -0
  58. data/lib/install/bin/shakapacker +4 -6
  59. data/lib/install/bin/shakapacker-dev-server +1 -1
  60. data/lib/install/config/rspack/rspack.config.js +6 -0
  61. data/lib/install/config/rspack/rspack.config.ts +7 -0
  62. data/lib/install/config/shakapacker.yml +25 -5
  63. data/lib/install/config/webpack/webpack.config.ts +7 -0
  64. data/lib/install/package.json +38 -0
  65. data/lib/install/template.rb +194 -44
  66. data/lib/shakapacker/bundler_switcher.rb +329 -0
  67. data/lib/shakapacker/compiler.rb +2 -1
  68. data/lib/shakapacker/compiler_strategy.rb +2 -2
  69. data/lib/shakapacker/configuration.rb +173 -2
  70. data/lib/shakapacker/dev_server_runner.rb +29 -8
  71. data/lib/shakapacker/digest_strategy.rb +2 -1
  72. data/lib/shakapacker/doctor.rb +905 -0
  73. data/lib/shakapacker/helper.rb +64 -16
  74. data/lib/shakapacker/manifest.rb +10 -3
  75. data/lib/shakapacker/mtime_strategy.rb +1 -1
  76. data/lib/shakapacker/railtie.rb +4 -4
  77. data/lib/shakapacker/rspack_runner.rb +19 -0
  78. data/lib/shakapacker/runner.rb +159 -10
  79. data/lib/shakapacker/swc_migrator.rb +384 -0
  80. data/lib/shakapacker/utils/manager.rb +15 -2
  81. data/lib/shakapacker/version.rb +1 -1
  82. data/lib/shakapacker/version_checker.rb +2 -2
  83. data/lib/shakapacker/webpack_runner.rb +6 -43
  84. data/lib/shakapacker.rb +22 -11
  85. data/lib/tasks/shakapacker/doctor.rake +8 -0
  86. data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
  87. data/lib/tasks/shakapacker/install.rake +12 -2
  88. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  89. data/lib/tasks/shakapacker/switch_bundler.rake +82 -0
  90. data/lib/tasks/shakapacker.rake +2 -0
  91. data/package/.npmignore +4 -0
  92. data/package/babel/preset.ts +56 -0
  93. data/package/config.ts +175 -0
  94. data/package/configExporter/cli.ts +683 -0
  95. data/package/configExporter/configDocs.ts +102 -0
  96. data/package/configExporter/fileWriter.ts +92 -0
  97. data/package/configExporter/index.ts +5 -0
  98. data/package/configExporter/types.ts +36 -0
  99. data/package/configExporter/yamlSerializer.ts +266 -0
  100. data/package/{dev_server.js → dev_server.ts} +8 -5
  101. data/package/env.ts +92 -0
  102. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +30 -0
  103. data/package/environments/{base.js → base.ts} +56 -60
  104. data/package/environments/development.ts +90 -0
  105. data/package/environments/production.ts +80 -0
  106. data/package/environments/test.ts +53 -0
  107. data/package/environments/types.ts +98 -0
  108. data/package/esbuild/index.ts +42 -0
  109. data/package/index.d.ts +3 -60
  110. data/package/index.ts +55 -0
  111. data/package/loaders.d.ts +28 -0
  112. data/package/optimization/rspack.ts +36 -0
  113. data/package/optimization/webpack.ts +57 -0
  114. data/package/plugins/rspack.ts +103 -0
  115. data/package/plugins/webpack.ts +62 -0
  116. data/package/rspack/index.ts +64 -0
  117. data/package/rules/{babel.js → babel.ts} +2 -2
  118. data/package/rules/{coffee.js → coffee.ts} +1 -1
  119. data/package/rules/css.ts +3 -0
  120. data/package/rules/{erb.js → erb.ts} +1 -1
  121. data/package/rules/esbuild.ts +10 -0
  122. data/package/rules/file.ts +40 -0
  123. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  124. data/package/rules/{less.js → less.ts} +4 -4
  125. data/package/rules/raw.ts +25 -0
  126. data/package/rules/rspack.ts +176 -0
  127. data/package/rules/{sass.js → sass.ts} +7 -3
  128. data/package/rules/{stylus.js → stylus.ts} +4 -8
  129. data/package/rules/swc.ts +10 -0
  130. data/package/rules/webpack.ts +16 -0
  131. data/package/swc/index.ts +56 -0
  132. data/package/types/README.md +88 -0
  133. data/package/types/index.ts +61 -0
  134. data/package/types.ts +108 -0
  135. data/package/utils/configPath.ts +6 -0
  136. data/package/utils/debug.ts +49 -0
  137. data/package/utils/defaultConfigPath.ts +4 -0
  138. data/package/utils/errorCodes.ts +219 -0
  139. data/package/utils/errorHelpers.ts +143 -0
  140. data/package/utils/getStyleRule.ts +64 -0
  141. data/package/utils/helpers.ts +85 -0
  142. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  143. data/package/utils/pathValidation.ts +139 -0
  144. data/package/utils/requireOrError.ts +15 -0
  145. data/package/utils/snakeToCamelCase.ts +5 -0
  146. data/package/utils/typeGuards.ts +342 -0
  147. data/package/utils/validateDependencies.ts +61 -0
  148. data/package/webpack-types.d.ts +33 -0
  149. data/package/webpackDevServerConfig.ts +117 -0
  150. data/package-lock.json +13047 -0
  151. data/package.json +154 -18
  152. data/scripts/remove-use-strict.js +45 -0
  153. data/scripts/type-check-no-emit.js +27 -0
  154. data/test/helpers.js +1 -1
  155. data/test/package/config.test.js +43 -0
  156. data/test/package/env.test.js +42 -7
  157. data/test/package/environments/base.test.js +5 -1
  158. data/test/package/rules/babel.test.js +16 -0
  159. data/test/package/rules/esbuild.test.js +1 -1
  160. data/test/package/rules/raw.test.js +40 -7
  161. data/test/package/rules/swc.test.js +1 -1
  162. data/test/package/rules/webpack.test.js +35 -0
  163. data/test/package/staging.test.js +4 -3
  164. data/test/package/transpiler-defaults.test.js +127 -0
  165. data/test/peer-dependencies.sh +85 -0
  166. data/test/scripts/remove-use-strict.test.js +125 -0
  167. data/test/typescript/build.test.js +118 -0
  168. data/test/typescript/environments.test.js +107 -0
  169. data/test/typescript/pathValidation.test.js +142 -0
  170. data/test/typescript/securityValidation.test.js +182 -0
  171. data/tools/README.md +124 -0
  172. data/tools/css-modules-v9-codemod.js +179 -0
  173. data/tsconfig.eslint.json +16 -0
  174. data/tsconfig.json +38 -0
  175. data/yarn.lock +4165 -2706
  176. metadata +129 -41
  177. data/package/babel/preset.js +0 -37
  178. data/package/config.js +0 -54
  179. data/package/env.js +0 -48
  180. data/package/environments/development.js +0 -13
  181. data/package/environments/production.js +0 -88
  182. data/package/environments/test.js +0 -3
  183. data/package/esbuild/index.js +0 -40
  184. data/package/index.js +0 -40
  185. data/package/rules/css.js +0 -3
  186. data/package/rules/esbuild.js +0 -10
  187. data/package/rules/file.js +0 -29
  188. data/package/rules/index.js +0 -20
  189. data/package/rules/raw.js +0 -5
  190. data/package/rules/swc.js +0 -10
  191. data/package/swc/index.js +0 -50
  192. data/package/utils/configPath.js +0 -4
  193. data/package/utils/defaultConfigPath.js +0 -2
  194. data/package/utils/getStyleRule.js +0 -40
  195. data/package/utils/helpers.js +0 -58
  196. data/package/utils/snakeToCamelCase.js +0 -5
  197. data/package/webpackDevServerConfig.js +0 -71
  198. data/test/package/rules/index.test.js +0 -16
data/Rakefile CHANGED
@@ -14,16 +14,51 @@ namespace :run_spec do
14
14
  sh("bundle exec rspec spec/shakapacker/*_spec.rb")
15
15
  end
16
16
 
17
- desc "Run specs in the dummy app"
17
+ desc "Run specs in the dummy app with webpack"
18
18
  task :dummy do
19
- puts "Running dummy app specs"
19
+ puts "Running dummy app specs with webpack"
20
20
  spec_dummy_dir = Pathname.new(File.join("spec", "dummy")).realpath
21
21
  Bundler.with_unbundled_env do
22
22
  sh_in_dir(".", "yalc publish")
23
23
  sh_in_dir(spec_dummy_dir, [
24
24
  "bundle install",
25
25
  "yalc link shakapacker",
26
- "yarn install",
26
+ "npm install",
27
+ "bin/test-bundler webpack",
28
+ "NODE_ENV=test RAILS_ENV=test bin/shakapacker",
29
+ "bundle exec rspec"
30
+ ])
31
+ end
32
+ end
33
+
34
+ desc "Run specs in the dummy app with rspack"
35
+ task :dummy_with_rspack do
36
+ puts "Running dummy app specs with rspack"
37
+ spec_dummy_dir = Pathname.new(File.join("spec", "dummy")).realpath
38
+ Bundler.with_unbundled_env do
39
+ sh_in_dir(".", "yalc publish")
40
+ sh_in_dir(spec_dummy_dir, [
41
+ "bundle install",
42
+ "yalc link shakapacker",
43
+ "npm install",
44
+ "bin/test-bundler rspack",
45
+ "NODE_ENV=test RAILS_ENV=test bin/shakapacker",
46
+ "bundle exec rspec"
47
+ ])
48
+ end
49
+ end
50
+
51
+ desc "Run specs in the dummy-rspack app"
52
+ task :dummy_rspack do
53
+ puts "Running dummy-rspack app specs"
54
+ spec_dummy_dir = Pathname.new(File.join("spec", "dummy-rspack")).realpath
55
+ Bundler.with_unbundled_env do
56
+ sh_in_dir(".", "yalc publish")
57
+ sh_in_dir(spec_dummy_dir, [
58
+ "bundle install",
59
+ "yalc link shakapacker",
60
+ "npm install",
61
+ "NODE_ENV=test RAILS_ENV=test npm exec --no -- rspack build --config config/rspack/rspack.config.js",
27
62
  "bundle exec rspec"
28
63
  ])
29
64
  end
@@ -35,7 +70,7 @@ namespace :run_spec do
35
70
  end
36
71
 
37
72
  desc "Run all specs"
38
- task all_specs: %i[gem dummy generator] do
73
+ task all_specs: %i[gem dummy dummy_with_rspack dummy_rspack generator] do
39
74
  puts "Completed all RSpec tests"
40
75
  end
41
76
  end
data/TODO.md ADDED
@@ -0,0 +1,50 @@
1
+ # TypeScript Migration Status
2
+
3
+ ## ✅ Completed (PR #602)
4
+ - Enhanced `package/index.d.ts` with comprehensive type definitions
5
+ - Added TypeScript type packages for better IDE support
6
+ - Improved Config and DevServerConfig interfaces
7
+ - Added missing properties (private_output_path, inline_css, env_prefix, etc.)
8
+ - All tests passing
9
+ - Zero JavaScript modifications (no whitespace changes)
10
+ - Full backward compatibility maintained
11
+
12
+ ## 📋 Next Steps (Issue #605)
13
+
14
+ ### Phase 2: Core Module Conversion
15
+ - [ ] Convert `package/config.js` to TypeScript
16
+ - [ ] Convert `package/env.js` to TypeScript
17
+ - [ ] Convert `package/index.js` to TypeScript
18
+ - [ ] Convert `package/utils/helpers.js` to TypeScript
19
+
20
+ ### Phase 3: Environment & Build System
21
+ - [ ] Convert environment files (base, development, production, test)
22
+ - [ ] Convert dev_server.js
23
+ - [ ] Convert webpackDevServerConfig.js
24
+
25
+ ### Phase 4: Rules & Loaders (PR #620) ✅
26
+ - [x] Convert all files in `package/rules/`
27
+ - [x] Convert all files in `package/plugins/`
28
+ - [x] Convert all files in `package/optimization/`
29
+
30
+ ### Phase 5: Framework-Specific Modules ✅
31
+ - [x] Convert rspack support files
32
+ - [x] Convert swc support files
33
+ - [x] Convert esbuild support files
34
+ - [x] Convert babel preset
35
+
36
+ ### Phase 6: Final Cleanup ✅
37
+ - [x] Add TypeScript linting with @typescript-eslint
38
+ - [x] Verify strict mode is enabled (already configured)
39
+ - [x] Update documentation
40
+
41
+ ## Why Gradual Migration?
42
+ - **Lower risk**: Each phase can be tested independently
43
+ - **Team learning**: Get familiar with TypeScript incrementally
44
+ - **Immediate value**: Type definitions already provide IDE benefits
45
+ - **No breaking changes**: Users unaffected during migration
46
+
47
+ ## Related Links
48
+ - Original issue: #200
49
+ - Initial PR: #602
50
+ - Next steps issue: #605
data/TODO_v9.md ADDED
@@ -0,0 +1,87 @@
1
+ # Shakapacker v9 TODO List
2
+
3
+ ## CSS Modules Configuration Alignment
4
+
5
+ ### Problem
6
+ Current CSS modules configuration causes TypeScript/webpack warnings because of default vs named export mismatch.
7
+
8
+ ### Current Behavior (v8)
9
+ - CSS modules use default export: `import styles from './styles.module.css'`
10
+ - This causes warnings but works at runtime
11
+ - Warning example: `export 'default' (imported as 'style') was not found in './HelloWorld.module.css'`
12
+
13
+ ### Proposed v9 Change
14
+ Align with Next.js and modern tooling by using named exports:
15
+
16
+ 1. **Update css-loader configuration:**
17
+ ```javascript
18
+ {
19
+ loader: 'css-loader',
20
+ options: {
21
+ modules: {
22
+ namedExport: true,
23
+ exportLocalsConvention: 'camelCaseOnly' // Must be 'camelCaseOnly' or 'dashesOnly' with namedExport: true
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ **Note:** Using `exportLocalsConvention: 'camelCase'` with `namedExport: true` will cause a build error.
30
+ css-loader only allows `'camelCaseOnly'` or `'dashesOnly'` when named exports are enabled.
31
+
32
+ 2. **Update TypeScript types:**
33
+ - Ensure proper typing for CSS modules with named exports
34
+ - May need to update or generate `.d.ts` files for CSS modules
35
+
36
+ 3. **Migration guide for users:**
37
+ - Document the breaking change
38
+ - Provide codemod or migration script to update imports from:
39
+ ```javascript
40
+ import styles from './styles.module.css'
41
+ styles.className
42
+ ```
43
+ to:
44
+ ```javascript
45
+ import * as styles from './styles.module.css'
46
+ // or
47
+ import { className } from './styles.module.css'
48
+ ```
49
+
50
+ ### Benefits
51
+ - Eliminates webpack/TypeScript warnings
52
+ - Better tree-shaking potential
53
+ - More explicit about what CSS classes are being used
54
+ - Easier interoperability with frameworks that support named exports
55
+
56
+ ### Implementation Notes
57
+ - This is a BREAKING CHANGE and appropriate for major version bump
58
+ - Need to test with both webpack and rspack
59
+ - Consider providing a compatibility mode via configuration option
60
+
61
+ ---
62
+
63
+ ## Related Issues from PR #597
64
+
65
+ ### React Component Not Rendering (spec/dummy) - RESOLVED ✅
66
+ - **Issue**: React component was not rendering due to CSS module import mismatch
67
+ - **Symptoms**:
68
+ - Component wasn't rendering "Hello, Stranger!"
69
+ - Input field not rendered, making interactive test fail
70
+ - Only the static H1 "Hello, World!" was visible
71
+ - **Resolution**:
72
+ - Fixed CSS module import syntax from `import style from` to `import * as style from`
73
+ - This matched webpack's named exports configuration for CSS modules
74
+ - Tests now pass with both React 18.3.1 and webpack/rspack configurations
75
+ - **Root Cause**: CSS module import/export mismatch
76
+ - Webpack was configured to use named exports for CSS modules
77
+ - TypeScript code was using default import syntax
78
+ - This caused `style` to be undefined, breaking SSR and client rendering
79
+ - **Status**: FIXED
80
+ - All tests re-enabled and passing
81
+ - Both SSR and client-side rendering working
82
+ - Interactive functionality restored
83
+
84
+ ### Test Infrastructure
85
+ - Successfully implemented dual bundler support (webpack/rspack)
86
+ - test-bundler script working well with status command
87
+ - Consider adding more comprehensive tests for both bundlers
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Minimal shim - all logic is in the TypeScript module
4
+ const { run } = require('shakapacker/configExporter')
5
+
6
+ run(process.argv.slice(2))
7
+ .then((exitCode) => process.exit(exitCode))
8
+ .catch((error) => {
9
+ console.error(error.message)
10
+ process.exit(1)
11
+ })
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ echo "🔧 Setting up Shakapacker workspace..."
5
+
6
+ # Set up Ruby version if asdf is available
7
+ if command -v asdf &> /dev/null; then
8
+ echo "📝 Using asdf Ruby version management..."
9
+ # Ensure we have the right Ruby version file
10
+ echo "ruby 3.3.4" > .tool-versions
11
+ # Use asdf exec to run commands with the right Ruby version
12
+ BUNDLE_CMD="asdf exec bundle"
13
+ else
14
+ BUNDLE_CMD="bundle"
15
+ fi
16
+
17
+ # Check for required tools
18
+ if ! $BUNDLE_CMD --version &> /dev/null; then
19
+ echo "❌ Error: Ruby bundler is not installed"
20
+ echo "Please install bundler first: gem install bundler"
21
+ exit 1
22
+ fi
23
+
24
+ if ! command -v yarn &> /dev/null; then
25
+ echo "❌ Error: Yarn is not installed"
26
+ echo "Please install yarn first"
27
+ exit 1
28
+ fi
29
+
30
+ # Install Ruby dependencies
31
+ echo "📦 Installing Ruby dependencies..."
32
+ $BUNDLE_CMD install
33
+
34
+ # Install JavaScript dependencies
35
+ echo "📦 Installing JavaScript dependencies..."
36
+ yarn install
37
+
38
+ # Set up Husky git hooks
39
+ echo "🪝 Setting up Husky git hooks..."
40
+ npx husky
41
+ if [ ! -f .husky/pre-commit ]; then
42
+ echo "Creating pre-commit hook..."
43
+ cat > .husky/pre-commit << 'EOF'
44
+ #!/usr/bin/env sh
45
+ npx lint-staged
46
+ EOF
47
+ chmod +x .husky/pre-commit
48
+ fi
49
+
50
+ # Copy environment files if they exist in root
51
+ if [ -n "${CONDUCTOR_ROOT_PATH:-}" ]; then
52
+ if [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
53
+ echo "📋 Copying .env file from root..."
54
+ cp "$CONDUCTOR_ROOT_PATH/.env" .env
55
+ fi
56
+
57
+ if [ -f "$CONDUCTOR_ROOT_PATH/.env.local" ]; then
58
+ echo "📋 Copying .env.local file from root..."
59
+ cp "$CONDUCTOR_ROOT_PATH/.env.local" .env.local
60
+ fi
61
+ fi
62
+
63
+ echo "✅ Workspace setup complete!"
64
+ echo ""
65
+ echo "Available commands:"
66
+ echo " - Run tests: bundle exec rspec"
67
+ echo " - Run specific test suites: bundle exec rake run_spec:gem"
68
+ echo " - Run JavaScript tests: yarn test"
69
+ echo " - Lint JavaScript: yarn lint"
70
+ echo " - Lint Ruby: bundle exec rubocop"
data/conductor.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "scripts": {
3
+ "setup": "./conductor-setup.sh",
4
+ "run": "bundle exec rspec",
5
+ "archive": ""
6
+ }
7
+ }
data/docs/cdn_setup.md ADDED
@@ -0,0 +1,379 @@
1
+ # CDN Setup Guide for Shakapacker
2
+
3
+ This guide explains how to configure Shakapacker to serve your JavaScript bundles and other assets from a Content Delivery Network (CDN) like CloudFlare, CloudFront, or Fastly.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Configuration Methods](#configuration-methods)
9
+ - [Step-by-Step Setup](#step-by-step-setup)
10
+ - [CloudFlare Specific Setup](#cloudflare-specific-setup)
11
+ - [Verification](#verification)
12
+ - [Troubleshooting](#troubleshooting)
13
+ - [Advanced Configuration](#advanced-configuration)
14
+
15
+ ## Overview
16
+
17
+ When using a CDN with Shakapacker, your compiled JavaScript bundles and other assets will be served from the CDN's edge servers instead of your application server. This provides:
18
+
19
+ - **Reduced latency** for users around the world
20
+ - **Decreased load** on your application servers
21
+ - **Better caching** and faster asset delivery
22
+ - **Improved scalability** for high-traffic applications
23
+
24
+ ## Configuration Methods
25
+
26
+ Shakapacker supports CDN configuration through three methods (in order of precedence):
27
+
28
+ 1. **Environment Variable** (highest priority): `SHAKAPACKER_ASSET_HOST`
29
+ 2. **Shakapacker Configuration File**: `asset_host` setting in `config/shakapacker.yml`
30
+ 3. **Rails Configuration**: `Rails.application.config.asset_host`
31
+
32
+ ## Step-by-Step Setup
33
+
34
+ ### 1. Configure Your CDN
35
+
36
+ First, set up your CDN to pull assets from your application's `/packs` directory. The exact steps depend on your CDN provider, but generally you'll need to:
37
+
38
+ 1. Create a CDN distribution/zone
39
+ 2. Set your application's domain as the origin server
40
+ 3. Configure the CDN to cache files from `/packs/*` path
41
+ 4. Note your CDN URL (e.g., `https://cdn.example.com` or `https://d1234567890.cloudfront.net`)
42
+
43
+ ### 2. Configure Shakapacker Asset Host
44
+
45
+ Choose one of the following methods:
46
+
47
+ #### Option A: Using Environment Variable (Recommended for Production)
48
+
49
+ Set the `SHAKAPACKER_ASSET_HOST` environment variable:
50
+
51
+ ```bash
52
+ # For production deployment
53
+ export SHAKAPACKER_ASSET_HOST=https://cdn.example.com
54
+
55
+ # Or in your .env file
56
+ SHAKAPACKER_ASSET_HOST=https://cdn.example.com
57
+ ```
58
+
59
+ #### Option B: Using shakapacker.yml
60
+
61
+ Add the `asset_host` setting to your `config/shakapacker.yml`:
62
+
63
+ ```yaml
64
+ production:
65
+ # ... other settings ...
66
+ asset_host: https://cdn.example.com
67
+
68
+ # You can also set different CDN hosts per environment
69
+ staging:
70
+ asset_host: https://staging-cdn.example.com
71
+ ```
72
+
73
+ #### Option C: Using Rails Configuration
74
+
75
+ Configure in your Rails environment file (e.g., `config/environments/production.rb`):
76
+
77
+ ```ruby
78
+ Rails.application.configure do
79
+ # ... other settings ...
80
+
81
+ # This will be used by Shakapacker if SHAKAPACKER_ASSET_HOST
82
+ # and asset_host in shakapacker.yml are not set
83
+ config.action_controller.asset_host = 'https://cdn.example.com'
84
+ end
85
+ ```
86
+
87
+ ### 3. Compile Assets
88
+
89
+ During deployment, compile your assets as usual:
90
+
91
+ ```bash
92
+ # The SHAKAPACKER_ASSET_HOST will be used during compilation
93
+ # to set the webpack publicPath
94
+ RAILS_ENV=production bundle exec rails assets:precompile
95
+ ```
96
+
97
+ This ensures that:
98
+ - Webpack's `publicPath` is set to your CDN URL
99
+ - Dynamic imports and code-split chunks load from the CDN
100
+ - Asset manifest references use CDN URLs
101
+
102
+ ### 4. Deploy and Sync Assets
103
+
104
+ After compilation, ensure your compiled assets in `public/packs` are accessible to your CDN:
105
+
106
+ - **Push CDN**: Upload the files to your CDN's storage
107
+ - **Pull CDN**: Deploy your application normally; the CDN will pull assets on first request
108
+
109
+ ## CloudFlare Specific Setup
110
+
111
+ For CloudFlare CDN setup:
112
+
113
+ ### 1. Create a CloudFlare Account and Add Your Domain
114
+
115
+ 1. Sign up for CloudFlare (if you haven't already)
116
+ 2. Add your domain to CloudFlare
117
+ 3. Update your domain's nameservers to CloudFlare's
118
+
119
+ ### 2. Configure Page Rules for Assets
120
+
121
+ Create a page rule for your assets:
122
+
123
+ 1. Go to **Page Rules** in CloudFlare dashboard
124
+ 2. Create a new rule for `*yourdomain.com/packs/*`
125
+ 3. Set the following settings:
126
+ - **Cache Level**: Cache Everything
127
+ - **Edge Cache TTL**: 1 month (or your preference)
128
+ - **Browser Cache TTL**: 1 month
129
+
130
+ ### 3. Set Up CloudFlare for Assets Only (Optional)
131
+
132
+ If you want CloudFlare to only serve your assets (not your entire site):
133
+
134
+ 1. Create a CNAME record: `cdn.yourdomain.com` → `yourdomain.com`
135
+ 2. Set CloudFlare proxy (orange cloud) ON for this record
136
+ 3. Configure Shakapacker:
137
+
138
+ ```bash
139
+ export SHAKAPACKER_ASSET_HOST=https://cdn.yourdomain.com
140
+ ```
141
+
142
+ ### 4. Configure CloudFlare Settings
143
+
144
+ Recommended CloudFlare settings for assets:
145
+
146
+ - **SSL/TLS**: Full or Full (Strict)
147
+ - **Caching Level**: Standard or Aggressive
148
+ - **Browser Cache TTL**: Respect Existing Headers
149
+ - **Always Online**: On
150
+ - **Auto Minify**: OFF (Shakapacker already minifies)
151
+
152
+ ## Verification
153
+
154
+ To verify your CDN setup is working:
155
+
156
+ ### 1. Check Compiled Assets
157
+
158
+ After compilation, inspect a compiled JavaScript file:
159
+
160
+ ```bash
161
+ # Look for the publicPath setting in your compiled bundles
162
+ grep -r "publicPath" public/packs/js/
163
+ ```
164
+
165
+ You should see your CDN URL in the publicPath configuration.
166
+
167
+ ### 2. Check Page Source
168
+
169
+ In production, view your page source and verify script tags use CDN URLs:
170
+
171
+ ```html
172
+ <!-- Correct: Assets loading from CDN -->
173
+ <script src="https://cdn.example.com/packs/js/application-abc123.js"></script>
174
+
175
+ <!-- Wrong: Assets loading from relative path -->
176
+ <script src="/packs/js/application-abc123.js"></script>
177
+ ```
178
+
179
+ ### 3. Check Network Tab
180
+
181
+ 1. Open browser DevTools
182
+ 2. Go to Network tab
183
+ 3. Reload the page
184
+ 4. Verify JavaScript files are loaded from CDN domain
185
+
186
+ ### 4. Check Dynamic Imports
187
+
188
+ If using code splitting, verify dynamic chunks load from CDN:
189
+
190
+ ```javascript
191
+ // This dynamic import should load from CDN
192
+ import('./components/HeavyComponent').then(module => {
193
+ // Check Network tab - chunk should load from CDN
194
+ });
195
+ ```
196
+
197
+ ## Troubleshooting
198
+
199
+ ### Assets Not Loading from CDN
200
+
201
+ **Problem**: Assets are still loading from your application domain.
202
+
203
+ **Solutions**:
204
+ 1. Ensure you set `SHAKAPACKER_ASSET_HOST` **before** running `assets:precompile`
205
+ 2. Clear Rails cache: `rails tmp:cache:clear`
206
+ 3. Check the manifest.json file includes CDN URLs:
207
+ ```bash
208
+ cat public/packs/manifest.json
209
+ ```
210
+
211
+ ### CORS Errors
212
+
213
+ **Problem**: Browser shows CORS errors when loading assets from CDN.
214
+
215
+ **Solutions**:
216
+ 1. Configure your CDN to add CORS headers:
217
+ ```
218
+ Access-Control-Allow-Origin: *
219
+ ```
220
+ 2. Or configure for specific domain:
221
+ ```
222
+ Access-Control-Allow-Origin: https://yourdomain.com
223
+ ```
224
+
225
+ ### Fonts Not Loading
226
+
227
+ **Problem**: Web fonts fail to load from CDN due to CORS.
228
+
229
+ **Solutions**:
230
+ 1. Ensure CDN sends proper CORS headers for font files
231
+ 2. In CloudFlare, create a page rule for `*.woff2`, `*.woff`, `*.ttf` files with CORS headers
232
+ 3. Consider hosting fonts separately or using base64 encoding
233
+
234
+ ### Development Environment Issues
235
+
236
+ **Problem**: CDN URLs appearing in development environment.
237
+
238
+ **Solution**: Only set `SHAKAPACKER_ASSET_HOST` in production:
239
+
240
+ ```ruby
241
+ # config/environments/development.rb
242
+ # Ensure asset_host is NOT set in development
243
+
244
+ # config/environments/production.rb
245
+ # Set asset_host only in production
246
+ ```
247
+
248
+ ## Advanced Configuration
249
+
250
+ ### Using Different CDNs for Different Assets
251
+
252
+ You can use Rails asset host proc for dynamic CDN selection:
253
+
254
+ ```ruby
255
+ # config/environments/production.rb
256
+ config.action_controller.asset_host = Proc.new do |source|
257
+ if source =~ /\.(js|css)$/
258
+ 'https://js-css-cdn.example.com'
259
+ else
260
+ 'https://images-cdn.example.com'
261
+ end
262
+ end
263
+ ```
264
+
265
+ ### CDN with Integrity Hashes
266
+
267
+ When using Subresource Integrity (SRI) with CDN:
268
+
269
+ ```yaml
270
+ # config/shakapacker.yml
271
+ production:
272
+ asset_host: https://cdn.example.com
273
+ integrity:
274
+ enabled: true
275
+ hash_functions: ["sha384"]
276
+ cross_origin: "anonymous"
277
+ ```
278
+
279
+ Ensure your CDN serves files with CORS headers:
280
+ ```
281
+ Access-Control-Allow-Origin: *
282
+ ```
283
+
284
+ ### Multiple CDN Domains for Parallel Downloads
285
+
286
+ For HTTP/1.1 optimization (less relevant with HTTP/2):
287
+
288
+ ```ruby
289
+ # config/environments/production.rb
290
+ config.action_controller.asset_host = Proc.new do |source|
291
+ "https://cdn#{Digest::MD5.hexdigest(source)[0..2].to_i(16) % 4}.example.com"
292
+ end
293
+ # This creates cdn0.example.com through cdn3.example.com
294
+ ```
295
+
296
+ ### Cache Busting
297
+
298
+ Shakapacker automatically includes content hashes in production:
299
+
300
+ ```yaml
301
+ # config/shakapacker.yml
302
+ production:
303
+ # This is already true by default in production
304
+ useContentHash: true
305
+ ```
306
+
307
+ This ensures CDN caches are invalidated when content changes.
308
+
309
+ ### Preloading Critical Assets
310
+
311
+ Use Rails helpers to preload critical assets from CDN:
312
+
313
+ ```erb
314
+ <%= preload_pack_asset 'application.js' %>
315
+ <%= preload_pack_asset 'application.css' %>
316
+ ```
317
+
318
+ ## Security Considerations
319
+
320
+ 1. **Use HTTPS**: Always use HTTPS for your CDN URL to prevent mixed content warnings
321
+ 2. **Configure CSP**: Update Content Security Policy headers to allow CDN domain:
322
+ ```ruby
323
+ # config/initializers/content_security_policy.rb
324
+ Rails.application.config.content_security_policy do |policy|
325
+ policy.script_src :self, 'https://cdn.example.com'
326
+ policy.style_src :self, 'https://cdn.example.com'
327
+ end
328
+ ```
329
+ 3. **Use SRI**: Enable Subresource Integrity for additional security
330
+ 4. **Monitor CDN**: Set up monitoring for CDN availability and performance
331
+
332
+ ## Example Configuration
333
+
334
+ Here's a complete example for a production setup with CloudFlare:
335
+
336
+ ```yaml
337
+ # config/shakapacker.yml
338
+ production:
339
+ compile: false
340
+ cache_manifest: true
341
+ asset_host: <%= ENV.fetch('SHAKAPACKER_ASSET_HOST', 'https://cdn.example.com') %>
342
+
343
+ # Enable integrity checking
344
+ integrity:
345
+ enabled: true
346
+ hash_functions: ["sha384"]
347
+ cross_origin: "anonymous"
348
+ ```
349
+
350
+ ```ruby
351
+ # config/environments/production.rb
352
+ Rails.application.configure do
353
+ # Fallback if SHAKAPACKER_ASSET_HOST is not set
354
+ config.action_controller.asset_host = 'https://cdn.example.com'
355
+
356
+ # Ensure proper headers for CDN
357
+ config.public_file_server.headers = {
358
+ 'Cache-Control' => 'public, max-age=31536000',
359
+ 'X-Content-Type-Options' => 'nosniff'
360
+ }
361
+ end
362
+ ```
363
+
364
+ ```bash
365
+ # Deployment script
366
+ export SHAKAPACKER_ASSET_HOST=https://cdn.example.com
367
+ RAILS_ENV=production bundle exec rails assets:precompile
368
+ ```
369
+
370
+ ## Summary
371
+
372
+ Setting up a CDN with Shakapacker involves:
373
+
374
+ 1. Configuring your CDN service
375
+ 2. Setting the `SHAKAPACKER_ASSET_HOST` environment variable
376
+ 3. Compiling assets with the CDN URL
377
+ 4. Deploying and verifying the setup
378
+
379
+ The key is ensuring `SHAKAPACKER_ASSET_HOST` is set during asset compilation so webpack's `publicPath` is configured correctly for dynamic imports and code-split chunks.