shakapacker 9.0.0.beta.8 → 9.0.0.beta.10

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.
data/README.md CHANGED
@@ -1,13 +1,14 @@
1
- # Shakapacker (v8)
1
+ # Shakapacker (v9)
2
2
  ---
3
3
 
4
- _🚀 Shakapacker 9.0.0.beta.2 supports [Rspack](https://rspack.rs/)! 10x faster than webpack!_
4
+ _🚀 Shakapacker 9 supports [Rspack](https://rspack.rs/)! 10x faster than webpack!_
5
5
 
6
6
  ---
7
7
 
8
8
  _Official, actively maintained successor to [rails/webpacker](https://github.com/rails/webpacker). ShakaCode stands behind the long-term maintenance and development of this project for the Rails community._
9
9
 
10
10
  * ⚠️ See the [6-stable](https://github.com/shakacode/shakapacker/tree/6-stable) branch for Shakapacker v6.x code and documentation. :warning:
11
+ * **See [V9 Upgrade](./docs/v9_upgrade.md) for upgrading from the v8 release.**
11
12
  * See [V8 Upgrade](./docs/v8_upgrade.md) for upgrading from the v7 release.
12
13
  * See [V7 Upgrade](./docs/v7_upgrade.md) for upgrading from the v6 release.
13
14
  * See [V6 Upgrade](./docs/v6_upgrade.md) for upgrading from v5 or prior v6 releases.
data/TODO_v9.md CHANGED
@@ -20,12 +20,15 @@ Align with Next.js and modern tooling by using named exports:
20
20
  options: {
21
21
  modules: {
22
22
  namedExport: true,
23
- exportLocalsConvention: 'camelCase'
23
+ exportLocalsConvention: 'camelCaseOnly' // Must be 'camelCaseOnly' or 'dashesOnly' with namedExport: true
24
24
  }
25
25
  }
26
26
  }
27
27
  ```
28
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
+
29
32
  2. **Update TypeScript types:**
30
33
  - Ensure proper typing for CSS modules with named exports
31
34
  - May need to update or generate `.d.ts` files for CSS modules
@@ -81,4 +84,4 @@ Align with Next.js and modern tooling by using named exports:
81
84
  ### Test Infrastructure
82
85
  - Successfully implemented dual bundler support (webpack/rspack)
83
86
  - test-bundler script working well with status command
84
- - Consider adding more comprehensive tests for both bundlers
87
+ - Consider adding more comprehensive tests for both bundlers
data/conductor-setup.sh CHANGED
@@ -35,6 +35,18 @@ $BUNDLE_CMD install
35
35
  echo "📦 Installing JavaScript dependencies..."
36
36
  yarn install
37
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
+
38
50
  # Copy environment files if they exist in root
39
51
  if [ -n "${CONDUCTOR_ROOT_PATH:-}" ]; then
40
52
  if [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
@@ -32,6 +32,55 @@ import * as styles from './Foo.module.css';
32
32
  - Aligns with modern JavaScript module standards
33
33
  - Automatically converts kebab-case to camelCase (`my-button` → `myButton`)
34
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
+
35
84
  ## Version 8.x and Earlier Behavior
36
85
 
37
86
  In Shakapacker v8 and earlier, the default behavior was to use a **default export object**:
@@ -244,7 +293,9 @@ import { bright, container, button } from './Component.module.css';
244
293
 
245
294
  #### 3. Handle Kebab-Case Class Names
246
295
 
247
- With v9's `exportLocalsConvention: 'camelCase'`, kebab-case class names are automatically converted:
296
+ **Option A: Use camelCase (v9 default)**
297
+
298
+ With `exportLocalsConvention: 'camelCaseOnly'`, kebab-case class names are automatically converted:
248
299
 
249
300
  ```css
250
301
  /* styles.module.css */
@@ -253,13 +304,35 @@ With v9's `exportLocalsConvention: 'camelCase'`, kebab-case class names are auto
253
304
  ```
254
305
 
255
306
  ```js
256
- // v9 imports (camelCase conversion)
307
+ // v9 default - camelCase conversion
257
308
  import { myButton, primaryColor } from './styles.module.css';
309
+ <button className={myButton} />
310
+ ```
258
311
 
259
- // Use the camelCase versions in your components
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';
260
331
  <button className={myButton} />
261
332
  ```
262
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
+
263
336
  #### 4. Using a Codemod for Large Codebases
264
337
 
265
338
  For large codebases, you can create a codemod to automate the migration:
@@ -298,12 +371,12 @@ npx jscodeshift -t css-modules-v9-migration.js src/
298
371
 
299
372
  ## Version Comparison
300
373
 
301
- | Feature | v8 (and earlier) | v9 |
302
- |---------|-----------------|----|
374
+ | Feature | v8 (and earlier) | v9 |
375
+ |---------|-----------------|----|
303
376
  | Default behavior | Default export object | Named exports |
304
377
  | Import syntax | `import styles from '...'` | `import { className } from '...'` |
305
378
  | Class reference | `styles.className` | `className` |
306
- | Export convention | `asIs` (no transformation) | `camelCase` |
379
+ | Export convention | `asIs` (no transformation) | `camelCaseOnly` |
307
380
  | TypeScript warnings | May show warnings | No warnings |
308
381
  | Tree-shaking | Limited | Optimized |
309
382
 
@@ -368,13 +441,34 @@ Then search for `css-loader` options in the generated JSON file.
368
441
 
369
442
  ## Troubleshooting
370
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
+
371
465
  ### CSS Classes Not Applying
372
466
 
373
467
  If your CSS classes aren't applying after the upgrade:
374
468
 
375
469
  1. **Check import syntax**: Ensure you're using the correct import style for your configuration
376
470
  2. **Verify class names**: Use `console.log` to see available classes
377
- 3. **Check camelCase conversion**: Kebab-case names are converted to camelCase in v9
471
+ 3. **Check camelCase conversion**: Kebab-case names are converted to camelCase in v9 with `'camelCaseOnly'`
378
472
  4. **Rebuild webpack**: Clear cache and rebuild: `rm -rf tmp/cache && bin/shakapacker`
379
473
 
380
474
  ### TypeScript Support
@@ -415,4 +509,4 @@ The configuration changes should not impact build performance significantly. If
415
509
  - **v8 default**: Default export object with no conversion
416
510
  - **Migration path**: Update imports or override configuration
417
511
  - **Benefits of v9**: No warnings, better tree-shaking, explicit dependencies
418
- - **Keeping v8 behavior**: Override css-loader configuration as shown above
512
+ - **Keeping v8 behavior**: Override css-loader configuration as shown above
data/docs/v9_upgrade.md CHANGED
@@ -9,30 +9,69 @@ This guide outlines new features, breaking changes, and migration steps for upgr
9
9
  Shakapacker v9 includes TypeScript definitions for better IDE support and type safety.
10
10
 
11
11
  - **No breaking changes** - JavaScript configs continue to work
12
- - **Optional** - Use TypeScript only if you want it
12
+ - **Optional** - Use TypeScript only if you want it
13
13
  - **Type safety** - Catch configuration errors at compile-time
14
14
  - **IDE support** - Full autocomplete for all options
15
15
 
16
16
  See the [TypeScript Documentation](./typescript.md) for usage examples.
17
17
 
18
+ ### NODE_ENV Default Behavior Fixed
19
+
20
+ **What changed:** NODE_ENV now intelligently defaults based on RAILS_ENV instead of always defaulting to "production".
21
+
22
+ **New behavior:**
23
+
24
+ - When `RAILS_ENV=production` → `NODE_ENV` defaults to `"production"`
25
+ - When `RAILS_ENV=development` or unset → `NODE_ENV` defaults to `"development"`
26
+ - When `RAILS_ENV` is any other value (test, staging, etc.) → `NODE_ENV` defaults to `"development"`
27
+
28
+ **Benefits:**
29
+
30
+ - **Dev server "just works"** - No need to explicitly set NODE_ENV when running the development server
31
+ - **Correct configuration loaded** - Development server now properly loads the development configuration from shakapacker.yml
32
+ - **Fixes port issues** - Dev server uses the configured port (e.g., 3035) instead of defaulting to 8080
33
+ - **Fixes 404 errors** - Assets load correctly without requiring manual NODE_ENV configuration
34
+
35
+ **No action required** - This change improves the default behavior and requires no migration.
36
+
37
+ **If you previously worked around this bug**, you can now remove these workarounds:
38
+
39
+ - Remove `NODE_ENV=development` from your `.env`, `.env.development`, or `.env.local` files
40
+ - Remove `NODE_ENV=development` from your `docker-compose.yml` or Dockerfile
41
+ - Remove custom scripts that set NODE_ENV before running the dev server
42
+ - Remove `NODE_ENV=development` from your `bin/dev` or Procfile.dev
43
+
18
44
  ## Breaking Changes
19
45
 
20
46
  ### 1. CSS Modules Configuration Changed to Named Exports
21
47
 
22
- **What changed:** CSS Modules are now configured with `namedExport: true` and `exportLocalsConvention: 'camelCase'` by default, aligning with Next.js and modern tooling standards.
48
+ **What changed:** CSS Modules are now configured with `namedExport: true` and `exportLocalsConvention: 'camelCaseOnly'` by default, aligning with Next.js and modern tooling standards.
49
+
50
+ > **Important:** When `namedExport: true` is enabled, css-loader requires `exportLocalsConvention` to be either `'camelCaseOnly'` or `'dashesOnly'`. Using `'camelCase'` will cause a build error: `"exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option`.
51
+
52
+ **Quick Reference: Configuration Options**
53
+
54
+ | Configuration | namedExport | exportLocalsConvention | CSS: `.my-button` | Export Available | Works With |
55
+ | --------------- | ----------- | ---------------------- | ----------------- | --------------------------------- | ----------------- |
56
+ | **v9 Default** | `true` | `'camelCaseOnly'` | `.my-button` | `myButton` only | ✅ Named exports |
57
+ | **Alternative** | `true` | `'dashesOnly'` | `.my-button` | `'my-button'` only | ✅ Named exports |
58
+ | **v8 Style** | `false` | `'camelCase'` | `.my-button` | Both `myButton` AND `'my-button'` | ✅ Default export |
59
+ | **❌ Invalid** | `true` | `'camelCase'` | - | - | ❌ Build Error |
23
60
 
24
61
  **JavaScript Projects:**
62
+
25
63
  ```js
26
64
  // Before (v8)
27
- import styles from './Component.module.css';
28
- <button className={styles.button} />
65
+ import styles from "./Component.module.css"
66
+ ;<button className={styles.button} />
29
67
 
30
68
  // After (v9)
31
- import { button } from './Component.module.css';
32
- <button className={button} />
69
+ import { button } from "./Component.module.css"
70
+ ;<button className={button} />
33
71
  ```
34
72
 
35
73
  **TypeScript Projects:**
74
+
36
75
  ```typescript
37
76
  // Before (v8)
38
77
  import styles from './Component.module.css';
@@ -46,6 +85,7 @@ import * as styles from './Component.module.css';
46
85
  **Migration Options:**
47
86
 
48
87
  1. **Update your code** (Recommended):
88
+
49
89
  - JavaScript: Change to named imports (`import { className }`)
50
90
  - TypeScript: Change to namespace imports (`import * as styles`)
51
91
  - Kebab-case class names are automatically converted to camelCase
@@ -55,6 +95,7 @@ import * as styles from './Component.module.css';
55
95
  - This gives you time to migrate gradually
56
96
 
57
97
  **Benefits of the change:**
98
+
58
99
  - Eliminates webpack/TypeScript warnings
59
100
  - Better tree-shaking of unused CSS classes
60
101
  - More explicit about which classes are used
@@ -65,15 +106,17 @@ import * as styles from './Component.module.css';
65
106
  **What changed:** The configuration option has been renamed to better reflect its purpose.
66
107
 
67
108
  **Before (v8):**
109
+
68
110
  ```yml
69
111
  # config/shakapacker.yml
70
- webpack_loader: 'babel'
112
+ webpack_loader: "babel"
71
113
  ```
72
114
 
73
115
  **After (v9):**
116
+
74
117
  ```yml
75
118
  # config/shakapacker.yml
76
- javascript_transpiler: 'babel'
119
+ javascript_transpiler: "babel"
77
120
  ```
78
121
 
79
122
  **Note:** The old `webpack_loader` option is deprecated but still supported with a warning.
@@ -85,38 +128,48 @@ javascript_transpiler: 'babel'
85
128
  **Why:** SWC is 20x faster than Babel while maintaining compatibility with most JavaScript and TypeScript code.
86
129
 
87
130
  **Impact on existing projects:**
131
+
88
132
  - Your project will continue using Babel if you already have babel packages in package.json
89
133
  - To switch to SWC for better performance, see migration options below
90
134
 
91
135
  **Impact on new projects:**
136
+
92
137
  - New installations will use SWC by default
93
138
  - Babel dependencies won't be installed unless explicitly configured
94
139
 
95
140
  ### Migration Options
96
141
 
97
142
  #### Option 1 (Recommended): Switch to SWC
143
+
98
144
  ```yml
99
145
  # config/shakapacker.yml
100
- javascript_transpiler: 'swc'
146
+ javascript_transpiler: "swc"
101
147
  ```
148
+
102
149
  Then install SWC:
150
+
103
151
  ```bash
104
152
  npm install @swc/core swc-loader
105
153
  ```
106
154
 
107
155
  #### Option 2: Keep using Babel
156
+
108
157
  ```yml
109
158
  # config/shakapacker.yml
110
- javascript_transpiler: 'babel'
159
+ javascript_transpiler: "babel"
111
160
  ```
161
+
112
162
  No other changes needed - your existing babel packages will continue to work.
113
163
 
114
164
  #### Option 3: Use esbuild
165
+
115
166
  ```yml
116
167
  # config/shakapacker.yml
117
- javascript_transpiler: 'esbuild'
168
+ javascript_transpiler: "esbuild"
118
169
  ```
170
+
119
171
  Then install esbuild:
172
+
120
173
  ```bash
121
174
  npm install esbuild esbuild-loader
122
175
  ```
@@ -127,7 +180,7 @@ npm install esbuild esbuild-loader
127
180
 
128
181
  ```yml
129
182
  # config/shakapacker.yml
130
- assets_bundler: 'rspack' # or 'webpack' (default)
183
+ assets_bundler: "rspack" # or 'webpack' (default)
131
184
  ```
132
185
 
133
186
  ### 5. All Peer Dependencies Now Optional
@@ -135,16 +188,19 @@ assets_bundler: 'rspack' # or 'webpack' (default)
135
188
  **What changed:** All peer dependencies are now marked as optional via `peerDependenciesMeta`.
136
189
 
137
190
  **Benefits:**
191
+
138
192
  - **No installation warnings** - You won't see peer dependency warnings for packages you don't use
139
193
  - **Install only what you need** - Using webpack? Don't install rspack. Using SWC? Don't install Babel.
140
194
  - **Clear version constraints** - When you do install a package, version compatibility is still enforced
141
195
 
142
196
  **What this means for you:**
197
+
143
198
  - **Existing projects:** No changes needed. Your existing dependencies will continue to work.
144
199
  - **New projects:** The installer only adds the packages you actually need based on your configuration.
145
200
  - **Package manager behavior:** npm, yarn, and pnpm will no longer warn about missing peer dependencies.
146
201
 
147
202
  **Example:** If you're using SWC with webpack, you only need:
203
+
148
204
  ```json
149
205
  {
150
206
  "dependencies": {
@@ -157,6 +213,7 @@ assets_bundler: 'rspack' # or 'webpack' (default)
157
213
  }
158
214
  }
159
215
  ```
216
+
160
217
  You won't get warnings about missing Babel, Rspack, or esbuild packages.
161
218
 
162
219
  ## Migration Steps
@@ -175,22 +232,22 @@ yarn upgrade shakapacker@^9.0.0
175
232
 
176
233
  ```js
177
234
  // Find imports like this:
178
- import styles from './styles.module.css';
235
+ import styles from "./styles.module.css"
179
236
 
180
237
  // Replace with named imports:
181
- import { className1, className2 } from './styles.module.css';
238
+ import { className1, className2 } from "./styles.module.css"
182
239
  ```
183
240
 
184
241
  #### Update TypeScript definitions:
185
242
 
186
243
  ```typescript
187
244
  // Update your CSS module type definitions
188
- declare module '*.module.css' {
245
+ declare module "*.module.css" {
189
246
  // With namedExport: true, css-loader generates individual named exports
190
247
  // TypeScript can't know the exact names at compile time, so we declare
191
248
  // a module with any number of string exports
192
- const classes: { readonly [key: string]: string };
193
- export = classes;
249
+ const classes: { readonly [key: string]: string }
250
+ export = classes
194
251
  // Note: This allows 'import * as styles' but not 'import styles from'
195
252
  // because css-loader with namedExport: true doesn't generate a default export
196
253
  }
@@ -198,19 +255,45 @@ declare module '*.module.css' {
198
255
 
199
256
  ### Step 3: Handle Kebab-Case Class Names
200
257
 
201
- v9 automatically converts kebab-case to camelCase:
258
+ v9 automatically converts kebab-case to camelCase with `exportLocalsConvention: 'camelCaseOnly'`:
202
259
 
203
260
  ```css
204
261
  /* styles.module.css */
205
- .my-button { }
206
- .primary-color { }
262
+ .my-button {
263
+ }
264
+ .primary-color {
265
+ }
266
+ ```
267
+
268
+ ```js
269
+ // v9 default - camelCase conversion
270
+ import { myButton, primaryColor } from "./styles.module.css"
207
271
  ```
208
272
 
273
+ **Alternative: Keep kebab-case names with 'dashesOnly'**
274
+
275
+ If you prefer to keep kebab-case names in JavaScript, you can override the configuration to use `'dashesOnly'`:
276
+
277
+ ```js
278
+ // config/webpack/commonWebpackConfig.js
279
+ modules: {
280
+ namedExport: true,
281
+ exportLocalsConvention: 'dashesOnly' // Keep original kebab-case names
282
+ }
283
+ ```
284
+
285
+ Then use the original kebab-case names in your imports:
286
+
209
287
  ```js
210
- // v9 imports
211
- import { myButton, primaryColor } from './styles.module.css';
288
+ // With dashesOnly configuration
289
+ import { 'my-button': myButton, 'primary-color': primaryColor } from './styles.module.css';
290
+ // or access as properties
291
+ import * as styles from './styles.module.css';
292
+ const buttonClass = styles['my-button'];
212
293
  ```
213
294
 
295
+ **Note:** With `'camelCaseOnly'` (default) or `'dashesOnly'`, only one version is exported. If you need both the original and camelCase versions, you would need to use `'camelCase'` instead, but this requires `namedExport: false` (v8 behavior). See the [CSS Modules Export Mode documentation](./css-modules-export-mode.md) for details on reverting to v8 behavior.
296
+
214
297
  ### Step 4: Update Configuration Files
215
298
 
216
299
  If you have `webpack_loader` in your configuration:
@@ -221,7 +304,7 @@ If you have `webpack_loader` in your configuration:
221
304
  # webpack_loader: 'babel'
222
305
 
223
306
  # NEW:
224
- javascript_transpiler: 'babel'
307
+ javascript_transpiler: "babel"
225
308
  ```
226
309
 
227
310
  ### Step 5: Run Tests
@@ -253,35 +336,60 @@ Update your global type definitions as shown in Step 2.
253
336
 
254
337
  If you see warnings about CSS module exports, ensure you've updated all imports to use named exports or have properly configured the override.
255
338
 
339
+ ### Build Error: exportLocalsConvention Incompatible with namedExport
340
+
341
+ If you see this error:
342
+
343
+ ```
344
+ "exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option
345
+ ```
346
+
347
+ This means your webpack configuration has `namedExport: true` with `exportLocalsConvention: 'camelCase'`. The fix is to change to `'camelCaseOnly'` or `'dashesOnly'`:
348
+
349
+ ```js
350
+ // config/webpack/commonWebpackConfig.js or wherever you configure css-loader
351
+ modules: {
352
+ namedExport: true,
353
+ exportLocalsConvention: 'camelCaseOnly' // or 'dashesOnly'
354
+ }
355
+ ```
356
+
357
+ If you want to use `'camelCase'` (which exports both original and camelCase versions), you must set `namedExport: false` and revert to v8 behavior. See the [CSS Modules Export Mode documentation](./css-modules-export-mode.md) for details.
358
+
256
359
  ### Unexpected Peer Dependency Warnings After Upgrade
257
360
 
258
361
  If you experience unexpected peer dependency warnings after upgrading to v9, you may need to clear your package manager's cache and reinstall dependencies. This ensures the new optional peer dependency configuration takes effect properly.
259
362
 
260
363
  **For npm:**
364
+
261
365
  ```bash
262
366
  rm -rf node_modules package-lock.json
263
367
  npm install
264
368
  ```
265
369
 
266
370
  **For Yarn:**
371
+
267
372
  ```bash
268
373
  rm -rf node_modules yarn.lock
269
374
  yarn install
270
375
  ```
271
376
 
272
377
  **For pnpm:**
378
+
273
379
  ```bash
274
380
  rm -rf node_modules pnpm-lock.yaml
275
381
  pnpm install
276
382
  ```
277
383
 
278
384
  **For Bun:**
385
+
279
386
  ```bash
280
387
  rm -rf node_modules bun.lockb
281
388
  bun install
282
389
  ```
283
390
 
284
391
  **When is this necessary?**
392
+
285
393
  - If you see peer dependency warnings for packages you don't use (e.g., warnings about Babel when using SWC)
286
394
  - If your package manager cached the old dependency resolution from v8
287
395
  - After switching transpilers or bundlers (e.g., from Babel to SWC, or webpack to rspack)
@@ -43,6 +43,7 @@ module Shakapacker
43
43
  # Dependency checks
44
44
  check_javascript_transpiler_dependencies if config_exists?
45
45
  check_css_dependencies
46
+ check_css_modules_configuration
46
47
  check_bundler_dependencies if config_exists?
47
48
  check_file_type_dependencies if config_exists?
48
49
  check_sri_dependencies if config_exists?
@@ -432,6 +433,28 @@ module Shakapacker
432
433
  if transpiler == "esbuild" && package_installed?("babel-loader")
433
434
  @warnings << "Both esbuild and Babel dependencies are installed. Consider removing Babel dependencies to reduce node_modules size"
434
435
  end
436
+
437
+ # Check for SWC configuration conflicts
438
+ if transpiler == "swc"
439
+ check_swc_config_conflicts
440
+ end
441
+ end
442
+
443
+ def check_swc_config_conflicts
444
+ swcrc_path = root_path.join(".swcrc")
445
+ return unless swcrc_path.exist?
446
+
447
+ begin
448
+ swcrc = JSON.parse(File.read(swcrc_path))
449
+ # Check for conflicting jsc.target and env settings
450
+ if swcrc.dig("jsc", "target") && swcrc["env"]
451
+ @issues << "SWC configuration conflict: .swcrc contains both 'jsc.target' and 'env' settings, which are mutually exclusive. Remove 'jsc.target' from .swcrc"
452
+ elsif swcrc.dig("jsc", "target")
453
+ @warnings << "SWC configuration: .swcrc contains 'jsc.target' which may conflict with the loader's 'env' setting. Consider removing 'jsc.target' from .swcrc to avoid build errors"
454
+ end
455
+ rescue JSON::ParserError
456
+ @warnings << "SWC configuration: .swcrc exists but contains invalid JSON"
457
+ end
435
458
  end
436
459
 
437
460
  def check_css_dependencies
@@ -440,6 +463,77 @@ module Shakapacker
440
463
  check_optional_dependency("mini-css-extract-plugin", @warnings, "CSS extraction")
441
464
  end
442
465
 
466
+ def check_css_modules_configuration
467
+ # Check for CSS module files in the project
468
+ return unless config_exists?
469
+
470
+ source_path = config.source_path
471
+ return unless source_path.exist?
472
+
473
+ # Performance optimization: Just check if ANY CSS module file exists
474
+ # Using .first with early return is much faster than globbing all files
475
+ css_module_exists = Dir.glob(File.join(source_path, "**/*.module.{css,scss,sass}")).first
476
+ return unless css_module_exists
477
+
478
+ # Check webpack configuration for CSS modules settings
479
+ webpack_config_paths = [
480
+ root_path.join("config/webpack/webpack.config.js"),
481
+ root_path.join("config/webpack/webpack.config.ts"),
482
+ root_path.join("config/webpack/commonWebpackConfig.js"),
483
+ root_path.join("config/webpack/commonWebpackConfig.ts")
484
+ ]
485
+
486
+ webpack_config_paths.each do |config_path|
487
+ next unless config_path.exist?
488
+
489
+ config_content = File.read(config_path)
490
+
491
+ # Check for the invalid configuration: namedExport: true with exportLocalsConvention: 'camelCase'
492
+ if config_content.match(/namedExport\s*:\s*true/) && config_content.match(/exportLocalsConvention\s*:\s*['"]camelCase['"]/)
493
+ @issues << "CSS Modules: Invalid configuration detected in #{config_path.relative_path_from(root_path)}"
494
+ @issues << " Using exportLocalsConvention: 'camelCase' with namedExport: true will cause build errors"
495
+ @issues << " Change to 'camelCaseOnly' or 'dashesOnly'. See docs/v9_upgrade.md for details"
496
+ end
497
+
498
+ # Warn if CSS modules are used but no configuration is found
499
+ if !config_content.match(/namedExport/) && !config_content.match(/exportLocalsConvention/)
500
+ @info << "CSS module files found but no explicit CSS modules configuration detected"
501
+ @info << " v9 defaults: namedExport: true, exportLocalsConvention: 'camelCaseOnly'"
502
+ end
503
+ end
504
+
505
+ # Check for common v8 to v9 migration issues
506
+ check_css_modules_import_patterns
507
+ rescue => e
508
+ # Don't fail doctor if CSS modules check has issues
509
+ @warnings << "Unable to validate CSS modules configuration: #{e.message}"
510
+ end
511
+
512
+ def check_css_modules_import_patterns
513
+ # Look for JavaScript/TypeScript files that might have v8-style imports
514
+ source_path = config.source_path
515
+
516
+ # Use lazy evaluation with Enumerator to avoid loading all file paths into memory
517
+ # Stop after checking 50 files or finding a match
518
+ v8_pattern = /import\s+\w+\s+from\s+['"][^'"]*\.module\.(css|scss|sass)['"]/
519
+
520
+ Dir.glob(File.join(source_path, "**/*.{js,jsx,ts,tsx}")).lazy.take(50).each do |file|
521
+ # Read file and check for v8 pattern
522
+ content = File.read(file)
523
+
524
+ # Check for v8 default import pattern with .module.css
525
+ if v8_pattern.match?(content)
526
+ @warnings << "Potential v8-style CSS module imports detected (using default import)"
527
+ @warnings << " v9 uses named exports. Update to: import { className } from './styles.module.css'"
528
+ @warnings << " Or use: import * as styles from './styles.module.css' (TypeScript)"
529
+ @warnings << " See docs/v9_upgrade.md for migration guide"
530
+ break # Stop after finding first occurrence
531
+ end
532
+ end
533
+ rescue => e
534
+ # Don't fail doctor if import pattern check has issues
535
+ end
536
+
443
537
  def check_bundler_dependencies
444
538
  bundler = config.assets_bundler
445
539
  case bundler