shakapacker 8.4.0 → 9.7.0

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 (265) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/address-review.md +206 -0
  3. data/.claude/commands/update-changelog.md +354 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
  6. data/.github/STATUS.md +1 -0
  7. data/.github/actionlint-matcher.json +17 -0
  8. data/.github/workflows/claude-code-review.yml +45 -0
  9. data/.github/workflows/claude.yml +55 -0
  10. data/.github/workflows/dummy.yml +18 -5
  11. data/.github/workflows/eslint-validation.yml +46 -0
  12. data/.github/workflows/generator.yml +38 -22
  13. data/.github/workflows/node.yml +116 -2
  14. data/.github/workflows/ruby.yml +57 -15
  15. data/.github/workflows/test-bundlers.yml +180 -0
  16. data/.gitignore +27 -0
  17. data/.husky/pre-commit +2 -0
  18. data/.npmignore +56 -0
  19. data/.prettierignore +7 -0
  20. data/.rubocop.yml +2 -0
  21. data/.yalcignore +26 -0
  22. data/CHANGELOG.md +487 -19
  23. data/CLAUDE.md +63 -0
  24. data/CONTRIBUTING.md +268 -21
  25. data/ESLINT_TECHNICAL_DEBT.md +165 -0
  26. data/README.md +497 -137
  27. data/Rakefile +44 -4
  28. data/TODO.md +58 -0
  29. data/TODO_v9.md +97 -0
  30. data/bin/conductor-exec +24 -0
  31. data/bin/shakapacker-config +11 -0
  32. data/conductor-setup.sh +147 -0
  33. data/conductor.json +9 -0
  34. data/docs/api-reference.md +519 -0
  35. data/docs/cdn_setup.md +384 -0
  36. data/docs/common-upgrades.md +695 -0
  37. data/docs/configuration.md +845 -0
  38. data/docs/css-modules-export-mode.md +566 -0
  39. data/docs/customizing_babel_config.md +16 -16
  40. data/docs/deployment.md +78 -7
  41. data/docs/developing_shakapacker.md +6 -0
  42. data/docs/early_hints.md +433 -0
  43. data/docs/early_hints_manual_api.md +454 -0
  44. data/docs/feature_testing.md +492 -0
  45. data/docs/node_package_api.md +70 -0
  46. data/docs/optional-peer-dependencies.md +203 -0
  47. data/docs/peer-dependencies.md +71 -0
  48. data/docs/precompile_hook.md +486 -0
  49. data/docs/preventing_fouc.md +132 -0
  50. data/docs/react.md +58 -48
  51. data/docs/releasing.md +288 -0
  52. data/docs/rspack.md +218 -0
  53. data/docs/rspack_migration_guide.md +862 -0
  54. data/docs/sprockets.md +1 -0
  55. data/docs/style_loader_vs_mini_css.md +12 -12
  56. data/docs/subresource_integrity.md +13 -7
  57. data/docs/transpiler-migration.md +212 -0
  58. data/docs/transpiler-performance.md +200 -0
  59. data/docs/troubleshooting.md +272 -24
  60. data/docs/typescript-migration.md +388 -0
  61. data/docs/typescript.md +103 -0
  62. data/docs/using_esbuild_loader.md +12 -12
  63. data/docs/using_swc_loader.md +121 -16
  64. data/docs/v6_upgrade.md +42 -19
  65. data/docs/v7_upgrade.md +8 -6
  66. data/docs/v8_upgrade.md +13 -12
  67. data/docs/v9_upgrade.md +616 -0
  68. data/eslint.config.fast.js +254 -0
  69. data/eslint.config.js +309 -0
  70. data/jest.config.js +8 -1
  71. data/knip.ts +61 -0
  72. data/lib/install/bin/shakapacker +4 -6
  73. data/lib/install/bin/shakapacker-config +11 -0
  74. data/lib/install/bin/shakapacker-dev-server +1 -1
  75. data/lib/install/binstubs.rb +6 -2
  76. data/lib/install/config/rspack/rspack.config.js +6 -0
  77. data/lib/install/config/rspack/rspack.config.ts +7 -0
  78. data/lib/install/config/shakapacker.yml +75 -12
  79. data/lib/install/config/webpack/webpack.config.ts +7 -0
  80. data/lib/install/package.json +38 -0
  81. data/lib/install/template.rb +207 -45
  82. data/lib/shakapacker/build_config_loader.rb +147 -0
  83. data/lib/shakapacker/bundler_switcher.rb +415 -0
  84. data/lib/shakapacker/compiler.rb +87 -0
  85. data/lib/shakapacker/configuration.rb +475 -6
  86. data/lib/shakapacker/dev_server.rb +88 -1
  87. data/lib/shakapacker/dev_server_runner.rb +240 -6
  88. data/lib/shakapacker/doctor.rb +1191 -0
  89. data/lib/shakapacker/env.rb +19 -3
  90. data/lib/shakapacker/helper.rb +411 -14
  91. data/lib/shakapacker/install/env.rb +33 -0
  92. data/lib/shakapacker/instance.rb +93 -4
  93. data/lib/shakapacker/manifest.rb +167 -30
  94. data/lib/shakapacker/railtie.rb +4 -0
  95. data/lib/shakapacker/rspack_runner.rb +19 -0
  96. data/lib/shakapacker/runner.rb +668 -9
  97. data/lib/shakapacker/swc_migrator.rb +384 -0
  98. data/lib/shakapacker/utils/manager.rb +2 -0
  99. data/lib/shakapacker/utils/version_syntax_converter.rb +1 -1
  100. data/lib/shakapacker/version.rb +1 -1
  101. data/lib/shakapacker/version_checker.rb +1 -1
  102. data/lib/shakapacker/webpack_runner.rb +4 -42
  103. data/lib/shakapacker.rb +159 -1
  104. data/lib/tasks/shakapacker/binstubs.rake +4 -2
  105. data/lib/tasks/shakapacker/check_binstubs.rake +2 -2
  106. data/lib/tasks/shakapacker/doctor.rake +48 -0
  107. data/lib/tasks/shakapacker/export_bundler_config.rake +68 -0
  108. data/lib/tasks/shakapacker/install.rake +16 -4
  109. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  110. data/lib/tasks/shakapacker/switch_bundler.rake +72 -0
  111. data/lib/tasks/shakapacker.rake +2 -0
  112. data/package/.npmignore +4 -0
  113. data/package/babel/preset.ts +59 -0
  114. data/package/config.ts +189 -0
  115. data/package/configExporter/buildValidator.ts +906 -0
  116. data/package/configExporter/cli.ts +1748 -0
  117. data/package/configExporter/configDocs.ts +102 -0
  118. data/package/configExporter/configFile.ts +663 -0
  119. data/package/configExporter/fileWriter.ts +112 -0
  120. data/package/configExporter/index.ts +15 -0
  121. data/package/configExporter/types.ts +159 -0
  122. data/package/configExporter/yamlSerializer.ts +391 -0
  123. data/package/dev_server.ts +27 -0
  124. data/package/env.ts +92 -0
  125. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +36 -0
  126. data/package/environments/base.ts +147 -0
  127. data/package/environments/development.ts +88 -0
  128. data/package/environments/production.ts +82 -0
  129. data/package/environments/test.ts +55 -0
  130. data/package/environments/types.ts +98 -0
  131. data/package/esbuild/index.ts +40 -0
  132. data/package/index.d.ts +68 -93
  133. data/package/index.d.ts.template +72 -0
  134. data/package/index.ts +104 -0
  135. data/package/loaders.d.ts +28 -0
  136. data/package/optimization/rspack.ts +36 -0
  137. data/package/optimization/webpack.ts +55 -0
  138. data/package/plugins/envFilter.ts +82 -0
  139. data/package/plugins/rspack.ts +119 -0
  140. data/package/plugins/webpack.ts +82 -0
  141. data/package/rspack/index.ts +91 -0
  142. data/package/rules/{babel.js → babel.ts} +2 -2
  143. data/package/rules/{coffee.js → coffee.ts} +1 -1
  144. data/package/rules/css.ts +3 -0
  145. data/package/rules/{erb.js → erb.ts} +1 -1
  146. data/package/rules/esbuild.ts +10 -0
  147. data/package/rules/file.ts +41 -0
  148. data/package/rules/{jscommon.js → jscommon.ts} +5 -4
  149. data/package/rules/{less.js → less.ts} +4 -4
  150. data/package/rules/raw.ts +28 -0
  151. data/package/rules/rspack.ts +174 -0
  152. data/package/rules/sass.ts +21 -0
  153. data/package/rules/{stylus.js → stylus.ts} +4 -8
  154. data/package/rules/swc.ts +10 -0
  155. data/package/rules/{index.js → webpack.ts} +1 -2
  156. data/package/swc/index.ts +54 -0
  157. data/package/types/README.md +90 -0
  158. data/package/types/index.ts +69 -0
  159. data/package/types.ts +105 -0
  160. data/package/utils/bundlerUtils.ts +232 -0
  161. data/package/utils/configPath.ts +6 -0
  162. data/package/utils/debug.ts +45 -0
  163. data/package/utils/defaultConfigPath.ts +7 -0
  164. data/package/utils/ensureManifestExists.ts +17 -0
  165. data/package/utils/errorCodes.ts +249 -0
  166. data/package/utils/errorHelpers.ts +152 -0
  167. data/package/utils/getStyleRule.ts +75 -0
  168. data/package/utils/helpers.ts +99 -0
  169. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  170. data/package/utils/pathValidation.ts +207 -0
  171. data/package/utils/requireOrError.ts +24 -0
  172. data/package/utils/snakeToCamelCase.ts +5 -0
  173. data/package/utils/typeGuards.ts +388 -0
  174. data/package/utils/validateDependencies.ts +61 -0
  175. data/package/webpack-types.d.ts +33 -0
  176. data/package/webpackDevServerConfig.ts +130 -0
  177. data/package.json +157 -18
  178. data/scripts/remove-use-strict.js +44 -0
  179. data/scripts/type-check-no-emit.js +27 -0
  180. data/shakapacker.gemspec +4 -2
  181. data/sig/shakapacker/commands.rbs +35 -0
  182. data/sig/shakapacker/compiler.rbs +65 -0
  183. data/sig/shakapacker/compiler_strategy.rbs +41 -0
  184. data/sig/shakapacker/configuration.rbs +140 -0
  185. data/sig/shakapacker/dev_server.rbs +56 -0
  186. data/sig/shakapacker/env.rbs +25 -0
  187. data/sig/shakapacker/helper.rbs +98 -0
  188. data/sig/shakapacker/instance.rbs +46 -0
  189. data/sig/shakapacker/manifest.rbs +69 -0
  190. data/sig/shakapacker/version.rbs +4 -0
  191. data/sig/shakapacker.rbs +66 -0
  192. data/test/configExporter/buildValidator.test.js +1295 -0
  193. data/test/configExporter/configFile.test.js +393 -0
  194. data/test/configExporter/integration.test.js +262 -0
  195. data/test/helpers.js +1 -1
  196. data/test/package/bundlerUtils.rspack.test.js +145 -0
  197. data/test/package/bundlerUtils.test.js +97 -0
  198. data/test/package/config.test.js +14 -0
  199. data/test/package/configExporter/cli.test.js +440 -0
  200. data/test/package/configExporter/types.test.js +163 -0
  201. data/test/package/configExporter.test.js +491 -0
  202. data/test/package/env.test.js +42 -7
  203. data/test/package/environments/base.test.js +14 -4
  204. data/test/package/helpers.test.js +2 -2
  205. data/test/package/plugins/envFiltering.test.js +453 -0
  206. data/test/package/plugins/webpackSubresourceIntegrity.test.js +89 -0
  207. data/test/package/rspack/index.test.js +293 -0
  208. data/test/package/rspack/optimization.test.js +86 -0
  209. data/test/package/rspack/plugins.test.js +185 -0
  210. data/test/package/rspack/rules.test.js +229 -0
  211. data/test/package/rules/babel.test.js +65 -38
  212. data/test/package/rules/esbuild.test.js +13 -4
  213. data/test/package/rules/file.test.js +7 -1
  214. data/test/package/rules/raw.test.js +40 -7
  215. data/test/package/rules/sass-version-parsing.test.js +71 -0
  216. data/test/package/rules/sass.test.js +11 -6
  217. data/test/package/rules/sass1.test.js +8 -5
  218. data/test/package/rules/sass16.test.js +24 -0
  219. data/test/package/rules/swc.test.js +50 -39
  220. data/test/package/rules/webpack.test.js +35 -0
  221. data/test/package/staging.test.js +4 -3
  222. data/test/package/transpiler-defaults.test.js +169 -0
  223. data/test/package/utils/ensureManifestExists.test.js +51 -0
  224. data/test/package/yamlSerializer.test.js +204 -0
  225. data/test/peer-dependencies.sh +85 -0
  226. data/test/resolver.js +34 -3
  227. data/test/scripts/remove-use-strict.test.js +125 -0
  228. data/test/typescript/build.test.js +118 -0
  229. data/test/typescript/environments.test.js +107 -0
  230. data/test/typescript/pathValidation.test.js +186 -0
  231. data/test/typescript/requireOrError.test.js +49 -0
  232. data/test/typescript/securityValidation.test.js +182 -0
  233. data/tools/README.md +134 -0
  234. data/tools/css-modules-v9-codemod.js +179 -0
  235. data/tsconfig.eslint.json +9 -0
  236. data/tsconfig.json +38 -0
  237. data/yarn.lock +3202 -1097
  238. metadata +212 -44
  239. data/.eslintignore +0 -4
  240. data/.eslintrc.js +0 -36
  241. data/Gemfile.lock +0 -251
  242. data/package/babel/preset.js +0 -48
  243. data/package/config.js +0 -56
  244. data/package/dev_server.js +0 -23
  245. data/package/env.js +0 -48
  246. data/package/environments/base.js +0 -171
  247. data/package/environments/development.js +0 -13
  248. data/package/environments/production.js +0 -88
  249. data/package/environments/test.js +0 -3
  250. data/package/esbuild/index.js +0 -40
  251. data/package/index.js +0 -40
  252. data/package/rules/css.js +0 -3
  253. data/package/rules/esbuild.js +0 -10
  254. data/package/rules/file.js +0 -29
  255. data/package/rules/raw.js +0 -5
  256. data/package/rules/sass.js +0 -18
  257. data/package/rules/swc.js +0 -10
  258. data/package/swc/index.js +0 -50
  259. data/package/utils/configPath.js +0 -4
  260. data/package/utils/defaultConfigPath.js +0 -2
  261. data/package/utils/getStyleRule.js +0 -40
  262. data/package/utils/helpers.js +0 -62
  263. data/package/utils/snakeToCamelCase.js +0 -5
  264. data/package/webpackDevServerConfig.js +0 -71
  265. data/test/package/rules/index.test.js +0 -16
