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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +54 -0
  3. data/.github/workflows/claude.yml +50 -0
  4. data/.github/workflows/dummy.yml +3 -3
  5. data/.github/workflows/test-bundlers.yml +152 -0
  6. data/.rubocop.yml +1 -0
  7. data/CHANGELOG.md +15 -3
  8. data/CLAUDE.md +29 -0
  9. data/Gemfile.lock +1 -1
  10. data/README.md +42 -1
  11. data/Rakefile +39 -4
  12. data/TODO.md +51 -0
  13. data/TODO_v9.md +84 -0
  14. data/conductor-setup.sh +58 -0
  15. data/conductor.json +7 -0
  16. data/docs/cdn_setup.md +379 -0
  17. data/docs/css-modules-export-mode.md +216 -86
  18. data/docs/deployment.md +10 -1
  19. data/docs/rspack.md +7 -7
  20. data/docs/rspack_migration_guide.md +202 -0
  21. data/docs/using_esbuild_loader.md +3 -3
  22. data/docs/using_swc_loader.md +5 -3
  23. data/docs/v6_upgrade.md +10 -0
  24. data/docs/v9_upgrade.md +185 -0
  25. data/lib/install/bin/shakapacker +3 -17
  26. data/lib/install/config/shakapacker.yml +9 -4
  27. data/lib/shakapacker/configuration.rb +75 -3
  28. data/lib/shakapacker/dev_server_runner.rb +19 -9
  29. data/lib/shakapacker/manifest.rb +4 -3
  30. data/lib/shakapacker/rspack_runner.rb +4 -42
  31. data/lib/shakapacker/runner.rb +105 -11
  32. data/lib/shakapacker/utils/manager.rb +2 -0
  33. data/lib/shakapacker/version.rb +1 -1
  34. data/lib/shakapacker/version_checker.rb +1 -1
  35. data/lib/shakapacker/webpack_runner.rb +4 -42
  36. data/lib/tasks/shakapacker/install.rake +6 -2
  37. data/package/config.js +24 -0
  38. data/package/environments/base.js +12 -2
  39. data/package/environments/development.js +52 -12
  40. data/package/environments/production.js +8 -3
  41. data/package/environments/test.js +5 -3
  42. data/package/index.d.ts +69 -30
  43. data/package/index.js +1 -1
  44. data/package/optimization/rspack.js +9 -5
  45. data/package/plugins/rspack.js +12 -28
  46. data/package/rspack/index.js +57 -0
  47. data/package/rules/babel.js +2 -2
  48. data/package/rules/esbuild.js +2 -2
  49. data/package/rules/raw.js +5 -5
  50. data/package/rules/rspack.js +77 -7
  51. data/package/rules/swc.js +2 -2
  52. data/package/utils/debug.js +49 -0
  53. data/package/utils/getStyleRule.js +19 -10
  54. data/package/utils/requireOrError.js +1 -1
  55. data/package/utils/validateCssModulesConfig.js +91 -0
  56. data/package/utils/validateDependencies.js +61 -0
  57. data/package/webpackDevServerConfig.js +2 -0
  58. data/package-lock.json +11966 -0
  59. data/package.json +9 -2
  60. data/test/package/rules/esbuild.test.js +1 -1
  61. data/test/package/rules/swc.test.js +1 -1
  62. data/tools/README.md +124 -0
  63. data/tools/css-modules-v9-codemod.js +179 -0
  64. data/yarn.lock +199 -81
  65. metadata +20 -3
  66. 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.0",
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.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
- webpack_loader: "esbuild",
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
- webpack_loader: "swc",
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"