shakapacker 9.0.0.beta.0 → 9.0.0.beta.3
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/claude-code-review.yml +54 -0
- data/.github/workflows/claude.yml +50 -0
- data/.github/workflows/dummy.yml +3 -3
- data/.github/workflows/test-bundlers.yml +152 -0
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +15 -3
- data/CLAUDE.md +29 -0
- data/Gemfile.lock +1 -1
- data/README.md +42 -1
- data/Rakefile +39 -4
- 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/rspack.md +7 -7
- data/docs/rspack_migration_guide.md +202 -0
- data/docs/using_esbuild_loader.md +3 -3
- data/docs/using_swc_loader.md +5 -3
- data/docs/v6_upgrade.md +10 -0
- data/docs/v9_upgrade.md +185 -0
- data/lib/install/bin/shakapacker +3 -17
- data/lib/install/config/shakapacker.yml +9 -4
- data/lib/shakapacker/configuration.rb +75 -3
- data/lib/shakapacker/dev_server_runner.rb +19 -9
- data/lib/shakapacker/manifest.rb +4 -3
- data/lib/shakapacker/rspack_runner.rb +4 -42
- data/lib/shakapacker/runner.rb +105 -11
- data/lib/shakapacker/utils/manager.rb +2 -0
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/version_checker.rb +1 -1
- data/lib/shakapacker/webpack_runner.rb +4 -42
- data/lib/tasks/shakapacker/install.rake +6 -2
- data/package/config.js +24 -0
- data/package/environments/base.js +12 -2
- data/package/environments/development.js +52 -12
- data/package/environments/production.js +8 -3
- data/package/environments/test.js +5 -3
- data/package/index.d.ts +69 -30
- data/package/index.js +1 -1
- data/package/optimization/rspack.js +9 -5
- data/package/plugins/rspack.js +12 -28
- data/package/rspack/index.js +57 -0
- data/package/rules/babel.js +2 -2
- data/package/rules/esbuild.js +2 -2
- data/package/rules/raw.js +5 -5
- data/package/rules/rspack.js +77 -7
- data/package/rules/swc.js +2 -2
- data/package/utils/debug.js +49 -0
- data/package/utils/getStyleRule.js +19 -10
- data/package/utils/requireOrError.js +1 -1
- data/package/utils/validateCssModulesConfig.js +91 -0
- data/package/utils/validateDependencies.js +61 -0
- data/package/webpackDevServerConfig.js +2 -0
- data/package-lock.json +11966 -0
- data/package.json +9 -2
- data/test/package/rules/esbuild.test.js +1 -1
- data/test/package/rules/swc.test.js +1 -1
- data/tools/README.md +124 -0
- data/tools/css-modules-v9-codemod.js +179 -0
- data/yarn.lock +199 -81
- metadata +20 -3
- data/lib/install/bin/shakapacker-rspack +0 -13
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.3",
|
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,7 +11,7 @@ jest.mock("../../../package/config", () => {
|
|
11
11
|
const original = jest.requireActual("../../../package/config")
|
12
12
|
return {
|
13
13
|
...original,
|
14
|
-
|
14
|
+
javascript_transpiler: "esbuild",
|
15
15
|
additional_paths: [...original.additional_paths, "node_modules/included"]
|
16
16
|
}
|
17
17
|
})
|
@@ -11,7 +11,7 @@ jest.mock("../../../package/config", () => {
|
|
11
11
|
const original = jest.requireActual("../../../package/config")
|
12
12
|
return {
|
13
13
|
...original,
|
14
|
-
|
14
|
+
javascript_transpiler: "swc",
|
15
15
|
additional_paths: [...original.additional_paths, "node_modules/included"]
|
16
16
|
}
|
17
17
|
})
|
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"
|