diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31329d5a7..4987d97dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -93,10 +93,11 @@ Changes since the last non-beta release.
```
2. **CSS Modules now use named exports by default**
- - Configured with `namedExport: true` and `exportLocalsConvention: 'camelCase'`
+ - Configured with `namedExport: true` and `exportLocalsConvention: 'camelCaseOnly'`
- **JavaScript:** Use named imports: `import { className } from './styles.module.css'`
- **TypeScript:** Use namespace imports: `import * as styles from './styles.module.css'`
- Default imports (`import styles from '...'`) no longer work
+ - **Note:** css-loader requires `'camelCaseOnly'` or `'dashesOnly'` when `namedExport: true`. Using `'camelCase'` causes a build error.
- See [CSS Modules Export Mode documentation](./docs/css-modules-export-mode.md) for migration details
3. **Configuration option renamed from `webpack_loader` to `javascript_transpiler`**
diff --git a/TODO_v9.md b/TODO_v9.md
index 067471529..79bd8a278 100644
--- a/TODO_v9.md
+++ b/TODO_v9.md
@@ -20,12 +20,15 @@ Align with Next.js and modern tooling by using named exports:
options: {
modules: {
namedExport: true,
- exportLocalsConvention: 'camelCase'
+ exportLocalsConvention: 'camelCaseOnly' // Must be 'camelCaseOnly' or 'dashesOnly' with namedExport: true
}
}
}
```
+**Note:** Using `exportLocalsConvention: 'camelCase'` with `namedExport: true` will cause a build error.
+css-loader only allows `'camelCaseOnly'` or `'dashesOnly'` when named exports are enabled.
+
2. **Update TypeScript types:**
- Ensure proper typing for CSS modules with named exports
- 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:
### Test Infrastructure
- Successfully implemented dual bundler support (webpack/rspack)
- test-bundler script working well with status command
-- Consider adding more comprehensive tests for both bundlers
\ No newline at end of file
+- Consider adding more comprehensive tests for both bundlers
diff --git a/docs/css-modules-export-mode.md b/docs/css-modules-export-mode.md
index 6e613256f..002b558b9 100644
--- a/docs/css-modules-export-mode.md
+++ b/docs/css-modules-export-mode.md
@@ -32,6 +32,55 @@ import * as styles from './Foo.module.css';
- Aligns with modern JavaScript module standards
- Automatically converts kebab-case to camelCase (`my-button` → `myButton`)
+### Important: exportLocalsConvention with namedExport
+
+When `namedExport: true` is enabled (v9 default), css-loader requires `exportLocalsConvention` to be either `'camelCaseOnly'` or `'dashesOnly'`.
+
+**The following will cause a build error:**
+```js
+modules: {
+ namedExport: true,
+ exportLocalsConvention: 'camelCase' // ❌ ERROR: incompatible with namedExport: true
+}
+```
+
+**Error message:**
+```
+"exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option
+```
+
+**Correct v9 configuration:**
+```js
+modules: {
+ namedExport: true,
+ exportLocalsConvention: 'camelCaseOnly' // ✅ Correct - only camelCase exported
+}
+```
+
+**exportLocalsConvention options with namedExport:**
+
+When `namedExport: true`, you can use:
+- `'camelCaseOnly'` (v9 default): Exports ONLY the camelCase version (e.g., only `myButton`)
+- `'dashesOnly'`: Exports ONLY the original kebab-case version (e.g., only `my-button`)
+
+**Not compatible with namedExport: true:**
+- `'camelCase'`: Exports both versions (both `my-button` and `myButton`) - only works with `namedExport: false` (v8 behavior)
+
+**Configuration Quick Reference:**
+
+| namedExport | exportLocalsConvention | `.my-button` exports | Use Case | Compatible? |
+|-------------|------------------------|---------------------|----------|-------------|
+| `true` | `'camelCaseOnly'` | `myButton` | JavaScript conventions | ✅ Valid |
+| `true` | `'dashesOnly'` | `'my-button'` | Preserve CSS naming | ✅ Valid |
+| `false` | `'camelCase'` | Both `myButton` AND `'my-button'` | v8 compatibility | ✅ Valid |
+| `false` | `'asIs'` | `'my-button'` | No transformation | ✅ Valid |
+| `true` | `'camelCase'` | - | - | ❌ Build Error |
+
+**When to use each option:**
+- Use `'camelCaseOnly'` if you prefer standard JavaScript naming conventions
+- Use `'dashesOnly'` if you want to preserve your CSS class names exactly as written
+- Use `'camelCase'` (with `namedExport: false`) only if you need both versions available
+
## Version 8.x and Earlier Behavior
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';
#### 3. Handle Kebab-Case Class Names
-With v9's `exportLocalsConvention: 'camelCase'`, kebab-case class names are automatically converted:
+**Option A: Use camelCase (v9 default)**
+
+With `exportLocalsConvention: 'camelCaseOnly'`, kebab-case class names are automatically converted:
```css
/* styles.module.css */
@@ -253,13 +304,35 @@ With v9's `exportLocalsConvention: 'camelCase'`, kebab-case class names are auto
```
```js
-// v9 imports (camelCase conversion)
+// v9 default - camelCase conversion
import { myButton, primaryColor } from './styles.module.css';
+
+```
-// Use the camelCase versions in your components
+**Option B: Keep kebab-case with 'dashesOnly'**
+
+If you prefer to preserve the original kebab-case names, configure your webpack to use `'dashesOnly'`:
+
+```js
+// config/webpack/commonWebpackConfig.js
+modules: {
+ namedExport: true,
+ exportLocalsConvention: 'dashesOnly'
+}
+```
+
+```js
+// With dashesOnly - preserve kebab-case
+import * as styles from './styles.module.css';
+
+
+// Or with aliasing:
+import { 'my-button': myButton } from './styles.module.css';
```
+**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'`.
+
#### 4. Using a Codemod for Large Codebases
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/
## Version Comparison
-| Feature | v8 (and earlier) | v9 |
-|---------|-----------------|----|
+| Feature | v8 (and earlier) | v9 |
+|---------|-----------------|----|
| Default behavior | Default export object | Named exports |
| Import syntax | `import styles from '...'` | `import { className } from '...'` |
| Class reference | `styles.className` | `className` |
-| Export convention | `asIs` (no transformation) | `camelCase` |
+| Export convention | `asIs` (no transformation) | `camelCaseOnly` |
| TypeScript warnings | May show warnings | No warnings |
| Tree-shaking | Limited | Optimized |
@@ -368,13 +441,34 @@ Then search for `css-loader` options in the generated JSON file.
## Troubleshooting
+### Build Error: exportLocalsConvention Incompatible with namedExport
+
+If you see this error during build:
+```
+"exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option
+```
+
+**Cause:** Your webpack configuration has `namedExport: true` with `exportLocalsConvention: 'camelCase'`.
+
+**Solution:** Change `exportLocalsConvention` to `'camelCaseOnly'` or `'dashesOnly'`:
+
+```js
+// config/webpack/commonWebpackConfig.js or similar
+modules: {
+ namedExport: true,
+ exportLocalsConvention: 'camelCaseOnly' // or 'dashesOnly'
+}
+```
+
+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.
+
### CSS Classes Not Applying
If your CSS classes aren't applying after the upgrade:
1. **Check import syntax**: Ensure you're using the correct import style for your configuration
2. **Verify class names**: Use `console.log` to see available classes
-3. **Check camelCase conversion**: Kebab-case names are converted to camelCase in v9
+3. **Check camelCase conversion**: Kebab-case names are converted to camelCase in v9 with `'camelCaseOnly'`
4. **Rebuild webpack**: Clear cache and rebuild: `rm -rf tmp/cache && bin/shakapacker`
### TypeScript Support
@@ -415,4 +509,4 @@ The configuration changes should not impact build performance significantly. If
- **v8 default**: Default export object with no conversion
- **Migration path**: Update imports or override configuration
- **Benefits of v9**: No warnings, better tree-shaking, explicit dependencies
-- **Keeping v8 behavior**: Override css-loader configuration as shown above
\ No newline at end of file
+- **Keeping v8 behavior**: Override css-loader configuration as shown above
diff --git a/docs/v9_upgrade.md b/docs/v9_upgrade.md
index 446742e72..d2a1218e1 100644
--- a/docs/v9_upgrade.md
+++ b/docs/v9_upgrade.md
@@ -19,7 +19,18 @@ See the [TypeScript Documentation](./typescript.md) for usage examples.
### 1. CSS Modules Configuration Changed to Named Exports
-**What changed:** CSS Modules are now configured with `namedExport: true` and `exportLocalsConvention: 'camelCase'` by default, aligning with Next.js and modern tooling standards.
+**What changed:** CSS Modules are now configured with `namedExport: true` and `exportLocalsConvention: 'camelCaseOnly'` by default, aligning with Next.js and modern tooling standards.
+
+> **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`.
+
+**Quick Reference: Configuration Options**
+
+| Configuration | namedExport | exportLocalsConvention | CSS: `.my-button` | Export Available | Works With |
+|---------------|-------------|------------------------|-------------------|------------------|------------|
+| **v9 Default** | `true` | `'camelCaseOnly'` | `.my-button` | `myButton` only | ✅ Named exports |
+| **Alternative** | `true` | `'dashesOnly'` | `.my-button` | `'my-button'` only | ✅ Named exports |
+| **v8 Style** | `false` | `'camelCase'` | `.my-button` | Both `myButton` AND `'my-button'` | ✅ Default export |
+| **❌ Invalid** | `true` | `'camelCase'` | - | - | ❌ Build Error |
**JavaScript Projects:**
```js
@@ -198,7 +209,7 @@ declare module '*.module.css' {
### Step 3: Handle Kebab-Case Class Names
-v9 automatically converts kebab-case to camelCase:
+v9 automatically converts kebab-case to camelCase with `exportLocalsConvention: 'camelCaseOnly'`:
```css
/* styles.module.css */
@@ -207,10 +218,34 @@ v9 automatically converts kebab-case to camelCase:
```
```js
-// v9 imports
+// v9 default - camelCase conversion
import { myButton, primaryColor } from './styles.module.css';
```
+**Alternative: Keep kebab-case names with 'dashesOnly'**
+
+If you prefer to keep kebab-case names in JavaScript, you can override the configuration to use `'dashesOnly'`:
+
+```js
+// config/webpack/commonWebpackConfig.js
+modules: {
+ namedExport: true,
+ exportLocalsConvention: 'dashesOnly' // Keep original kebab-case names
+}
+```
+
+Then use the original kebab-case names in your imports:
+
+```js
+// With dashesOnly configuration
+import { 'my-button': myButton, 'primary-color': primaryColor } from './styles.module.css';
+// or access as properties
+import * as styles from './styles.module.css';
+const buttonClass = styles['my-button'];
+```
+
+**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.
+
### Step 4: Update Configuration Files
If you have `webpack_loader` in your configuration:
@@ -253,6 +288,25 @@ Update your global type definitions as shown in Step 2.
If you see warnings about CSS module exports, ensure you've updated all imports to use named exports or have properly configured the override.
+### Build Error: exportLocalsConvention Incompatible with namedExport
+
+If you see this error:
+```
+"exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option
+```
+
+This means your webpack configuration has `namedExport: true` with `exportLocalsConvention: 'camelCase'`. The fix is to change to `'camelCaseOnly'` or `'dashesOnly'`:
+
+```js
+// config/webpack/commonWebpackConfig.js or wherever you configure css-loader
+modules: {
+ namedExport: true,
+ exportLocalsConvention: 'camelCaseOnly' // or 'dashesOnly'
+}
+```
+
+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.
+
### Unexpected Peer Dependency Warnings After Upgrade
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.
diff --git a/lib/shakapacker/doctor.rb b/lib/shakapacker/doctor.rb
index 1a075c1a4..3ab4b5fb4 100644
--- a/lib/shakapacker/doctor.rb
+++ b/lib/shakapacker/doctor.rb
@@ -43,6 +43,7 @@ def perform_checks
# Dependency checks
check_javascript_transpiler_dependencies if config_exists?
check_css_dependencies
+ check_css_modules_configuration
check_bundler_dependencies if config_exists?
check_file_type_dependencies if config_exists?
check_sri_dependencies if config_exists?
@@ -440,6 +441,77 @@ def check_css_dependencies
check_optional_dependency("mini-css-extract-plugin", @warnings, "CSS extraction")
end
+ def check_css_modules_configuration
+ # Check for CSS module files in the project
+ return unless config_exists?
+
+ source_path = config.source_path
+ return unless source_path.exist?
+
+ # Performance optimization: Just check if ANY CSS module file exists
+ # Using .first with early return is much faster than globbing all files
+ css_module_exists = Dir.glob(File.join(source_path, "**/*.module.{css,scss,sass}")).first
+ return unless css_module_exists
+
+ # Check webpack configuration for CSS modules settings
+ webpack_config_paths = [
+ root_path.join("config/webpack/webpack.config.js"),
+ root_path.join("config/webpack/webpack.config.ts"),
+ root_path.join("config/webpack/commonWebpackConfig.js"),
+ root_path.join("config/webpack/commonWebpackConfig.ts")
+ ]
+
+ webpack_config_paths.each do |config_path|
+ next unless config_path.exist?
+
+ config_content = File.read(config_path)
+
+ # Check for the invalid configuration: namedExport: true with exportLocalsConvention: 'camelCase'
+ if config_content.match(/namedExport\s*:\s*true/) && config_content.match(/exportLocalsConvention\s*:\s*['"]camelCase['"]/)
+ @issues << "CSS Modules: Invalid configuration detected in #{config_path.relative_path_from(root_path)}"
+ @issues << " Using exportLocalsConvention: 'camelCase' with namedExport: true will cause build errors"
+ @issues << " Change to 'camelCaseOnly' or 'dashesOnly'. See docs/v9_upgrade.md for details"
+ end
+
+ # Warn if CSS modules are used but no configuration is found
+ if !config_content.match(/namedExport/) && !config_content.match(/exportLocalsConvention/)
+ @info << "CSS module files found but no explicit CSS modules configuration detected"
+ @info << " v9 defaults: namedExport: true, exportLocalsConvention: 'camelCaseOnly'"
+ end
+ end
+
+ # Check for common v8 to v9 migration issues
+ check_css_modules_import_patterns
+ rescue => e
+ # Don't fail doctor if CSS modules check has issues
+ @warnings << "Unable to validate CSS modules configuration: #{e.message}"
+ end
+
+ def check_css_modules_import_patterns
+ # Look for JavaScript/TypeScript files that might have v8-style imports
+ source_path = config.source_path
+
+ # Use lazy evaluation with Enumerator to avoid loading all file paths into memory
+ # Stop after checking 50 files or finding a match
+ v8_pattern = /import\s+\w+\s+from\s+['"][^'"]*\.module\.(css|scss|sass)['"]/
+
+ Dir.glob(File.join(source_path, "**/*.{js,jsx,ts,tsx}")).lazy.take(50).each do |file|
+ # Read file and check for v8 pattern
+ content = File.read(file)
+
+ # Check for v8 default import pattern with .module.css
+ if v8_pattern.match?(content)
+ @warnings << "Potential v8-style CSS module imports detected (using default import)"
+ @warnings << " v9 uses named exports. Update to: import { className } from './styles.module.css'"
+ @warnings << " Or use: import * as styles from './styles.module.css' (TypeScript)"
+ @warnings << " See docs/v9_upgrade.md for migration guide"
+ break # Stop after finding first occurrence
+ end
+ end
+ rescue => e
+ # Don't fail doctor if import pattern check has issues
+ end
+
def check_bundler_dependencies
bundler = config.assets_bundler
case bundler
diff --git a/package/utils/getStyleRule.ts b/package/utils/getStyleRule.ts
index 216f4b60d..51565fa23 100644
--- a/package/utils/getStyleRule.ts
+++ b/package/utils/getStyleRule.ts
@@ -33,7 +33,12 @@ const getStyleRule = (test: RegExp, preprocessors: any[] = []): StyleRule | null
sourceMap: true,
importLoaders: 2,
modules: {
- auto: true
+ auto: true,
+ // v9 defaults: Use named exports with camelCase conversion
+ // Note: css-loader requires 'camelCaseOnly' or 'dashesOnly' when namedExport is true
+ // Using 'camelCase' with namedExport: true causes a build error
+ namedExport: true,
+ exportLocalsConvention: 'camelCaseOnly'
}
}
},
@@ -56,4 +61,4 @@ const getStyleRule = (test: RegExp, preprocessors: any[] = []): StyleRule | null
return null
}
-export = { getStyleRule }
\ No newline at end of file
+export = { getStyleRule }
diff --git a/spec/shakapacker/css_modules_spec.rb b/spec/shakapacker/css_modules_spec.rb
index 150818fb1..a6e5219ba 100644
--- a/spec/shakapacker/css_modules_spec.rb
+++ b/spec/shakapacker/css_modules_spec.rb
@@ -141,6 +141,58 @@
# Test validation for kebab-case issues
skip "Integration test - requires Node.js environment to test validation"
end
+
+ it "validates that 'camelCase' is incompatible with namedExport: true" do
+ # This test documents the exact error that would occur with incorrect configuration
+ # css-loader will reject this configuration with an error
+
+ invalid_config = {
+ namedExport: true,
+ exportLocalsConvention: "camelCase"
+ }
+
+ # The configuration itself is invalid
+ expect(invalid_config[:namedExport]).to eq(true)
+ expect(invalid_config[:exportLocalsConvention]).to eq("camelCase")
+
+ # This combination would cause css-loader to throw:
+ # "exportLocalsConvention" with "camelCase" value is incompatible with "namedExport: true" option
+
+ # Document the valid alternatives
+ valid_configs = [
+ { namedExport: true, exportLocalsConvention: "camelCaseOnly" },
+ { namedExport: true, exportLocalsConvention: "dashesOnly" },
+ { namedExport: false, exportLocalsConvention: "camelCase" }
+ ]
+
+ valid_configs.each do |config|
+ if config[:namedExport]
+ # With namedExport true, only camelCaseOnly or dashesOnly allowed
+ expect(["camelCaseOnly", "dashesOnly"]).to include(config[:exportLocalsConvention])
+ else
+ # With namedExport false, camelCase is allowed
+ expect(config[:exportLocalsConvention]).to eq("camelCase")
+ end
+ end
+ end
+
+ it "ensures getStyleRule.ts uses valid configuration" do
+ # This test validates that our actual implementation uses a valid combination
+ # Read the TypeScript source to verify configuration
+ style_rule_content = File.read("package/utils/getStyleRule.ts")
+
+ # Should have namedExport: true
+ expect(style_rule_content).to include("namedExport: true")
+
+ # Should have exportLocalsConvention: 'camelCaseOnly' (not 'camelCase')
+ expect(style_rule_content).to include("exportLocalsConvention: 'camelCaseOnly'")
+
+ # Should NOT have the invalid 'camelCase' with namedExport: true
+ expect(style_rule_content).not_to include("exportLocalsConvention: 'camelCase'")
+
+ # Should have explanatory comment about the requirement
+ expect(style_rule_content).to include("css-loader requires 'camelCaseOnly' or 'dashesOnly'")
+ end
end
describe "migration scenarios" do
@@ -186,14 +238,14 @@
# Both modes should work, just with different syntax
configs = [
{ namedExport: false, description: "v8 mode" },
- { namedExport: true, exportLocalsConvention: "camelCase", description: "v9 mode" }
+ { namedExport: true, exportLocalsConvention: "camelCaseOnly", description: "v9 mode" }
]
configs.each do |config|
expect(config[:description]).to match(/v\d mode/)
if config[:namedExport]
- expect(config[:exportLocalsConvention]).to eq("camelCase")
+ expect(config[:exportLocalsConvention]).to eq("camelCaseOnly")
else
expect(config[:exportLocalsConvention]).to be_nil
end
diff --git a/spec/shakapacker/doctor_spec.rb b/spec/shakapacker/doctor_spec.rb
index ce9ee810f..7a6045ba5 100644
--- a/spec/shakapacker/doctor_spec.rb
+++ b/spec/shakapacker/doctor_spec.rb
@@ -991,4 +991,141 @@
end
end
end
+
+ describe "CSS modules configuration checks" do
+ let(:webpack_config_path) { root_path.join("config/webpack/webpack.config.js") }
+
+ before do
+ File.write(config_path, "test: config")
+ FileUtils.mkdir_p(source_path)
+ end
+
+ context "when no CSS module files exist" do
+ it "skips the check" do
+ doctor.send(:check_css_modules_configuration)
+ expect(doctor.issues).to be_empty
+ expect(doctor.warnings).to be_empty
+ end
+ end
+
+ context "when CSS module files exist" do
+ before do
+ File.write(source_path.join("styles.module.css"), ".button { color: red; }")
+ end
+
+ context "with invalid configuration (namedExport: true + camelCase)" do
+ before do
+ FileUtils.mkdir_p(webpack_config_path.dirname)
+ webpack_config = <<~JS
+ module.exports = {
+ modules: {
+ namedExport: true,
+ exportLocalsConvention: 'camelCase'
+ }
+ };
+ JS
+ File.write(webpack_config_path, webpack_config)
+ end
+
+ it "adds critical issue about invalid configuration" do
+ doctor.send(:check_css_modules_configuration)
+ expect(doctor.issues).to include(match(/CSS Modules: Invalid configuration detected/))
+ expect(doctor.issues).to include(match(/exportLocalsConvention: 'camelCase' with namedExport: true/))
+ expect(doctor.issues).to include(match(/Change to 'camelCaseOnly' or 'dashesOnly'/))
+ end
+ end
+
+ context "with valid configuration (namedExport: true + camelCaseOnly)" do
+ before do
+ FileUtils.mkdir_p(webpack_config_path.dirname)
+ webpack_config = <<~JS
+ module.exports = {
+ modules: {
+ namedExport: true,
+ exportLocalsConvention: 'camelCaseOnly'
+ }
+ };
+ JS
+ File.write(webpack_config_path, webpack_config)
+ end
+
+ it "does not add issues" do
+ doctor.send(:check_css_modules_configuration)
+ expect(doctor.issues).to be_empty
+ end
+ end
+
+ context "with valid configuration (namedExport: true + dashesOnly)" do
+ before do
+ FileUtils.mkdir_p(webpack_config_path.dirname)
+ webpack_config = <<~JS
+ module.exports = {
+ modules: {
+ namedExport: true,
+ exportLocalsConvention: 'dashesOnly'
+ }
+ };
+ JS
+ File.write(webpack_config_path, webpack_config)
+ end
+
+ it "does not add issues" do
+ doctor.send(:check_css_modules_configuration)
+ expect(doctor.issues).to be_empty
+ end
+ end
+
+ context "without explicit CSS modules configuration" do
+ before do
+ FileUtils.mkdir_p(webpack_config_path.dirname)
+ webpack_config = <<~JS
+ module.exports = {
+ entry: './app.js'
+ };
+ JS
+ File.write(webpack_config_path, webpack_config)
+ end
+
+ it "adds info about default v9 configuration" do
+ doctor.send(:check_css_modules_configuration)
+ expect(doctor.info).to include(match(/CSS module files found but no explicit CSS modules configuration/))
+ expect(doctor.info).to include(match(/v9 defaults: namedExport: true, exportLocalsConvention: 'camelCaseOnly'/))
+ end
+ end
+
+ context "with v8-style import patterns" do
+ before do
+ js_file = source_path.join("component.jsx")
+ js_content = <<~JS
+ import styles from './styles.module.css';
+ export const Button = () => ;
+ JS
+ File.write(js_file, js_content)
+ end
+
+ it "warns about v8-style imports" do
+ doctor.send(:check_css_modules_configuration)
+ expect(doctor.warnings).to include(match(/Potential v8-style CSS module imports detected/))
+ expect(doctor.warnings).to include(match(/v9 uses named exports/))
+ expect(doctor.warnings).to include(match(/See docs\/v9_upgrade.md for migration guide/))
+ end
+ end
+
+ context "with v9-style import patterns" do
+ before do
+ js_file = source_path.join("component.jsx")
+ js_content = <<~JS
+ import { button } from './styles.module.css';
+ export const Button = () => ;
+ JS
+ File.write(js_file, js_content)
+ end
+
+ it "does not warn about imports" do
+ doctor.send(:check_css_modules_configuration)
+ expect(doctor.warnings).not_to include(match(/v8-style CSS module imports/))
+ end
+ end
+ end
+ end
end
diff --git a/tools/README.md b/tools/README.md
index 2dcd1b653..e92ab8ddb 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -104,7 +104,7 @@ const Button: React.FC = () => {
### Notes
-1. **Kebab-case conversion**: CSS classes with kebab-case (e.g., `my-button`) are automatically converted to camelCase (`myButton`) for JavaScript files, matching css-loader's `exportLocalsConvention: 'camelCase'` setting.
+1. **Kebab-case conversion**: CSS classes with kebab-case (e.g., `my-button`) are automatically converted to camelCase (`myButton`) for JavaScript files, matching css-loader's `exportLocalsConvention: 'camelCaseOnly'` setting.
2. **Unused imports**: The codemod only imports CSS classes that are actually used in JavaScript files. If you pass the entire styles object to a component, it will convert to namespace import for safety.
@@ -121,4 +121,4 @@ const Button: React.FC = () => {
**Solution**: Ensure your TypeScript definitions are updated as shown in the [v9 Upgrade Guide](../docs/v9_upgrade.md).
**Issue**: Runtime errors about missing CSS classes
-**Solution**: Check if you have kebab-case class names that need camelCase conversion.
\ No newline at end of file
+**Solution**: Check if you have kebab-case class names that need camelCase conversion.