shakapacker 9.0.0.beta.2 ā 9.0.0.beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/dummy.yml +3 -3
- data/.github/workflows/test-bundlers.yml +152 -0
- data/CHANGELOG.md +31 -2
- data/CLAUDE.md +29 -0
- data/Gemfile.lock +1 -1
- data/README.md +42 -1
- data/Rakefile +25 -7
- data/TODO.md +51 -0
- data/TODO_v9.md +84 -0
- data/conductor-setup.sh +58 -0
- data/conductor.json +7 -0
- data/docs/cdn_setup.md +379 -0
- data/docs/css-modules-export-mode.md +216 -86
- data/docs/deployment.md +10 -1
- data/docs/peer-dependencies.md +23 -3
- data/docs/transpiler-performance.md +179 -0
- data/docs/v6_upgrade.md +10 -0
- data/docs/v9_upgrade.md +228 -0
- data/lib/install/config/shakapacker.yml +8 -2
- data/lib/install/package.json +8 -0
- data/lib/install/template.rb +121 -51
- data/lib/shakapacker/configuration.rb +39 -0
- data/lib/shakapacker/version.rb +1 -1
- data/package/config.js +2 -2
- data/package/index.d.ts +12 -1
- data/package/rules/rspack.js +4 -0
- data/package/utils/getStyleRule.js +16 -7
- data/package/utils/helpers.js +66 -1
- data/package/utils/validateCssModulesConfig.js +91 -0
- data/package.json +9 -2
- data/test/package/rules/babel.test.js +16 -0
- data/tools/README.md +124 -0
- data/tools/css-modules-v9-codemod.js +179 -0
- data/yarn.lock +203 -85
- metadata +14 -2
data/package/utils/helpers.js
CHANGED
@@ -25,6 +25,46 @@ const canProcess = (rule, fn) => {
|
|
25
25
|
return null
|
26
26
|
}
|
27
27
|
|
28
|
+
const validateBabelDependencies = () => {
|
29
|
+
// Only validate core dependencies that are absolutely required
|
30
|
+
// Additional packages like presets are optional and project-specific
|
31
|
+
const coreRequiredPackages = ["@babel/core", "babel-loader"]
|
32
|
+
|
33
|
+
const missingCorePackages = coreRequiredPackages.filter(
|
34
|
+
(pkg) => !moduleExists(pkg)
|
35
|
+
)
|
36
|
+
|
37
|
+
if (missingCorePackages.length > 0) {
|
38
|
+
const installCommand = `npm install --save-dev ${missingCorePackages.join(" ")}`
|
39
|
+
|
40
|
+
// Check for commonly needed packages and suggest them
|
41
|
+
const suggestedPackages = [
|
42
|
+
"@babel/preset-env",
|
43
|
+
"@babel/plugin-transform-runtime",
|
44
|
+
"@babel/runtime"
|
45
|
+
]
|
46
|
+
|
47
|
+
const missingSuggested = suggestedPackages.filter(
|
48
|
+
(pkg) => !moduleExists(pkg)
|
49
|
+
)
|
50
|
+
let additionalHelp = ""
|
51
|
+
|
52
|
+
if (missingSuggested.length > 0) {
|
53
|
+
additionalHelp =
|
54
|
+
`\n\nYou may also need: ${missingSuggested.join(", ")}\n` +
|
55
|
+
`Install with: npm install --save-dev ${missingSuggested.join(" ")}`
|
56
|
+
}
|
57
|
+
|
58
|
+
throw new Error(
|
59
|
+
`Babel is configured but core packages are missing: ${missingCorePackages.join(", ")}\n\n` +
|
60
|
+
`To fix this, run:\n ${installCommand}${additionalHelp}\n\n` +
|
61
|
+
`š” Tip: Consider migrating to SWC for 20x faster compilation:\n` +
|
62
|
+
` 1. Set javascript_transpiler: 'swc' in config/shakapacker.yml\n` +
|
63
|
+
` 2. Run: npm install @swc/core swc-loader`
|
64
|
+
)
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
28
68
|
const loaderMatches = (configLoader, loaderToCheck, fn) => {
|
29
69
|
if (configLoader !== loaderToCheck) {
|
30
70
|
return null
|
@@ -32,9 +72,33 @@ const loaderMatches = (configLoader, loaderToCheck, fn) => {
|
|
32
72
|
|
33
73
|
const loaderName = `${configLoader}-loader`
|
34
74
|
|
75
|
+
// Special validation for babel to check all dependencies
|
76
|
+
if (configLoader === "babel") {
|
77
|
+
validateBabelDependencies()
|
78
|
+
}
|
79
|
+
|
35
80
|
if (!moduleExists(loaderName)) {
|
81
|
+
let installCommand = ""
|
82
|
+
let migrationHelp = ""
|
83
|
+
|
84
|
+
if (configLoader === "babel") {
|
85
|
+
installCommand =
|
86
|
+
"npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/runtime"
|
87
|
+
migrationHelp =
|
88
|
+
"\n\nš” Tip: Consider migrating to SWC for 20x faster compilation:\n" +
|
89
|
+
" 1. Set javascript_transpiler: 'swc' in config/shakapacker.yml\n" +
|
90
|
+
" 2. Run: npm install @swc/core swc-loader"
|
91
|
+
} else if (configLoader === "swc") {
|
92
|
+
installCommand = "npm install --save-dev @swc/core swc-loader"
|
93
|
+
migrationHelp =
|
94
|
+
"\n\n⨠SWC is 20x faster than Babel with zero configuration!"
|
95
|
+
} else if (configLoader === "esbuild") {
|
96
|
+
installCommand = "npm install --save-dev esbuild esbuild-loader"
|
97
|
+
}
|
98
|
+
|
36
99
|
throw new Error(
|
37
|
-
`Your Shakapacker config specified using ${configLoader}, but ${loaderName} package is not installed
|
100
|
+
`Your Shakapacker config specified using ${configLoader}, but ${loaderName} package is not installed.\n\n` +
|
101
|
+
`To fix this, run:\n ${installCommand}${migrationHelp}`
|
38
102
|
)
|
39
103
|
}
|
40
104
|
|
@@ -56,6 +120,7 @@ module.exports = {
|
|
56
120
|
ensureTrailingSlash,
|
57
121
|
canProcess,
|
58
122
|
moduleExists,
|
123
|
+
validateBabelDependencies,
|
59
124
|
loaderMatches,
|
60
125
|
packageFullVersion,
|
61
126
|
packageMajorVersion
|
@@ -0,0 +1,91 @@
|
|
1
|
+
/* eslint global-require: 0 */
|
2
|
+
const { warn } = require("./debug")
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Validates CSS modules configuration and warns about potential issues
|
6
|
+
* with v9 defaults or conflicting settings.
|
7
|
+
*/
|
8
|
+
const validateCssModulesConfig = (cssLoaderOptions) => {
|
9
|
+
// Skip validation in production by default for performance
|
10
|
+
if (
|
11
|
+
process.env.NODE_ENV === "production" &&
|
12
|
+
process.env.SHAKAPACKER_VALIDATE_CSS_MODULES !== "true"
|
13
|
+
) {
|
14
|
+
return
|
15
|
+
}
|
16
|
+
|
17
|
+
if (!cssLoaderOptions || !cssLoaderOptions.modules) {
|
18
|
+
return
|
19
|
+
}
|
20
|
+
|
21
|
+
const { modules } = cssLoaderOptions
|
22
|
+
|
23
|
+
// Check for conflicting namedExport and esModule settings
|
24
|
+
if (modules.namedExport === true && modules.esModule === false) {
|
25
|
+
warn(
|
26
|
+
"ā ļø CSS Modules Configuration Warning:\n" +
|
27
|
+
" namedExport: true with esModule: false may cause issues.\n" +
|
28
|
+
" Consider setting esModule: true or removing it (defaults to true)."
|
29
|
+
)
|
30
|
+
}
|
31
|
+
|
32
|
+
// Check for v8-style configuration with v9
|
33
|
+
if (modules.namedExport === false) {
|
34
|
+
warn(
|
35
|
+
"ā¹ļø CSS Modules Configuration Note:\n" +
|
36
|
+
" You are using namedExport: false (v8 behavior).\n" +
|
37
|
+
" Shakapacker v9 defaults to namedExport: true for better tree-shaking.\n" +
|
38
|
+
" See docs/css-modules-export-mode.md for migration instructions."
|
39
|
+
)
|
40
|
+
}
|
41
|
+
|
42
|
+
// Check for inconsistent exportLocalsConvention with namedExport
|
43
|
+
if (
|
44
|
+
modules.namedExport === true &&
|
45
|
+
modules.exportLocalsConvention === "asIs"
|
46
|
+
) {
|
47
|
+
warn(
|
48
|
+
"ā ļø CSS Modules Configuration Warning:\n" +
|
49
|
+
" Using namedExport: true with exportLocalsConvention: 'asIs' may cause issues\n" +
|
50
|
+
" with kebab-case class names (e.g., 'my-button').\n" +
|
51
|
+
" Consider using exportLocalsConvention: 'camelCase' (v9 default)."
|
52
|
+
)
|
53
|
+
}
|
54
|
+
|
55
|
+
// Check for deprecated localIdentName pattern
|
56
|
+
if (
|
57
|
+
modules.localIdentName &&
|
58
|
+
modules.localIdentName.includes("[hash:base64]")
|
59
|
+
) {
|
60
|
+
warn(
|
61
|
+
"ā ļø CSS Modules Configuration Warning:\n" +
|
62
|
+
" [hash:base64] is deprecated in css-loader v6+.\n" +
|
63
|
+
" Use [hash] instead for better compatibility."
|
64
|
+
)
|
65
|
+
}
|
66
|
+
|
67
|
+
// Check for potential TypeScript issues
|
68
|
+
if (
|
69
|
+
modules.namedExport === true &&
|
70
|
+
process.env.SHAKAPACKER_ASSET_COMPILER_TYPESCRIPT === "true"
|
71
|
+
) {
|
72
|
+
warn(
|
73
|
+
"ā¹ļø TypeScript CSS Modules Note:\n" +
|
74
|
+
" With namedExport: true, TypeScript projects should use:\n" +
|
75
|
+
" import * as styles from './styles.module.css'\n" +
|
76
|
+
" instead of: import styles from './styles.module.css'\n" +
|
77
|
+
" See docs/css-modules-export-mode.md for TypeScript setup."
|
78
|
+
)
|
79
|
+
}
|
80
|
+
|
81
|
+
// Check for auto: true with getLocalIdent (potential conflict)
|
82
|
+
if (modules.auto === true && modules.getLocalIdent) {
|
83
|
+
warn(
|
84
|
+
"ā ļø CSS Modules Configuration Warning:\n" +
|
85
|
+
" Using both 'auto: true' and 'getLocalIdent' may cause conflicts.\n" +
|
86
|
+
" The 'auto' option determines which files are treated as CSS modules."
|
87
|
+
)
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
module.exports = { validateCssModulesConfig }
|
data/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "shakapacker",
|
3
|
-
"version": "9.0.0-beta.
|
3
|
+
"version": "9.0.0-beta.4",
|
4
4
|
"description": "Use webpack to manage app-like JavaScript modules in Rails",
|
5
5
|
"homepage": "https://github.com/shakacode/shakapacker",
|
6
6
|
"bugs": {
|
@@ -39,12 +39,18 @@
|
|
39
39
|
"devDependencies": {
|
40
40
|
"@rspack/cli": "^1.4.11",
|
41
41
|
"@rspack/core": "^1.4.11",
|
42
|
+
"@types/js-yaml": "^4.0.9",
|
43
|
+
"@types/node": "^24.5.2",
|
44
|
+
"@types/path-complete-extname": "^1.0.3",
|
45
|
+
"@types/webpack": "^5.28.5",
|
46
|
+
"@types/webpack-dev-server": "^4.7.2",
|
47
|
+
"@types/webpack-merge": "^5.0.0",
|
42
48
|
"babel-loader": "^8.2.4",
|
43
49
|
"compression-webpack-plugin": "^9.0.0",
|
44
50
|
"css-loader": "^7.1.2",
|
45
51
|
"esbuild-loader": "^2.18.0",
|
46
52
|
"eslint": "^8.0.0",
|
47
|
-
"eslint-config-airbnb": "^19.0.
|
53
|
+
"eslint-config-airbnb": "^19.0.4",
|
48
54
|
"eslint-config-prettier": "^9.0.0",
|
49
55
|
"eslint-plugin-import": "^2.31.0",
|
50
56
|
"eslint-plugin-jest": "^27.9.0",
|
@@ -60,6 +66,7 @@
|
|
60
66
|
"sass-loader": "^16.0.5",
|
61
67
|
"swc-loader": "^0.1.15",
|
62
68
|
"thenify": "^3.3.1",
|
69
|
+
"typescript": "^5.9.2",
|
63
70
|
"webpack": "5.93.0",
|
64
71
|
"webpack-assets-manifest": "^5.0.6",
|
65
72
|
"webpack-merge": "^5.8.0",
|
@@ -11,6 +11,7 @@ jest.mock("../../../package/config", () => {
|
|
11
11
|
const original = jest.requireActual("../../../package/config")
|
12
12
|
return {
|
13
13
|
...original,
|
14
|
+
javascript_transpiler: "babel", // Force babel for this test
|
14
15
|
additional_paths: [...original.additional_paths, "node_modules/included"]
|
15
16
|
}
|
16
17
|
})
|
@@ -32,6 +33,21 @@ const createWebpackConfig = (file, use) => ({
|
|
32
33
|
})
|
33
34
|
|
34
35
|
describe("babel", () => {
|
36
|
+
// Mock validateBabelDependencies to avoid actual dependency checking in tests
|
37
|
+
beforeAll(() => {
|
38
|
+
jest.mock("../../../package/utils/helpers", () => {
|
39
|
+
const original = jest.requireActual("../../../package/utils/helpers")
|
40
|
+
return {
|
41
|
+
...original,
|
42
|
+
validateBabelDependencies: jest.fn() // Mock to do nothing
|
43
|
+
}
|
44
|
+
})
|
45
|
+
})
|
46
|
+
|
47
|
+
afterAll(() => {
|
48
|
+
jest.unmock("../../../package/utils/helpers")
|
49
|
+
})
|
50
|
+
|
35
51
|
test("process source path", async () => {
|
36
52
|
const normalPath = `${pathToAppJavascript}/a.js`
|
37
53
|
const [tracked, loader] = createTrackLoader()
|
data/tools/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# Shakapacker v9 Migration Tools
|
2
|
+
|
3
|
+
## CSS Modules Codemod
|
4
|
+
|
5
|
+
A jscodeshift codemod to help migrate CSS module imports from v8 to v9 format.
|
6
|
+
|
7
|
+
### What it does
|
8
|
+
|
9
|
+
#### For JavaScript files (.js, .jsx):
|
10
|
+
- Converts `import styles from './styles.module.css'` to `import { className1, className2 } from './styles.module.css'`
|
11
|
+
- Automatically detects which CSS classes are used in the file
|
12
|
+
- Handles kebab-case to camelCase conversion (e.g., `my-button` ā `myButton`)
|
13
|
+
- Updates all class references from `styles.className` to `className`
|
14
|
+
|
15
|
+
#### For TypeScript files (.ts, .tsx):
|
16
|
+
- Converts `import styles from './styles.module.css'` to `import * as styles from './styles.module.css'`
|
17
|
+
- Preserves the same usage pattern (`styles.className`)
|
18
|
+
- Works around TypeScript's limitation with dynamic named exports
|
19
|
+
|
20
|
+
### Installation
|
21
|
+
|
22
|
+
```bash
|
23
|
+
npm install -g jscodeshift
|
24
|
+
```
|
25
|
+
|
26
|
+
### Usage
|
27
|
+
|
28
|
+
#### Dry run (see what would change):
|
29
|
+
```bash
|
30
|
+
npx jscodeshift -t tools/css-modules-v9-codemod.js src/ --dry
|
31
|
+
```
|
32
|
+
|
33
|
+
#### Apply to JavaScript files:
|
34
|
+
```bash
|
35
|
+
npx jscodeshift -t tools/css-modules-v9-codemod.js src/
|
36
|
+
```
|
37
|
+
|
38
|
+
#### Apply to TypeScript files:
|
39
|
+
```bash
|
40
|
+
npx jscodeshift -t tools/css-modules-v9-codemod.js --parser tsx src/
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Apply to specific file patterns:
|
44
|
+
```bash
|
45
|
+
# Only .jsx files
|
46
|
+
npx jscodeshift -t tools/css-modules-v9-codemod.js src/**/*.jsx
|
47
|
+
|
48
|
+
# Only .tsx files
|
49
|
+
npx jscodeshift -t tools/css-modules-v9-codemod.js --parser tsx src/**/*.tsx
|
50
|
+
```
|
51
|
+
|
52
|
+
### Options
|
53
|
+
|
54
|
+
- `--dry` - Run without modifying files
|
55
|
+
- `--print` - Print the transformed output
|
56
|
+
- `--parser tsx` - Use TypeScript parser
|
57
|
+
- `--verbose` - Show detailed progress
|
58
|
+
|
59
|
+
### Examples
|
60
|
+
|
61
|
+
#### Before (JavaScript):
|
62
|
+
```javascript
|
63
|
+
import styles from './Button.module.css';
|
64
|
+
|
65
|
+
function Button() {
|
66
|
+
return (
|
67
|
+
<button className={styles.button}>
|
68
|
+
<span className={styles['button-text']}>Click me</span>
|
69
|
+
</button>
|
70
|
+
);
|
71
|
+
}
|
72
|
+
```
|
73
|
+
|
74
|
+
#### After (JavaScript):
|
75
|
+
```javascript
|
76
|
+
import { button, buttonText } from './Button.module.css';
|
77
|
+
|
78
|
+
function Button() {
|
79
|
+
return (
|
80
|
+
<button className={button}>
|
81
|
+
<span className={buttonText}>Click me</span>
|
82
|
+
</button>
|
83
|
+
);
|
84
|
+
}
|
85
|
+
```
|
86
|
+
|
87
|
+
#### Before (TypeScript):
|
88
|
+
```typescript
|
89
|
+
import styles from './Button.module.css';
|
90
|
+
|
91
|
+
const Button: React.FC = () => {
|
92
|
+
return <button className={styles.button}>Click</button>;
|
93
|
+
};
|
94
|
+
```
|
95
|
+
|
96
|
+
#### After (TypeScript):
|
97
|
+
```typescript
|
98
|
+
import * as styles from './Button.module.css';
|
99
|
+
|
100
|
+
const Button: React.FC = () => {
|
101
|
+
return <button className={styles.button}>Click</button>;
|
102
|
+
};
|
103
|
+
```
|
104
|
+
|
105
|
+
### Notes
|
106
|
+
|
107
|
+
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.
|
108
|
+
|
109
|
+
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.
|
110
|
+
|
111
|
+
3. **Manual review recommended**: Always review the changes, especially for complex usage patterns or dynamic class name construction.
|
112
|
+
|
113
|
+
4. **Backup your code**: Run the codemod on version-controlled code or create a backup first.
|
114
|
+
|
115
|
+
### Troubleshooting
|
116
|
+
|
117
|
+
**Issue**: Codemod doesn't detect all CSS class usages
|
118
|
+
**Solution**: For dynamic class names or complex patterns, manual migration may be needed.
|
119
|
+
|
120
|
+
**Issue**: TypeScript errors after transformation
|
121
|
+
**Solution**: Ensure your TypeScript definitions are updated as shown in the [v9 Upgrade Guide](../docs/v9_upgrade.md).
|
122
|
+
|
123
|
+
**Issue**: Runtime errors about missing CSS classes
|
124
|
+
**Solution**: Check if you have kebab-case class names that need camelCase conversion.
|
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Shakapacker v9 CSS Modules Codemod
|
5
|
+
*
|
6
|
+
* This codemod helps migrate CSS module imports from v8 (default exports)
|
7
|
+
* to v9 (named exports for JS, namespace imports for TS).
|
8
|
+
*
|
9
|
+
* Usage:
|
10
|
+
* npx jscodeshift -t tools/css-modules-v9-codemod.js src/
|
11
|
+
* npx jscodeshift -t tools/css-modules-v9-codemod.js --parser tsx src/ (for TypeScript)
|
12
|
+
*
|
13
|
+
* Options:
|
14
|
+
* --dry Run in dry mode (no files modified)
|
15
|
+
* --print Print transformed files to stdout
|
16
|
+
* --parser tsx Use TypeScript parser
|
17
|
+
*/
|
18
|
+
|
19
|
+
module.exports = function transformer(fileInfo, api) {
|
20
|
+
const j = api.jscodeshift
|
21
|
+
const root = j(fileInfo.source)
|
22
|
+
let hasChanges = false
|
23
|
+
|
24
|
+
// Detect if this is a TypeScript file
|
25
|
+
const isTypeScript = fileInfo.path.match(/\.tsx?$/)
|
26
|
+
|
27
|
+
// Find all CSS module imports
|
28
|
+
root
|
29
|
+
.find(j.ImportDeclaration, {
|
30
|
+
source: {
|
31
|
+
value: (value) =>
|
32
|
+
value && value.match(/\.module\.(css|scss|sass|less)$/)
|
33
|
+
}
|
34
|
+
})
|
35
|
+
.forEach((path) => {
|
36
|
+
const importDecl = path.node
|
37
|
+
|
38
|
+
// Check if it's a default import (v8 style)
|
39
|
+
const defaultSpecifier = importDecl.specifiers.find(
|
40
|
+
(spec) => spec.type === "ImportDefaultSpecifier"
|
41
|
+
)
|
42
|
+
|
43
|
+
if (!defaultSpecifier) {
|
44
|
+
// Already using named or namespace imports, skip
|
45
|
+
return
|
46
|
+
}
|
47
|
+
|
48
|
+
const defaultImportName = defaultSpecifier.local.name
|
49
|
+
|
50
|
+
if (isTypeScript) {
|
51
|
+
// For TypeScript: Convert to namespace import (import * as styles)
|
52
|
+
const namespaceSpecifier = j.importNamespaceSpecifier(
|
53
|
+
j.identifier(defaultImportName)
|
54
|
+
)
|
55
|
+
|
56
|
+
// Replace the import specifiers
|
57
|
+
importDecl.specifiers = [namespaceSpecifier]
|
58
|
+
hasChanges = true
|
59
|
+
} else {
|
60
|
+
// For JavaScript: Convert to named imports
|
61
|
+
// First, we need to find all usages of the imported object
|
62
|
+
const usages = new Set()
|
63
|
+
|
64
|
+
// Find all member expressions using the imported default
|
65
|
+
root
|
66
|
+
.find(j.MemberExpression, {
|
67
|
+
object: {
|
68
|
+
type: "Identifier",
|
69
|
+
name: defaultImportName
|
70
|
+
}
|
71
|
+
})
|
72
|
+
.forEach((memberPath) => {
|
73
|
+
// Handle both dot notation (styles.className) and bracket notation (styles['class-name'])
|
74
|
+
if (
|
75
|
+
memberPath.node.computed &&
|
76
|
+
memberPath.node.property.type === "Literal"
|
77
|
+
) {
|
78
|
+
// Computed property access: styles['active-button']
|
79
|
+
const propertyValue = memberPath.node.property.value
|
80
|
+
if (typeof propertyValue === "string") {
|
81
|
+
usages.add(propertyValue)
|
82
|
+
}
|
83
|
+
} else if (
|
84
|
+
!memberPath.node.computed &&
|
85
|
+
memberPath.node.property.type === "Identifier"
|
86
|
+
) {
|
87
|
+
// Dot notation: styles.activeButton
|
88
|
+
const propertyName = memberPath.node.property.name
|
89
|
+
if (propertyName) {
|
90
|
+
usages.add(propertyName)
|
91
|
+
}
|
92
|
+
}
|
93
|
+
})
|
94
|
+
|
95
|
+
if (usages.size > 0) {
|
96
|
+
// Create named import specifiers
|
97
|
+
const namedSpecifiers = Array.from(usages)
|
98
|
+
.sort()
|
99
|
+
.map((name) => {
|
100
|
+
// Handle kebab-case to camelCase conversion
|
101
|
+
const camelCaseName = name.replace(/-([a-z])/g, (g) =>
|
102
|
+
g[1].toUpperCase()
|
103
|
+
)
|
104
|
+
|
105
|
+
if (camelCaseName !== name) {
|
106
|
+
// If conversion happened, we need to alias it
|
107
|
+
return j.importSpecifier(
|
108
|
+
j.identifier(camelCaseName),
|
109
|
+
j.identifier(camelCaseName) // css-loader exports it as camelCase
|
110
|
+
)
|
111
|
+
}
|
112
|
+
|
113
|
+
return j.importSpecifier(j.identifier(name))
|
114
|
+
})
|
115
|
+
|
116
|
+
// Replace the import specifiers
|
117
|
+
importDecl.specifiers = namedSpecifiers
|
118
|
+
|
119
|
+
// Update all usages in the file
|
120
|
+
root
|
121
|
+
.find(j.MemberExpression, {
|
122
|
+
object: {
|
123
|
+
type: "Identifier",
|
124
|
+
name: defaultImportName
|
125
|
+
}
|
126
|
+
})
|
127
|
+
.forEach((memberPath) => {
|
128
|
+
let propertyName
|
129
|
+
|
130
|
+
// Extract property name from both computed and dot notation
|
131
|
+
if (
|
132
|
+
memberPath.node.computed &&
|
133
|
+
memberPath.node.property.type === "Literal"
|
134
|
+
) {
|
135
|
+
propertyName = memberPath.node.property.value
|
136
|
+
} else if (
|
137
|
+
!memberPath.node.computed &&
|
138
|
+
memberPath.node.property.type === "Identifier"
|
139
|
+
) {
|
140
|
+
propertyName = memberPath.node.property.name
|
141
|
+
}
|
142
|
+
|
143
|
+
if (propertyName && typeof propertyName === "string") {
|
144
|
+
// Convert kebab-case to camelCase
|
145
|
+
const camelCaseName = propertyName.replace(/-([a-z])/g, (g) =>
|
146
|
+
g[1].toUpperCase()
|
147
|
+
)
|
148
|
+
// Replace with direct identifier
|
149
|
+
j(memberPath).replaceWith(j.identifier(camelCaseName))
|
150
|
+
}
|
151
|
+
})
|
152
|
+
|
153
|
+
hasChanges = true
|
154
|
+
} else if (usages.size === 0) {
|
155
|
+
// No usages found, might be passed as a whole object
|
156
|
+
// In this case, convert to namespace import for safety
|
157
|
+
const namespaceSpecifier = j.importNamespaceSpecifier(
|
158
|
+
j.identifier(defaultImportName)
|
159
|
+
)
|
160
|
+
|
161
|
+
importDecl.specifiers = [namespaceSpecifier]
|
162
|
+
hasChanges = true
|
163
|
+
}
|
164
|
+
}
|
165
|
+
})
|
166
|
+
|
167
|
+
if (!hasChanges) {
|
168
|
+
return null // No changes made
|
169
|
+
}
|
170
|
+
|
171
|
+
return root.toSource({
|
172
|
+
quote: "single",
|
173
|
+
trailingComma: true,
|
174
|
+
tabWidth: 2
|
175
|
+
})
|
176
|
+
}
|
177
|
+
|
178
|
+
// Export the parser to use
|
179
|
+
module.exports.parser = "tsx"
|