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
@@ -0,0 +1,512 @@
1
+ # CSS Modules Export Mode
2
+
3
+ ## Version 9.x (Current Default Behavior)
4
+
5
+ Starting with Shakapacker v9, CSS Modules are configured with **named exports** (`namedExport: true`) by default to align with Next.js and modern tooling standards.
6
+
7
+ ### JavaScript Usage
8
+
9
+ In pure JavaScript projects, you can use true named imports:
10
+
11
+ ```js
12
+ // v9 - named exports in JavaScript
13
+ import { bright, container } from './Foo.module.css';
14
+ <button className={bright} />
15
+ ```
16
+
17
+ ### TypeScript Usage
18
+
19
+ TypeScript cannot statically analyze CSS files to determine the exact export names at compile time. When css-loader generates individual named exports dynamically from your CSS classes, TypeScript doesn't know what those exports will be. Therefore, you must use namespace imports:
20
+
21
+ ```typescript
22
+ // v9 - namespace import required for TypeScript
23
+ import * as styles from './Foo.module.css';
24
+ <button className={styles.bright} />
25
+ ```
26
+
27
+ **Why namespace imports?** While webpack's css-loader generates true named exports at runtime (with `namedExport: true`), TypeScript's type system cannot determine these dynamic exports during compilation. The namespace import pattern allows TypeScript to treat the import as an object with string keys, bypassing the need for static export validation while still benefiting from the runtime optimizations of named exports.
28
+
29
+ ### Benefits of v9 Configuration
30
+ - Eliminates certain webpack warnings
31
+ - Provides better tree-shaking potential
32
+ - Aligns with modern JavaScript module standards
33
+ - Automatically converts kebab-case to camelCase (`my-button` → `myButton`)
34
+
35
+ ### Important: exportLocalsConvention with namedExport
36
+
37
+ When `namedExport: true` is enabled (v9 default), css-loader requires `exportLocalsConvention` to be either `'camelCaseOnly'` or `'dashesOnly'`.
38
+
39
+ **The following will cause a build error:**
40
+ ```js
41
+ modules: {
42
+ namedExport: true,
43
+ exportLocalsConvention: 'camelCase' // ❌ ERROR: incompatible with namedExport: true
44
+ }
45
+ ```
46
+
47
+ **Error message:**
48
+ ```
49
+ "exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option
50
+ ```
51
+
52
+ **Correct v9 configuration:**
53
+ ```js
54
+ modules: {
55
+ namedExport: true,
56
+ exportLocalsConvention: 'camelCaseOnly' // ✅ Correct - only camelCase exported
57
+ }
58
+ ```
59
+
60
+ **exportLocalsConvention options with namedExport:**
61
+
62
+ When `namedExport: true`, you can use:
63
+ - `'camelCaseOnly'` (v9 default): Exports ONLY the camelCase version (e.g., only `myButton`)
64
+ - `'dashesOnly'`: Exports ONLY the original kebab-case version (e.g., only `my-button`)
65
+
66
+ **Not compatible with namedExport: true:**
67
+ - `'camelCase'`: Exports both versions (both `my-button` and `myButton`) - only works with `namedExport: false` (v8 behavior)
68
+
69
+ **Configuration Quick Reference:**
70
+
71
+ | namedExport | exportLocalsConvention | `.my-button` exports | Use Case | Compatible? |
72
+ |-------------|------------------------|---------------------|----------|-------------|
73
+ | `true` | `'camelCaseOnly'` | `myButton` | JavaScript conventions | ✅ Valid |
74
+ | `true` | `'dashesOnly'` | `'my-button'` | Preserve CSS naming | ✅ Valid |
75
+ | `false` | `'camelCase'` | Both `myButton` AND `'my-button'` | v8 compatibility | ✅ Valid |
76
+ | `false` | `'asIs'` | `'my-button'` | No transformation | ✅ Valid |
77
+ | `true` | `'camelCase'` | - | - | ❌ Build Error |
78
+
79
+ **When to use each option:**
80
+ - Use `'camelCaseOnly'` if you prefer standard JavaScript naming conventions
81
+ - Use `'dashesOnly'` if you want to preserve your CSS class names exactly as written
82
+ - Use `'camelCase'` (with `namedExport: false`) only if you need both versions available
83
+
84
+ ## Version 8.x and Earlier Behavior
85
+
86
+ In Shakapacker v8 and earlier, the default behavior was to use a **default export object**:
87
+
88
+ ```js
89
+ // v8 and earlier default
90
+ import styles from './Foo.module.css';
91
+ <button className={styles.bright} />
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Migrating from v8 to v9
97
+
98
+ When upgrading to Shakapacker v9, you'll need to update your CSS Module imports from default exports to named exports.
99
+
100
+ ### Migration Options
101
+
102
+ #### Option 1: Update Your Code (Recommended)
103
+
104
+ **For JavaScript projects:**
105
+ ```js
106
+ // Before (v8)
107
+ import styles from './Component.module.css';
108
+ <div className={styles.container}>
109
+ <button className={styles.button}>Click me</button>
110
+ </div>
111
+
112
+ // After (v9) - JavaScript
113
+ import { container, button } from './Component.module.css';
114
+ <div className={container}>
115
+ <button className={button}>Click me</button>
116
+ </div>
117
+ ```
118
+
119
+ **For TypeScript projects:**
120
+ ```typescript
121
+ // Before (v8)
122
+ import styles from './Component.module.css';
123
+ <div className={styles.container}>
124
+ <button className={styles.button}>Click me</button>
125
+ </div>
126
+
127
+ // After (v9) - TypeScript
128
+ import * as styles from './Component.module.css';
129
+ <div className={styles.container}>
130
+ <button className={styles.button}>Click me</button>
131
+ </div>
132
+ ```
133
+
134
+ Note: TypeScript projects only need to change from default import to namespace import (`* as styles`), the property access remains the same.
135
+
136
+ #### Option 2: Keep v8 Behavior
137
+
138
+ If you prefer to keep the v8 default export behavior during migration, you can override the configuration (see below).
139
+
140
+ ---
141
+
142
+ ## Reverting to Default Exports (v8 Behavior)
143
+
144
+ To use the v8-style default exports instead of v9's named exports:
145
+
146
+ ### Option 1: Update `config/webpack/commonWebpackConfig.js` (Recommended)
147
+
148
+ This approach modifies the common webpack configuration that applies to all environments:
149
+
150
+ ```js
151
+ // config/webpack/commonWebpackConfig.js
152
+ const { generateWebpackConfig, merge } = require('shakapacker');
153
+
154
+ const baseClientWebpackConfig = generateWebpackConfig();
155
+
156
+ // Override CSS Modules configuration to use v8-style default exports
157
+ const overrideCssModulesConfig = (config) => {
158
+ // Find the CSS rule in the module rules
159
+ const cssRule = config.module.rules.find(rule =>
160
+ rule.test && rule.test.toString().includes('css')
161
+ );
162
+
163
+ if (cssRule && cssRule.use) {
164
+ const cssLoaderUse = cssRule.use.find(use =>
165
+ use.loader && use.loader.includes('css-loader')
166
+ );
167
+
168
+ if (cssLoaderUse && cssLoaderUse.options && cssLoaderUse.options.modules) {
169
+ // Override v9 default to use v8-style default exports
170
+ cssLoaderUse.options.modules.namedExport = false;
171
+ cssLoaderUse.options.modules.exportLocalsConvention = 'asIs';
172
+ }
173
+ }
174
+
175
+ return config;
176
+ };
177
+
178
+ const commonOptions = {
179
+ resolve: {
180
+ extensions: ['.css', '.ts', '.tsx'],
181
+ },
182
+ };
183
+
184
+ const commonWebpackConfig = () => {
185
+ const config = merge({}, baseClientWebpackConfig, commonOptions);
186
+ return overrideCssModulesConfig(config);
187
+ };
188
+
189
+ module.exports = commonWebpackConfig;
190
+ ```
191
+
192
+ ### Option 2: Create `config/webpack/environment.js` (Alternative)
193
+
194
+ If you prefer using a separate environment file:
195
+
196
+ ```js
197
+ // config/webpack/environment.js
198
+ const { environment } = require('@shakacode/shakapacker');
199
+ const getStyleRule = require('@shakacode/shakapacker/package/utils/getStyleRule');
200
+
201
+ // CSS Modules rule for *.module.css with v8-style default export
202
+ const cssModulesRule = getStyleRule(/\.module\.css$/i, [], {
203
+ sourceMap: true,
204
+ importLoaders: 2,
205
+ modules: {
206
+ auto: true,
207
+ namedExport: false, // <-- override v9 default
208
+ exportLocalsConvention: 'asIs' // keep class names as-is instead of camelCase
209
+ }
210
+ });
211
+
212
+ // Ensure this rule wins for *.module.css
213
+ if (cssModulesRule) {
214
+ environment.loaders.prepend('css-modules', cssModulesRule);
215
+ }
216
+
217
+ // Plain CSS rule for non-modules
218
+ const plainCssRule = getStyleRule(/(?<!\.module)\.css$/i, [], {
219
+ sourceMap: true,
220
+ importLoaders: 2,
221
+ modules: false
222
+ });
223
+
224
+ if (plainCssRule) {
225
+ environment.loaders.append('css', plainCssRule);
226
+ }
227
+
228
+ module.exports = environment;
229
+ ```
230
+
231
+ Then reference this in your environment-specific configs (development.js, production.js, etc.).
232
+
233
+ ### Option 3: (Optional) Sass Modules
234
+
235
+ If you also use Sass modules, add similar configuration for SCSS files:
236
+
237
+ ```js
238
+ // For Option 1 approach, extend the overrideCssModulesConfig function:
239
+ const overrideCssModulesConfig = (config) => {
240
+ // Handle both CSS and SCSS rules
241
+ const styleRules = config.module.rules.filter(rule =>
242
+ rule.test && (rule.test.toString().includes('css') || rule.test.toString().includes('scss'))
243
+ );
244
+
245
+ styleRules.forEach(rule => {
246
+ if (rule.use) {
247
+ const cssLoaderUse = rule.use.find(use =>
248
+ use.loader && use.loader.includes('css-loader')
249
+ );
250
+
251
+ if (cssLoaderUse && cssLoaderUse.options && cssLoaderUse.options.modules) {
252
+ cssLoaderUse.options.modules.namedExport = false;
253
+ cssLoaderUse.options.modules.exportLocalsConvention = 'asIs';
254
+ }
255
+ }
256
+ });
257
+
258
+ return config;
259
+ };
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Detailed Migration Guide
265
+
266
+ ### Migrating from v8 (Default Exports) to v9 (Named Exports)
267
+
268
+ #### 1. Update Import Statements
269
+
270
+ ```js
271
+ // Old (v8 - default export)
272
+ import styles from './Component.module.css';
273
+
274
+ // New (v9 - named exports)
275
+ import { bright, container, button } from './Component.module.css';
276
+ ```
277
+
278
+ #### 2. Update Class References
279
+
280
+ ```js
281
+ // Old (v8)
282
+ <div className={styles.container}>
283
+ <button className={styles.button}>Click me</button>
284
+ <span className={styles.bright}>Highlighted text</span>
285
+ </div>
286
+
287
+ // New (v9)
288
+ <div className={container}>
289
+ <button className={button}>Click me</button>
290
+ <span className={bright}>Highlighted text</span>
291
+ </div>
292
+ ```
293
+
294
+ #### 3. Handle Kebab-Case Class Names
295
+
296
+ **Option A: Use camelCase (v9 default)**
297
+
298
+ With `exportLocalsConvention: 'camelCaseOnly'`, kebab-case class names are automatically converted:
299
+
300
+ ```css
301
+ /* styles.module.css */
302
+ .my-button { ... }
303
+ .primary-color { ... }
304
+ ```
305
+
306
+ ```js
307
+ // v9 default - camelCase conversion
308
+ import { myButton, primaryColor } from './styles.module.css';
309
+ <button className={myButton} />
310
+ ```
311
+
312
+ **Option B: Keep kebab-case with 'dashesOnly'**
313
+
314
+ If you prefer to preserve the original kebab-case names, configure your webpack to use `'dashesOnly'`:
315
+
316
+ ```js
317
+ // config/webpack/commonWebpackConfig.js
318
+ modules: {
319
+ namedExport: true,
320
+ exportLocalsConvention: 'dashesOnly'
321
+ }
322
+ ```
323
+
324
+ ```js
325
+ // With dashesOnly - preserve kebab-case
326
+ import * as styles from './styles.module.css';
327
+ <button className={styles['my-button']} />
328
+
329
+ // Or with aliasing:
330
+ import { 'my-button': myButton } from './styles.module.css';
331
+ <button className={myButton} />
332
+ ```
333
+
334
+ **Note:** With both `'camelCaseOnly'` and `'dashesOnly'`, only one version of each class name is exported. The original kebab-case name is NOT available with `'camelCaseOnly'`, and the camelCase version is NOT available with `'dashesOnly'`.
335
+
336
+ #### 4. Using a Codemod for Large Codebases
337
+
338
+ For large codebases, you can create a codemod to automate the migration:
339
+
340
+ ```js
341
+ // css-modules-v9-migration.js
342
+ module.exports = function(fileInfo, api) {
343
+ const j = api.jscodeshift;
344
+ const root = j(fileInfo.source);
345
+
346
+ // Find CSS module imports
347
+ root.find(j.ImportDeclaration, {
348
+ source: { value: value => value.endsWith('.module.css') }
349
+ }).forEach(path => {
350
+ const defaultSpecifier = path.node.specifiers.find(
351
+ spec => spec.type === 'ImportDefaultSpecifier'
352
+ );
353
+
354
+ if (defaultSpecifier) {
355
+ // Convert default import to namespace import for analysis
356
+ // Then extract used properties and convert to named imports
357
+ // ... codemod implementation
358
+ }
359
+ });
360
+
361
+ return root.toSource();
362
+ };
363
+ ```
364
+
365
+ Run with:
366
+ ```bash
367
+ npx jscodeshift -t css-modules-v9-migration.js src/
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Version Comparison
373
+
374
+ | Feature | v8 (and earlier) | v9 |
375
+ |---------|-----------------|----|
376
+ | Default behavior | Default export object | Named exports |
377
+ | Import syntax | `import styles from '...'` | `import { className } from '...'` |
378
+ | Class reference | `styles.className` | `className` |
379
+ | Export convention | `asIs` (no transformation) | `camelCaseOnly` |
380
+ | TypeScript warnings | May show warnings | No warnings |
381
+ | Tree-shaking | Limited | Optimized |
382
+
383
+ ---
384
+
385
+ ## Benefits of Named Exports (v9 Default)
386
+
387
+ 1. **No Build Warnings**: Eliminates webpack/TypeScript warnings about missing exports
388
+ 2. **Better Tree-Shaking**: Unused CSS class exports can be eliminated
389
+ 3. **Explicit Dependencies**: Clear about which CSS classes are being used
390
+ 4. **Modern Standards**: Aligns with ES modules and modern tooling
391
+ 5. **Type Safety**: TypeScript can validate individual class imports
392
+
393
+ ## Benefits of Default Exports (v8 Behavior)
394
+
395
+ 1. **Familiar Pattern**: Matches most existing React tutorials
396
+ 2. **Namespace Import**: All classes available under one import
397
+ 3. **Less Verbose**: Single import for all classes
398
+ 4. **Legacy Compatibility**: Works with existing codebases
399
+
400
+ ---
401
+
402
+ ## Verifying the Configuration
403
+
404
+ ### 1. Rebuild Your Packs
405
+
406
+ After making any configuration changes, rebuild your webpack bundles:
407
+
408
+ ```bash
409
+ # For development
410
+ NODE_ENV=development bin/shakapacker
411
+
412
+ # Or with the dev server
413
+ bin/shakapacker-dev-server
414
+ ```
415
+
416
+ ### 2. Test in Your React Component
417
+
418
+ Verify your imports work correctly:
419
+
420
+ ```js
421
+ // v9 default (named exports)
422
+ import { bright } from './Foo.module.css';
423
+ console.log(bright); // 'Foo_bright__hash'
424
+
425
+ // Or if using v8 configuration (default export)
426
+ import styles from './Foo.module.css';
427
+ console.log(styles); // { bright: 'Foo_bright__hash' }
428
+ ```
429
+
430
+ ### 3. Debug Webpack Configuration (Optional)
431
+
432
+ To inspect the final webpack configuration:
433
+
434
+ ```bash
435
+ NODE_ENV=development bin/shakapacker --profile --json > /tmp/webpack-stats.json
436
+ ```
437
+
438
+ Then search for `css-loader` options in the generated JSON file.
439
+
440
+ ---
441
+
442
+ ## Troubleshooting
443
+
444
+ ### Build Error: exportLocalsConvention Incompatible with namedExport
445
+
446
+ If you see this error during build:
447
+ ```
448
+ "exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option
449
+ ```
450
+
451
+ **Cause:** Your webpack configuration has `namedExport: true` with `exportLocalsConvention: 'camelCase'`.
452
+
453
+ **Solution:** Change `exportLocalsConvention` to `'camelCaseOnly'` or `'dashesOnly'`:
454
+
455
+ ```js
456
+ // config/webpack/commonWebpackConfig.js or similar
457
+ modules: {
458
+ namedExport: true,
459
+ exportLocalsConvention: 'camelCaseOnly' // or 'dashesOnly'
460
+ }
461
+ ```
462
+
463
+ Alternatively, if you need the `'camelCase'` option (both original and camelCase exports), you must revert to v8 behavior by setting `namedExport: false` as shown in the "Reverting to Default Exports" section above.
464
+
465
+ ### CSS Classes Not Applying
466
+
467
+ If your CSS classes aren't applying after the upgrade:
468
+
469
+ 1. **Check import syntax**: Ensure you're using the correct import style for your configuration
470
+ 2. **Verify class names**: Use `console.log` to see available classes
471
+ 3. **Check camelCase conversion**: Kebab-case names are converted to camelCase in v9 with `'camelCaseOnly'`
472
+ 4. **Rebuild webpack**: Clear cache and rebuild: `rm -rf tmp/cache && bin/shakapacker`
473
+
474
+ ### TypeScript Support
475
+
476
+ #### For v9 (Named Exports)
477
+
478
+ ```typescript
479
+ // src/types/css-modules.d.ts
480
+ declare module '*.module.css' {
481
+ const classes: { [key: string]: string };
482
+ export = classes;
483
+ }
484
+ ```
485
+
486
+ #### For v8 Behavior (Default Export)
487
+
488
+ ```typescript
489
+ // src/types/css-modules.d.ts
490
+ declare module '*.module.css' {
491
+ const classes: { [key: string]: string };
492
+ export default classes;
493
+ }
494
+ ```
495
+
496
+ ### Build Performance
497
+
498
+ The configuration changes should not impact build performance significantly. If you experience issues:
499
+
500
+ 1. Check webpack stats: `bin/shakapacker --profile`
501
+ 2. Verify only necessary rules are being modified
502
+ 3. Consider using webpack bundle analyzer for deeper insights
503
+
504
+ ---
505
+
506
+ ## Summary
507
+
508
+ - **v9 default**: Named exports with camelCase conversion
509
+ - **v8 default**: Default export object with no conversion
510
+ - **Migration path**: Update imports or override configuration
511
+ - **Benefits of v9**: No warnings, better tree-shaking, explicit dependencies
512
+ - **Keeping v8 behavior**: Override css-loader configuration as shown above
data/docs/deployment.md CHANGED
@@ -3,8 +3,6 @@
3
3
  Shakapacker hooks up a new `shakapacker:compile` task to `assets:precompile`, which gets run whenever you run `assets:precompile`.
4
4
  If you are not using Sprockets `shakapacker:compile` is automatically aliased to `assets:precompile`.
5
5
 
6
- ```
7
-
8
6
  ## Heroku