@@ -0,0 +1,293 @@
1
+ /* eslint-disable func-names, jest/no-conditional-in-test */
2
+
3
+ const { chdirTestApp } = require("../../helpers")
4
+
5
+ const rootPath = process.cwd()
6
+ chdirTestApp()
7
+
8
+ // Mock config to ensure assets_bundler is set to rspack
9
+ jest.mock("../../../package/config", () => {
10
+ const actual = jest.requireActual("../../../package/config")
11
+ return {
12
+ ...actual,
13
+ assets_bundler: "rspack"
14
+ }
15
+ })
16
+
17
+ // Mock helpers before requiring the rspack module
18
+ jest.mock("../../../package/utils/helpers", () => {
19
+ const original = jest.requireActual("../../../package/utils/helpers")
20
+ const moduleExists = jest.fn(() => true)
21
+ return {
22
+ ...original,
23
+ moduleExists
24
+ }
25
+ })
26
+
27
+ // Mock validateDependencies to prevent actual validation
28
+ jest.mock("../../../package/utils/validateDependencies", () => ({
29
+ validateRspackDependencies: jest.fn()
30
+ }))
31
+
32
+ // Mock requireOrError to provide a fake @rspack/core (v2 is pure ESM, can't be require()'d by Jest)
33
+ jest.mock("../../../package/utils/requireOrError", () => ({
34
+ requireOrError: (moduleName) => {
35
+ if (moduleName === "@rspack/core") {
36
+ const CssExtractRspackPlugin = jest.fn(function (options) {
37
+ this.options = options
38
+ })
39
+ CssExtractRspackPlugin.loader = "css-extract-rspack-loader"
40
+
41
+ return {
42
+ DefinePlugin: jest.fn(function (definitions) {
43
+ this.definitions = definitions
44
+ }),
45
+ EnvironmentPlugin: jest.fn(function (env) {
46
+ this.env = env
47
+ }),
48
+ ProvidePlugin: jest.fn(function (definitions) {
49
+ this.definitions = definitions
50
+ }),
51
+ HotModuleReplacementPlugin: jest.fn(),
52
+ ProgressPlugin: jest.fn(),
53
+ CssExtractRspackPlugin,
54
+ SubresourceIntegrityPlugin: jest.fn(function (options) {
55
+ this.options = options
56
+ }),
57
+ SwcJsMinimizerRspackPlugin: jest.fn(),
58
+ LightningCssMinimizerRspackPlugin: jest.fn()
59
+ }
60
+ }
61
+ if (moduleName === "rspack-manifest-plugin") {
62
+ return {
63
+ RspackManifestPlugin: jest.fn(function (options) {
64
+ this.options = options
65
+ })
66
+ }
67
+ }
68
+ return jest
69
+ .requireActual("../../../package/utils/requireOrError")
70
+ .requireOrError(moduleName)
71
+ }
72
+ }))
73
+
74
+ describe("rspack/index", () => {
75
+ let rspackIndex
76
+ let validateRspackDependencies
77
+
78
+ beforeEach(() => {
79
+ jest.resetModules()
80
+ rspackIndex = require("../../../package/rspack/index")
81
+ ;({
82
+ validateRspackDependencies
83
+ } = require("../../../package/utils/validateDependencies"))
84
+ })
85
+
86
+ afterAll(() => process.chdir(rootPath))
87
+
88
+ describe("exports", () => {
89
+ test("exports webpack-merge v5 functions", () => {
90
+ expect(rspackIndex.merge).toBeInstanceOf(Function)
91
+ expect(rspackIndex.mergeWithRules).toBeInstanceOf(Function)
92
+ expect(rspackIndex.mergeWithCustomize).toBeInstanceOf(Function)
93
+ expect(rspackIndex.unique).toBeInstanceOf(Function)
94
+ })
95
+
96
+ test("exports config object", () => {
97
+ expect(rspackIndex.config).toHaveProperty("source_path")
98
+ expect(rspackIndex.config).toHaveProperty("public_output_path")
99
+ })
100
+
101
+ test("exports devServer object", () => {
102
+ expect(rspackIndex.devServer).toBeDefined()
103
+ })
104
+
105
+ test("exports generateRspackConfig function", () => {
106
+ expect(rspackIndex.generateRspackConfig).toBeInstanceOf(Function)
107
+ })
108
+
109
+ test("exports baseConfig object", () => {
110
+ expect(rspackIndex.baseConfig).toBeDefined()
111
+ expect(rspackIndex.baseConfig).toHaveProperty("mode")
112
+ })
113
+
114
+ test("exports env object", () => {
115
+ expect(rspackIndex.env).toHaveProperty("railsEnv")
116
+ expect(rspackIndex.env).toHaveProperty("nodeEnv")
117
+ })
118
+
119
+ test("exports rules array", () => {
120
+ expect(Array.isArray(rspackIndex.rules)).toBe(true)
121
+ expect(rspackIndex.rules.length).toBeGreaterThan(0)
122
+ })
123
+
124
+ test("exports moduleExists function", () => {
125
+ expect(typeof rspackIndex.moduleExists).toBe("function")
126
+ })
127
+
128
+ test("exports canProcess function", () => {
129
+ expect(rspackIndex.canProcess).toBeInstanceOf(Function)
130
+ })
131
+
132
+ test("exports inliningCss value", () => {
133
+ expect(typeof rspackIndex.inliningCss).toBe("boolean")
134
+ })
135
+ })
136
+
137
+ describe("generateRspackConfig", () => {
138
+ test("returns a valid rspack config object", () => {
139
+ const config = rspackIndex.generateRspackConfig()
140
+
141
+ expect(config).toBeDefined()
142
+ expect(config).toHaveProperty("mode")
143
+ expect(config).toHaveProperty("module")
144
+ expect(config).toHaveProperty("plugins")
145
+ expect(config).toHaveProperty("optimization")
146
+ })
147
+
148
+ test("returns a new top-level config object on each call", () => {
149
+ const config1 = rspackIndex.generateRspackConfig()
150
+ const config2 = rspackIndex.generateRspackConfig()
151
+
152
+ expect(config1).not.toBe(config2)
153
+ config1.newKey = "new value"
154
+
155
+ expect(config2).not.toHaveProperty("newKey")
156
+ })
157
+
158
+ test("merges extra config", () => {
159
+ const config = rspackIndex.generateRspackConfig({
160
+ newKey: "new value",
161
+ output: {
162
+ path: "new path"
163
+ }
164
+ })
165
+
166
+ expect(config).toHaveProperty("newKey", "new value")
167
+ expect(config).toHaveProperty("output.path", "new path")
168
+ })
169
+
170
+ test("includes module rules in config", () => {
171
+ const config = rspackIndex.generateRspackConfig()
172
+
173
+ expect(config.module).toBeDefined()
174
+ expect(config.module.rules).toBeDefined()
175
+ expect(Array.isArray(config.module.rules)).toBe(true)
176
+ // The exact number of rules depends on which optional loaders are installed,
177
+ // so we only verify that at least some rules exist
178
+ expect(config.module.rules.length).toBeGreaterThan(0)
179
+ })
180
+
181
+ test("includes plugins in config", () => {
182
+ const config = rspackIndex.generateRspackConfig()
183
+
184
+ expect(config.plugins).toBeDefined()
185
+ expect(Array.isArray(config.plugins)).toBe(true)
186
+ })
187
+
188
+ test("includes optimization in config", () => {
189
+ const config = rspackIndex.generateRspackConfig()
190
+
191
+ expect(config.optimization).toBeDefined()
192
+ expect(config.optimization).toHaveProperty("minimize")
193
+ })
194
+
195
+ test("errors if multiple configs are provided", () => {
196
+ expect(() => rspackIndex.generateRspackConfig({}, {})).toThrow(
197
+ "use webpack-merge to merge configs before passing them to Shakapacker"
198
+ )
199
+ })
200
+
201
+ test("validates rspack dependencies on generation", () => {
202
+ rspackIndex.generateRspackConfig()
203
+ expect(validateRspackDependencies).toHaveBeenCalledTimes(1)
204
+ })
205
+ })
206
+
207
+ describe("rules", () => {
208
+ test("includes JavaScript/JSX rule with builtin:swc-loader", () => {
209
+ const jsRule = rspackIndex.rules.find(
210
+ (rule) => rule.test && rule.test.toString().includes("js|jsx|mjs")
211
+ )
212
+
213
+ expect(jsRule).toBeDefined()
214
+ expect(jsRule.type).toBe("javascript/auto")
215
+ expect(jsRule.use).toBeDefined()
216
+ expect(Array.isArray(jsRule.use)).toBe(true)
217
+ expect(jsRule.use[0].loader).toBe("builtin:swc-loader")
218
+ })
219
+
220
+ test("includes TypeScript rule with builtin:swc-loader", () => {
221
+ const tsRule = rspackIndex.rules.find(
222
+ (rule) => rule.test && rule.test.toString().includes("ts|tsx")
223
+ )
224
+
225
+ expect(tsRule).toBeDefined()
226
+ expect(tsRule.type).toBe("javascript/auto")
227
+ expect(tsRule.use).toBeDefined()
228
+ expect(Array.isArray(tsRule.use)).toBe(true)
229
+ expect(tsRule.use[0].loader).toBe("builtin:swc-loader")
230
+ })
231
+
232
+ test("includes file/asset handling rule", () => {
233
+ const fileRule = rspackIndex.rules.find(
234
+ (rule) =>
235
+ rule.test &&
236
+ (rule.test.toString().includes("png") ||
237
+ rule.test.toString().includes("jpg") ||
238
+ rule.test.toString().includes("svg"))
239
+ )
240
+
241
+ expect(fileRule).toBeDefined()
242
+ })
243
+
244
+ test("includes raw file loading rule", () => {
245
+ const rawRule = rspackIndex.rules.find(
246
+ (rule) => rule.type === "asset/source"
247
+ )
248
+
249
+ expect(rawRule).toBeDefined()
250
+ })
251
+ })
252
+
253
+ describe("helper functions", () => {
254
+ test("moduleExists returns boolean", () => {
255
+ const result = rspackIndex.moduleExists("some-module")
256
+ expect(typeof result).toBe("boolean")
257
+ })
258
+
259
+ test("canProcess invokes callback when module resolves", () => {
260
+ const callback = jest.fn((modulePath) => ({
261
+ processed: true,
262
+ path: modulePath
263
+ }))
264
+ const result = rspackIndex.canProcess("path", callback)
265
+
266
+ expect(callback).toHaveBeenCalledWith(expect.any(String))
267
+ expect(result).toHaveProperty("processed", true)
268
+ expect(result).toHaveProperty("path")
269
+ })
270
+
271
+ test("canProcess returns null and does not invoke callback when module is missing", () => {
272
+ const callback = jest.fn()
273
+ const result = rspackIndex.canProcess(
274
+ "__definitely_not_a_real_package_name__",
275
+ callback
276
+ )
277
+
278
+ expect(result).toBeNull()
279
+ expect(callback).not.toHaveBeenCalled()
280
+ })
281
+ })
282
+
283
+ describe("environment integration", () => {
284
+ test("uses correct environment config based on NODE_ENV", () => {
285
+ const config = rspackIndex.generateRspackConfig()
286
+ const { nodeEnv } = rspackIndex.env
287
+
288
+ const expectedMode =
289
+ nodeEnv === "production" ? "production" : "development"
290
+ expect(config.mode).toBe(expectedMode)
291
+ })
292
+ })
293
+ })
@@ -0,0 +1,86 @@
1
+ /* eslint-disable func-names */
2
+
3
+ // Mock requireOrError to prevent actual module loading
4
+ jest.mock("../../../package/utils/requireOrError", () => ({
5
+ requireOrError: (moduleName) => {
6
+ if (moduleName === "@rspack/core") {
7
+ return {
8
+ SwcJsMinimizerRspackPlugin: jest.fn(function () {
9
+ this.name = "SwcJsMinimizerRspackPlugin"
10
+ }),
11
+ LightningCssMinimizerRspackPlugin: jest.fn(function () {
12
+ this.name = "LightningCssMinimizerRspackPlugin"
13
+ })
14
+ }
15
+ }
16
+ throw new Error(`Module ${moduleName} not found`)
17
+ }
18
+ }))
19
+
20
+ // Mock debug logger
21
+ jest.mock("../../../package/utils/debug", () => ({
22
+ error: jest.fn(),
23
+ warn: jest.fn(),
24
+ info: jest.fn(),
25
+ debug: jest.fn()
26
+ }))
27
+
28
+ describe("rspack/optimization", () => {
29
+ let getOptimization
30
+
31
+ beforeEach(() => {
32
+ jest.resetModules()
33
+ const optimizationModule = require("../../../package/optimization/rspack")
34
+ getOptimization = optimizationModule.getOptimization
35
+ })
36
+
37
+ afterEach(() => {
38
+ jest.clearAllMocks()
39
+ })
40
+
41
+ describe("getOptimization", () => {
42
+ test("returns an optimization config object", () => {
43
+ const optimization = getOptimization()
44
+
45
+ expect(optimization).toBeDefined()
46
+ expect(optimization).toHaveProperty("minimize")
47
+ expect(optimization).toHaveProperty("minimizer")
48
+ })
49
+
50
+ test("sets minimize to true", () => {
51
+ const optimization = getOptimization()
52
+
53
+ expect(optimization.minimize).toBe(true)
54
+ })
55
+
56
+ test("includes SwcJsMinimizerRspackPlugin", () => {
57
+ const optimization = getOptimization()
58
+
59
+ expect(Array.isArray(optimization.minimizer)).toBe(true)
60
+ const jsMinimizer = optimization.minimizer.find(
61
+ (m) => m.name === "SwcJsMinimizerRspackPlugin"
62
+ )
63
+ expect(jsMinimizer).toBeDefined()
64
+ })
65
+
66
+ test("includes LightningCssMinimizerRspackPlugin", () => {
67
+ const optimization = getOptimization()
68
+
69
+ expect(Array.isArray(optimization.minimizer)).toBe(true)
70
+ const cssMinimizer = optimization.minimizer.find(
71
+ (m) => m.name === "LightningCssMinimizerRspackPlugin"
72
+ )
73
+ expect(cssMinimizer).toBeDefined()
74
+ })
75
+
76
+ test("includes both minimizers in correct order", () => {
77
+ const optimization = getOptimization()
78
+
79
+ expect(optimization.minimizer).toHaveLength(2)
80
+ expect(optimization.minimizer[0].name).toBe("SwcJsMinimizerRspackPlugin")
81
+ expect(optimization.minimizer[1].name).toBe(
82
+ "LightningCssMinimizerRspackPlugin"
83
+ )
84
+ })
85
+ })
86
+ })
@@ -0,0 +1,185 @@
1
+ /* eslint-disable func-names, jest/prefer-strict-equal */
2
+
3
+ // Mock helpers before requiring the plugins module
4
+ jest.mock("../../../package/utils/helpers", () => {
5
+ const original = jest.requireActual("../../../package/utils/helpers")
6
+ return {
7
+ ...original,
8
+ moduleExists: jest.fn(() => true)
9
+ }
10
+ })
11
+
12
+ // Mock requireOrError to prevent actual module loading
13
+ jest.mock("../../../package/utils/requireOrError", () => ({
14
+ requireOrError: (moduleName) => {
15
+ if (moduleName === "rspack-manifest-plugin") {
16
+ return {
17
+ RspackManifestPlugin: jest.fn(function (options) {
18
+ this.options = options
19
+ this.name = "RspackManifestPlugin"
20
+ })
21
+ }
22
+ }
23
+ if (moduleName === "@rspack/core") {
24
+ return {
25
+ EnvironmentPlugin: jest.fn(function (env) {
26
+ this.env = env
27
+ this.name = "EnvironmentPlugin"
28
+ }),
29
+ CssExtractRspackPlugin: jest.fn(function (options) {
30
+ this.options = options
31
+ this.name = "CssExtractRspackPlugin"
32
+ }),
33
+ SubresourceIntegrityPlugin: jest.fn(function (options) {
34
+ this.options = options
35
+ this.name = "SubresourceIntegrityPlugin"
36
+ })
37
+ }
38
+ }
39
+ throw new Error(`Module ${moduleName} not found`)
40
+ }
41
+ }))
42
+
43
+ describe("rspack/plugins", () => {
44
+ let getPlugins
45
+ let moduleExists
46
+ let config
47
+
48
+ beforeEach(() => {
49
+ jest.resetModules()
50
+ config = require("../../../package/config")
51
+ moduleExists = require("../../../package/utils/helpers").moduleExists
52
+ const pluginsModule = require("../../../package/plugins/rspack")
53
+ getPlugins = pluginsModule.getPlugins
54
+ })
55
+
56
+ afterEach(() => {
57
+ jest.clearAllMocks()
58
+ })
59
+
60
+ describe("getPlugins", () => {
61
+ test("returns an array", () => {
62
+ const plugins = getPlugins()
63
+ expect(Array.isArray(plugins)).toBe(true)
64
+ })
65
+
66
+ test("includes EnvironmentPlugin with filtered env", () => {
67
+ const plugins = getPlugins()
68
+ const envPlugin = plugins.find((p) => p.name === "EnvironmentPlugin")
69
+ expect(envPlugin).toBeDefined()
70
+ // EnvironmentPlugin receives getFilteredEnv() - a security-filtered version of process.env
71
+ // that only includes allowlisted environment variables
72
+ expect(envPlugin.env).toBeDefined()
73
+ expect(typeof envPlugin.env).toBe("object")
74
+ })
75
+
76
+ test("includes RspackManifestPlugin", () => {
77
+ const plugins = getPlugins()
78
+ const manifestPlugin = plugins.find(
79
+ (p) => p.name === "RspackManifestPlugin"
80
+ )
81
+ expect(manifestPlugin).toBeDefined()
82
+ expect(manifestPlugin.options).toBeDefined()
83
+ expect(manifestPlugin.options.writeToFileEmit).toBe(true)
84
+ })
85
+
86
+ test("rspackManifestPlugin has generate function", () => {
87
+ const plugins = getPlugins()
88
+ const manifestPlugin = plugins.find(
89
+ (p) => p.name === "RspackManifestPlugin"
90
+ )
91
+ expect(manifestPlugin.options.generate).toBeInstanceOf(Function)
92
+ })
93
+
94
+ test("rspackManifestPlugin generate creates proper manifest structure", () => {
95
+ const plugins = getPlugins()
96
+ const manifestPlugin = plugins.find(
97
+ (p) => p.name === "RspackManifestPlugin"
98
+ )
99
+ const { publicPath } = manifestPlugin.options
100
+
101
+ const files = [
102
+ { name: "app.js", path: `${publicPath}app-123.js` },
103
+ { name: "app.css", path: `${publicPath}app-456.css` }
104
+ ]
105
+
106
+ const entrypoints = {
107
+ app: ["app-123.js", "app-456.css"]
108
+ }
109
+
110
+ const manifest = manifestPlugin.options.generate(null, files, entrypoints)
111
+
112
+ expect(manifest["app.js"]).toBe(`${publicPath}app-123.js`)
113
+ expect(manifest["app.css"]).toBe(`${publicPath}app-456.css`)
114
+ expect(manifest).toHaveProperty("entrypoints")
115
+ expect(manifest.entrypoints).toHaveProperty("app")
116
+ expect(manifest.entrypoints.app).toHaveProperty("assets")
117
+ })
118
+
119
+ test("rspackManifestPlugin filters hot-update files", () => {
120
+ const plugins = getPlugins()
121
+ const manifestPlugin = plugins.find(
122
+ (p) => p.name === "RspackManifestPlugin"
123
+ )
124
+ const { publicPath } = manifestPlugin.options
125
+
126
+ const files = []
127
+ const entrypoints = {
128
+ app: [
129
+ "app-123.js",
130
+ "app.hot-update.js",
131
+ "app-456.css",
132
+ "app.hot-update.css"
133
+ ]
134
+ }
135
+
136
+ const manifest = manifestPlugin.options.generate(null, files, entrypoints)
137
+
138
+ expect(manifest.entrypoints.app.assets.js).toEqual([
139
+ `${publicPath}app-123.js`
140
+ ])
141
+ expect(manifest.entrypoints.app.assets.css).toEqual([
142
+ `${publicPath}app-456.css`
143
+ ])
144
+ })
145
+
146
+ test("includes CssExtractRspackPlugin when css-loader exists", () => {
147
+ moduleExists.mockReturnValue(true)
148
+
149
+ const plugins = getPlugins()
150
+ const cssPlugin = plugins.find((p) => p.name === "CssExtractRspackPlugin")
151
+ expect(cssPlugin).toBeDefined()
152
+ expect(cssPlugin.options.filename).toMatch(/^css\//)
153
+ expect(cssPlugin.options.emit).toBe(true)
154
+ })
155
+
156
+ test("does not include CssExtractRspackPlugin when css-loader is missing", () => {
157
+ moduleExists.mockReturnValue(false)
158
+
159
+ const plugins = getPlugins()
160
+ const cssPlugin = plugins.find((p) => p.name === "CssExtractRspackPlugin")
161
+ expect(cssPlugin).toBeUndefined()
162
+ })
163
+
164
+ test("includes SubresourceIntegrityPlugin when integrity is enabled", () => {
165
+ const originalIntegrity = config.integrity
166
+ config.integrity = {
167
+ ...originalIntegrity,
168
+ enabled: true,
169
+ hash_functions: ["sha256"]
170
+ }
171
+
172
+ try {
173
+ const plugins = getPlugins()
174
+ const sriPlugin = plugins.find(
175
+ (p) => p.name === "SubresourceIntegrityPlugin"
176
+ )
177
+
178
+ expect(sriPlugin).toBeDefined()
179
+ expect(sriPlugin.options.hashFuncNames).toEqual(["sha256"])
180
+ } finally {
181
+ config.integrity = originalIntegrity
182
+ }
183
+ })
184
+ })
185
+ })