9
7
 
10
8
  In order for your Shakapacker app to run on Heroku, you'll need to do a bit of configuration before hand.
@@ -19,13 +17,59 @@ git push heroku master
19
17
 
20
18
  We're essentially doing the following here:
21
19
 
22
- * Creating an app on Heroku
23
- * Creating a Postgres database for the app (this is assuming that you're using Heroku Postgres for your app)
24
- * Adding the Heroku NodeJS and Ruby buildpacks for your app. This allows the `npm` or `yarn` executables to properly function when compiling your app - as well as Ruby.
25
- * Pushing your code to Heroku and kicking off the deployment
20
+ - Creating an app on Heroku
21
+ - Creating a Postgres database for the app (this is assuming that you're using Heroku Postgres for your app)
22
+ - Adding the Heroku NodeJS and Ruby buildpacks for your app. This allows the `npm` or `yarn` executables to properly function when compiling your app - as well as Ruby.
23
+ - Pushing your code to Heroku and kicking off the deployment
26
24
 
27
25
  Your production build process is responsible for installing your JavaScript dependencies before `rake assets:precompile`. For example, if you are on Heroku, the `heroku/nodejs` buildpack must run **prior** to the `heroku/ruby` buildpack for precompilation to run successfully.
28
26
 
27
+ ### Custom Rails Environments (e.g., staging)
28
+
29
+ **Key distinction:**
30
+
31
+ - **RAILS_ENV** is used to look up configuration in `config/shakapacker.yml`
32
+ - **NODE_ENV** is used by your `webpack.config.js` (or `rspack.config.js`) for build optimizations
33
+
34
+ **Good news:** As of this version, `bin/shakapacker` automatically sets `NODE_ENV=production` for custom environments like staging:
35
+
36
+ ```bash
37
+ # NODE_ENV automatically set to 'production' for staging
38
+ RAILS_ENV=staging bin/shakapacker
39
+
40
+ # Also works with rake task
41
+ RAILS_ENV=staging bundle exec rails assets:precompile
42
+ ```
43
+
44
+ **How it works:**
45
+
46
+ - `RAILS_ENV=development` → `NODE_ENV=development`
47
+ - `RAILS_ENV=test` → `NODE_ENV=test`
48
+ - `RAILS_ENV=production` → `NODE_ENV=production`
49
+ - Any other custom env → `NODE_ENV=production`
50
+
51
+ **Configuration fallback:**
52
+
53
+ You don't need to add custom environments to your `shakapacker.yml`. Shakapacker automatically falls back to production-like defaults:
54
+
55
+ 1. First, it looks for the environment you're deploying to (e.g., `staging`)
56
+ 2. If not found, it falls back to `production` configuration
57
+
58
+ This means staging environments automatically use production settings (compile: false, cache_manifest: true, etc.).
59
+
60
+ **Optional: Staging-specific configuration**
61
+
62
+ If you want different settings for staging, explicitly add a `staging` section:
63
+
64
+ ```yaml
65
+ staging:
66
+ <<: *default
67
+ compile: false
68
+ cache_manifest: true
69
+ # Staging-specific overrides (e.g., different output path)
70
+ public_output_path: packs-staging
71
+ ```
72
+
29
73
  ## Nginx
30
74
 
31
75
  Shakapacker doesn't serve anything in production. You’re expected to configure your web server to serve files in public/ directly.
@@ -88,7 +132,16 @@ Now, you can set `brotli_static on;` in your nginx site config, as per the confi
88
132
 
89
133
  ## CDN
90
134
 
91
- If you are using a CDN setup, Shakapacker does NOT use the `ASSET_HOST` environment variable to prefix URLs for assets during bundle compilation. You must use the `SHAKAPACKER_ASSET_HOST` environment variable instead (`WEBPACKER_ASSET_HOST` if you're using any version of Webpacker or Shakapacker before Shakapacker v7).
135
+ Shakapacker supports serving JavaScript bundles and assets from a CDN. For a comprehensive guide on setting up CDN with Shakapacker, including CloudFlare configuration, troubleshooting, and advanced setups, see the [CDN Setup Guide](cdn_setup.md).
136
+
137
+ **Quick Setup**: Set the `SHAKAPACKER_ASSET_HOST` environment variable before compiling assets:
138
+
139
+ ```bash
140
+ export SHAKAPACKER_ASSET_HOST=https://cdn.example.com
141
+ RAILS_ENV=production bundle exec rails assets:precompile
142
+ ```
143
+
144
+ Note: Shakapacker does NOT use the `ASSET_HOST` environment variable. You must use `SHAKAPACKER_ASSET_HOST` instead (`WEBPACKER_ASSET_HOST` if using Shakapacker before v7).
92
145
 
93
146
  ## Capistrano
94
147
 
@@ -108,10 +161,10 @@ namespace :deploy do
108
161
  desc "Run rake js install"
109
162
  task :js_install do
110
163
  require "package_json"
111
-
164
+
112
165
  # this will use the package manager specified via `packageManager`, or otherwise fallback to `npm`
113
166
  native_js_install_command = PackageJson.read.manager.native_install_command(frozen: true).join(" ")
114
-
167
+
115
168
  on roles(:web) do
116
169
  within release_path do
117
170
  execute("cd #{release_path} && #{native_js_install_command}")