shakapacker 9.1.0 → 9.3.0.beta.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
  4. data/.github/workflows/claude-code-review.yml +4 -5
  5. data/.github/workflows/claude.yml +1 -2
  6. data/.github/workflows/dummy.yml +4 -4
  7. data/.github/workflows/generator.yml +9 -9
  8. data/.github/workflows/node.yml +11 -2
  9. data/.github/workflows/ruby.yml +16 -16
  10. data/.github/workflows/test-bundlers.yml +9 -9
  11. data/.gitignore +7 -0
  12. data/CHANGELOG.md +50 -4
  13. data/CLAUDE.md +6 -1
  14. data/CONTRIBUTING.md +0 -1
  15. data/Gemfile.lock +1 -1
  16. data/README.md +35 -14
  17. data/TODO.md +10 -2
  18. data/TODO_v9.md +13 -3
  19. data/bin/export-bundler-config +11 -0
  20. data/conductor-setup.sh +1 -1
  21. data/conductor.json +1 -1
  22. data/docs/cdn_setup.md +13 -8
  23. data/docs/common-upgrades.md +2 -1
  24. data/docs/configuration.md +630 -0
  25. data/docs/css-modules-export-mode.md +120 -100
  26. data/docs/customizing_babel_config.md +16 -16
  27. data/docs/deployment.md +68 -6
  28. data/docs/developing_shakapacker.md +6 -0
  29. data/docs/optional-peer-dependencies.md +9 -4
  30. data/docs/peer-dependencies.md +17 -6
  31. data/docs/precompile_hook.md +342 -0
  32. data/docs/react.md +57 -47
  33. data/docs/releasing.md +195 -0
  34. data/docs/rspack.md +25 -21
  35. data/docs/rspack_migration_guide.md +363 -8
  36. data/docs/sprockets.md +1 -0
  37. data/docs/style_loader_vs_mini_css.md +12 -12
  38. data/docs/subresource_integrity.md +13 -7
  39. data/docs/transpiler-performance.md +40 -19
  40. data/docs/troubleshooting.md +122 -23
  41. data/docs/typescript-migration.md +48 -39
  42. data/docs/typescript.md +12 -8
  43. data/docs/using_esbuild_loader.md +10 -10
  44. data/docs/v6_upgrade.md +33 -20
  45. data/docs/v7_upgrade.md +8 -6
  46. data/docs/v8_upgrade.md +13 -12
  47. data/docs/v9_upgrade.md +2 -1
  48. data/eslint.config.fast.js +134 -0
  49. data/eslint.config.js +140 -0
  50. data/knip.ts +54 -0
  51. data/lib/install/bin/export-bundler-config +11 -0
  52. data/lib/install/bin/shakapacker +1 -1
  53. data/lib/install/bin/shakapacker-dev-server +1 -1
  54. data/lib/install/config/shakapacker.yml +16 -5
  55. data/lib/shakapacker/bundler_switcher.rb +7 -0
  56. data/lib/shakapacker/compiler.rb +80 -0
  57. data/lib/shakapacker/configuration.rb +56 -2
  58. data/lib/shakapacker/dev_server_runner.rb +140 -1
  59. data/lib/shakapacker/doctor.rb +302 -57
  60. data/lib/shakapacker/instance.rb +8 -3
  61. data/lib/shakapacker/rspack_runner.rb +1 -1
  62. data/lib/shakapacker/runner.rb +245 -9
  63. data/lib/shakapacker/version.rb +1 -1
  64. data/lib/shakapacker/webpack_runner.rb +1 -1
  65. data/lib/shakapacker.rb +10 -0
  66. data/lib/tasks/shakapacker/doctor.rake +42 -2
  67. data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
  68. data/package/babel/preset.ts +7 -4
  69. data/package/config.ts +42 -30
  70. data/package/configExporter/cli.ts +1274 -0
  71. data/package/configExporter/configDocs.ts +102 -0
  72. data/package/configExporter/configFile.ts +520 -0
  73. data/package/configExporter/fileWriter.ts +96 -0
  74. data/package/configExporter/index.ts +13 -0
  75. data/package/configExporter/types.ts +70 -0
  76. data/package/configExporter/yamlSerializer.ts +280 -0
  77. data/package/dev_server.ts +1 -1
  78. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
  79. data/package/environments/base.ts +18 -13
  80. data/package/environments/development.ts +1 -1
  81. data/package/environments/production.ts +4 -1
  82. data/package/index.d.ts +50 -3
  83. data/package/index.d.ts.template +50 -0
  84. data/package/index.ts +7 -7
  85. data/package/loaders.d.ts +2 -2
  86. data/package/optimization/rspack.ts +1 -1
  87. data/package/plugins/rspack.ts +15 -4
  88. data/package/plugins/webpack.ts +7 -3
  89. data/package/rspack/index.ts +10 -2
  90. data/package/rules/raw.ts +3 -2
  91. data/package/rules/sass.ts +1 -1
  92. data/package/types/README.md +15 -13
  93. data/package/types/index.ts +5 -5
  94. data/package/types.ts +0 -1
  95. data/package/utils/defaultConfigPath.ts +4 -1
  96. data/package/utils/errorCodes.ts +129 -100
  97. data/package/utils/errorHelpers.ts +34 -29
  98. data/package/utils/getStyleRule.ts +5 -2
  99. data/package/utils/helpers.ts +21 -11
  100. data/package/utils/pathValidation.ts +43 -35
  101. data/package/utils/requireOrError.ts +1 -1
  102. data/package/utils/snakeToCamelCase.ts +1 -1
  103. data/package/utils/typeGuards.ts +132 -83
  104. data/package/utils/validateDependencies.ts +1 -1
  105. data/package/webpack-types.d.ts +3 -3
  106. data/package/webpackDevServerConfig.ts +22 -10
  107. data/package-lock.json +2 -2
  108. data/package.json +37 -28
  109. data/scripts/type-check-no-emit.js +1 -1
  110. data/test/configExporter/configFile.test.js +392 -0
  111. data/test/configExporter/integration.test.js +275 -0
  112. data/test/helpers.js +1 -1
  113. data/test/package/configExporter.test.js +154 -0
  114. data/test/package/helpers.test.js +2 -2
  115. data/test/package/rules/sass-version-parsing.test.js +71 -0
  116. data/test/package/rules/sass.test.js +2 -4
  117. data/test/package/rules/sass1.test.js +1 -3
  118. data/test/package/rules/sass16.test.js +23 -0
  119. data/tools/README.md +15 -5
  120. data/tsconfig.eslint.json +2 -9
  121. data/yarn.lock +1635 -1442
  122. metadata +29 -3
  123. data/.eslintignore +0 -5
@@ -0,0 +1,96 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from "fs"
2
+ import { resolve, dirname, relative, isAbsolute, basename } from "path"
3
+ import { tmpdir } from "os"
4
+ import { FileOutput } from "./types"
5
+
6
+ /**
7
+ * Handles writing config exports to files.
8
+ * Supports single file output or multiple files (one per config).
9
+ */
10
+ export class FileWriter {
11
+ /**
12
+ * Write multiple config files (one per config in array)
13
+ */
14
+ writeMultipleFiles(outputs: FileOutput[], targetDir: string): void {
15
+ // Ensure directory exists
16
+ this.ensureDirectory(targetDir)
17
+
18
+ // Write each file
19
+ outputs.forEach((output) => {
20
+ const safeName = basename(output.filename)
21
+ const filePath = resolve(targetDir, safeName)
22
+ this.validateOutputPath(filePath)
23
+ this.writeFile(filePath, output.content)
24
+ console.log(`[Config Exporter] Created: ${filePath}`)
25
+ })
26
+
27
+ console.log(
28
+ `[Config Exporter] Exported ${outputs.length} config file(s) to ${targetDir}`
29
+ )
30
+ }
31
+
32
+ /**
33
+ * Write a single file
34
+ */
35
+ writeSingleFile(filePath: string, content: string): void {
36
+ // Ensure parent directory exists
37
+ const dir = dirname(filePath)
38
+ this.ensureDirectory(dir)
39
+
40
+ this.validateOutputPath(filePath)
41
+ this.writeFile(filePath, content)
42
+ console.log(`[Config Exporter] Created: ${filePath}`)
43
+ }
44
+
45
+ /**
46
+ * Generate filename for a config export
47
+ * Format without build: {bundler}-{env}-{type}.{ext}
48
+ * Format with build: {bundler}-{build}-{type}.{ext}
49
+ * Examples:
50
+ * webpack-development-client.yaml
51
+ * rspack-production-server.yaml
52
+ * webpack-test-all.json
53
+ * webpack-development-client-hmr.yaml
54
+ * webpack-dev-client.yaml (with build name)
55
+ * rspack-cypress-dev-server.yaml (with build name)
56
+ */
57
+ generateFilename(
58
+ bundler: string,
59
+ env: string,
60
+ configType: "client" | "server" | "all" | "client-hmr",
61
+ format: "yaml" | "json" | "inspect",
62
+ buildName?: string
63
+ ): string {
64
+ const ext = format === "yaml" ? "yaml" : format === "json" ? "json" : "txt"
65
+ const name = buildName || env
66
+ return `${bundler}-${name}-${configType}.${ext}`
67
+ }
68
+
69
+ private writeFile(filePath: string, content: string): void {
70
+ writeFileSync(filePath, content, "utf8")
71
+ }
72
+
73
+ private ensureDirectory(dir: string): void {
74
+ if (!existsSync(dir)) {
75
+ mkdirSync(dir, { recursive: true })
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Validate output path and warn if writing outside cwd
81
+ */
82
+ validateOutputPath(outputPath: string): void {
83
+ const absPath = resolve(outputPath)
84
+ const cwd = process.cwd()
85
+
86
+ const isWithin = (base: string, target: string) => {
87
+ const rel = relative(base, target)
88
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
89
+ }
90
+ if (!isWithin(cwd, absPath) && !isWithin(tmpdir(), absPath)) {
91
+ console.warn(
92
+ `[Config Exporter] Warning: Writing to ${absPath} which is outside current directory (${cwd}) or temp (${tmpdir()})`
93
+ )
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,13 @@
1
+ export { run } from "./cli"
2
+ export type {
3
+ ExportOptions,
4
+ ConfigMetadata,
5
+ FileOutput,
6
+ BundlerConfigFile,
7
+ BuildConfig,
8
+ ResolvedBuildConfig
9
+ } from "./types"
10
+ export { YamlSerializer } from "./yamlSerializer"
11
+ export { FileWriter } from "./fileWriter"
12
+ export { getDocForKey } from "./configDocs"
13
+ export { ConfigFileLoader, generateSampleConfigFile } from "./configFile"
@@ -0,0 +1,70 @@
1
+ export interface ExportOptions {
2
+ doctor?: boolean
3
+ saveDir?: string
4
+ stdout?: boolean
5
+ bundler?: "webpack" | "rspack"
6
+ env?: "development" | "production" | "test"
7
+ clientOnly?: boolean
8
+ serverOnly?: boolean
9
+ output?: string
10
+ format?: "yaml" | "json" | "inspect"
11
+ annotate?: boolean
12
+ verbose?: boolean
13
+ depth?: number | null
14
+ help?: boolean
15
+ // New config file options
16
+ init?: boolean
17
+ configFile?: string
18
+ build?: string
19
+ listBuilds?: boolean
20
+ allBuilds?: boolean
21
+ }
22
+
23
+ export interface ConfigMetadata {
24
+ exportedAt: string
25
+ bundler: string
26
+ environment: string
27
+ configFile: string
28
+ configType: "client" | "server" | "all" | "client-hmr"
29
+ configCount: number
30
+ buildName?: string // New: name of the build from config file
31
+ environmentVariables: {
32
+ NODE_ENV?: string
33
+ RAILS_ENV?: string
34
+ CLIENT_BUNDLE_ONLY?: string
35
+ SERVER_BUNDLE_ONLY?: string
36
+ WEBPACK_SERVE?: string
37
+ }
38
+ }
39
+
40
+ export interface FileOutput {
41
+ filename: string
42
+ content: string
43
+ metadata: ConfigMetadata
44
+ }
45
+
46
+ // Config file schema types
47
+ export interface BundlerConfigFile {
48
+ default_bundler?: "webpack" | "rspack"
49
+ shakapacker_doctor_default_builds_here?: boolean
50
+ builds: Record<string, BuildConfig>
51
+ }
52
+
53
+ export interface BuildConfig {
54
+ description?: string
55
+ bundler?: "webpack" | "rspack"
56
+ environment?: Record<string, string>
57
+ bundler_env?: Record<string, string | boolean>
58
+ outputs?: string[]
59
+ config?: string
60
+ }
61
+
62
+ export interface ResolvedBuildConfig {
63
+ name: string
64
+ description?: string
65
+ bundler: "webpack" | "rspack"
66
+ environment: Record<string, string>
67
+ bundlerEnvArgs: string[] // Converted bundler_env to CLI args
68
+ outputs: string[]
69
+ configFile?: string
70
+ }
@@ -0,0 +1,280 @@
1
+ import { ConfigMetadata } from "./types"
2
+ import { getDocForKey } from "./configDocs"
3
+ import { relative, isAbsolute } from "path"
4
+
5
+ /**
6
+ * Serializes webpack/rspack config to YAML format with optional inline documentation.
7
+ * Handles functions, RegExp, and special objects that don't serialize well to standard YAML.
8
+ */
9
+ export class YamlSerializer {
10
+ private annotate: boolean
11
+ private appRoot: string
12
+
13
+ constructor(options: { annotate: boolean; appRoot: string }) {
14
+ this.annotate = options.annotate
15
+ this.appRoot = options.appRoot
16
+ }
17
+
18
+ /**
19
+ * Serialize a config object to YAML string with metadata header
20
+ */
21
+ serialize(config: any, metadata: ConfigMetadata): string {
22
+ const output: string[] = []
23
+
24
+ // Add metadata header
25
+ output.push(this.createHeader(metadata))
26
+ output.push("")
27
+
28
+ // Serialize the config
29
+ output.push(this.serializeValue(config, 0, ""))
30
+
31
+ return output.join("\n")
32
+ }
33
+
34
+ private createHeader(metadata: ConfigMetadata): string {
35
+ const lines: string[] = []
36
+ lines.push("# " + "=".repeat(77))
37
+ lines.push("# Webpack/Rspack Configuration Export")
38
+ lines.push(`# Generated: ${metadata.exportedAt}`)
39
+ lines.push(`# Environment: ${metadata.environment}`)
40
+ lines.push(`# Bundler: ${metadata.bundler}`)
41
+ lines.push(`# Config Type: ${metadata.configType}`)
42
+ if (metadata.configCount > 1) {
43
+ lines.push(`# Total Configs: ${metadata.configCount}`)
44
+ }
45
+ lines.push("# " + "=".repeat(77))
46
+ return lines.join("\n")
47
+ }
48
+
49
+ private serializeValue(value: any, indent: number, keyPath: string): string {
50
+ if (value === null || value === undefined) {
51
+ return "null"
52
+ }
53
+
54
+ if (typeof value === "boolean") {
55
+ return value.toString()
56
+ }
57
+
58
+ if (typeof value === "number") {
59
+ return value.toString()
60
+ }
61
+
62
+ if (typeof value === "string") {
63
+ return this.serializeString(value, indent)
64
+ }
65
+
66
+ if (typeof value === "function") {
67
+ return this.serializeFunction(value)
68
+ }
69
+
70
+ if (value instanceof RegExp) {
71
+ return this.serializeString(value.toString())
72
+ }
73
+
74
+ if (Array.isArray(value)) {
75
+ return this.serializeArray(value, indent, keyPath)
76
+ }
77
+
78
+ if (typeof value === "object") {
79
+ return this.serializeObject(value, indent, keyPath)
80
+ }
81
+
82
+ return String(value)
83
+ }
84
+
85
+ private serializeString(str: string, indent: number = 0): string {
86
+ // Make absolute paths relative for cleaner output
87
+ const cleaned = this.makePathRelative(str)
88
+
89
+ // Handle multiline strings
90
+ if (cleaned.includes("\n")) {
91
+ const lines = cleaned.split("\n")
92
+ const lineIndent = " ".repeat(indent + 2)
93
+ return "|\n" + lines.map((line) => lineIndent + line).join("\n")
94
+ }
95
+
96
+ // Escape strings that need quoting
97
+ if (
98
+ cleaned.includes(":") ||
99
+ cleaned.includes("#") ||
100
+ cleaned.includes("'") ||
101
+ cleaned.includes('"') ||
102
+ cleaned.startsWith(" ") ||
103
+ cleaned.endsWith(" ")
104
+ ) {
105
+ // Escape backslashes first, then quotes to avoid double-escaping
106
+ return `"${cleaned.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
107
+ }
108
+
109
+ return cleaned
110
+ }
111
+
112
+ private serializeFunction(fn: Function): string {
113
+ // Get function source code
114
+ const source = fn.toString()
115
+
116
+ // Pretty-print function: maintain readable formatting
117
+ const lines = source.split("\n")
118
+
119
+ // For very long functions, truncate
120
+ const maxLines = 50
121
+ const truncated = lines.length > maxLines
122
+ const displayLines = truncated ? lines.slice(0, maxLines) : lines
123
+
124
+ // Clean up indentation while preserving structure
125
+ const minIndent = Math.min(
126
+ ...displayLines
127
+ .filter((l) => l.trim().length > 0)
128
+ .map((l) => l.match(/^\s*/)?.[0].length || 0)
129
+ )
130
+
131
+ const formatted =
132
+ displayLines.map((line) => line.substring(minIndent)).join("\n") +
133
+ (truncated ? "\n..." : "")
134
+
135
+ // Use serializeString to properly handle multiline
136
+ return this.serializeString(formatted)
137
+ }
138
+
139
+ private serializeArray(arr: any[], indent: number, keyPath: string): string {
140
+ if (arr.length === 0) {
141
+ return "[]"
142
+ }
143
+
144
+ const lines: string[] = []
145
+ const itemIndent = " ".repeat(indent + 2)
146
+ const contentIndent = " ".repeat(indent + 4)
147
+
148
+ arr.forEach((item, index) => {
149
+ const itemPath = `${keyPath}[${index}]`
150
+ const serialized = this.serializeValue(item, indent + 4, itemPath)
151
+
152
+ // Add documentation for array items if available
153
+ if (this.annotate) {
154
+ const doc = getDocForKey(itemPath)
155
+ if (doc) {
156
+ lines.push(`${itemIndent}# ${doc}`)
157
+ }
158
+ }
159
+
160
+ if (typeof item === "object" && !Array.isArray(item) && item !== null) {
161
+ // For objects in arrays, emit marker on its own line and indent content
162
+ lines.push(`${itemIndent}-`)
163
+ const nonEmptyLines = serialized
164
+ .split("\n")
165
+ .filter((line: string) => line.trim().length > 0)
166
+ // Compute minimum leading whitespace to preserve relative indentation
167
+ const minIndent = Math.min(
168
+ ...nonEmptyLines.map(
169
+ (line: string) => line.match(/^\s*/)?.[0].length || 0
170
+ )
171
+ )
172
+ nonEmptyLines.forEach((line: string) => {
173
+ // Remove only the common indent, preserving relative indentation
174
+ lines.push(contentIndent + line.substring(minIndent))
175
+ })
176
+ } else if (serialized.includes("\n")) {
177
+ // For multiline values, emit marker on its own line and indent content
178
+ lines.push(`${itemIndent}-`)
179
+ const nonEmptyLines = serialized
180
+ .split("\n")
181
+ .filter((line: string) => line.trim().length > 0)
182
+ // Compute minimum leading whitespace to preserve relative indentation
183
+ const minIndent = Math.min(
184
+ ...nonEmptyLines.map(
185
+ (line: string) => line.match(/^\s*/)?.[0].length || 0
186
+ )
187
+ )
188
+ nonEmptyLines.forEach((line: string) => {
189
+ // Remove only the common indent, preserving relative indentation
190
+ lines.push(contentIndent + line.substring(minIndent))
191
+ })
192
+ } else {
193
+ // For simple values, keep on same line
194
+ lines.push(`${itemIndent}- ${serialized}`)
195
+ }
196
+ })
197
+
198
+ return "\n" + lines.join("\n")
199
+ }
200
+
201
+ private serializeObject(obj: any, indent: number, keyPath: string): string {
202
+ const keys = Object.keys(obj)
203
+ if (keys.length === 0) {
204
+ return "{}"
205
+ }
206
+
207
+ const lines: string[] = []
208
+ const keyIndent = " ".repeat(indent)
209
+ const valueIndent = " ".repeat(indent + 2)
210
+
211
+ keys.forEach((key) => {
212
+ const value = obj[key]
213
+ const fullKeyPath = keyPath ? `${keyPath}.${key}` : key
214
+
215
+ // Add documentation comment if available and annotation is enabled
216
+ if (this.annotate) {
217
+ const doc = getDocForKey(fullKeyPath)
218
+ if (doc) {
219
+ lines.push(`${keyIndent}# ${doc}`)
220
+ }
221
+ }
222
+
223
+ // Handle multiline strings specially with block scalar
224
+ if (typeof value === "string" && value.includes("\n")) {
225
+ lines.push(`${keyIndent}${key}: |`)
226
+ for (const line of value.split("\n")) {
227
+ lines.push(`${valueIndent}${line}`)
228
+ }
229
+ } else if (
230
+ typeof value === "object" &&
231
+ value !== null &&
232
+ !Array.isArray(value)
233
+ ) {
234
+ if (Object.keys(value).length === 0) {
235
+ lines.push(`${keyIndent}${key}: {}`)
236
+ } else {
237
+ lines.push(`${keyIndent}${key}:`)
238
+ const nestedLines = this.serializeObject(
239
+ value,
240
+ indent + 2,
241
+ fullKeyPath
242
+ )
243
+ lines.push(nestedLines)
244
+ }
245
+ } else if (Array.isArray(value)) {
246
+ if (value.length === 0) {
247
+ lines.push(`${keyIndent}${key}: []`)
248
+ } else {
249
+ lines.push(`${keyIndent}${key}:`)
250
+ const arrayLines = this.serializeArray(value, indent + 2, fullKeyPath)
251
+ lines.push(arrayLines)
252
+ }
253
+ } else {
254
+ const serialized = this.serializeValue(value, indent + 2, fullKeyPath)
255
+ lines.push(`${keyIndent}${key}: ${serialized}`)
256
+ }
257
+ })
258
+
259
+ return lines.join("\n")
260
+ }
261
+
262
+ private makePathRelative(str: string): string {
263
+ if (typeof str !== "string") return str
264
+ if (!isAbsolute(str)) return str
265
+
266
+ // Convert absolute paths to relative paths using path.relative
267
+ const rel = relative(this.appRoot, str)
268
+
269
+ if (rel === "") {
270
+ return "."
271
+ }
272
+
273
+ // If path is outside appRoot or already absolute, keep original
274
+ if (rel.startsWith("..") || isAbsolute(rel)) {
275
+ return str
276
+ }
277
+
278
+ return "./" + rel
279
+ }
280
+ }
@@ -18,7 +18,7 @@ if (devServerConfig) {
18
18
  const envValue = envFetch(`${envPrefix}_${key.toUpperCase()}`)
19
19
  if (envValue !== undefined) {
20
20
  // Use bracket notation to avoid ASI issues
21
- (devServerConfig as Record<string, unknown>)[key] = envValue
21
+ ;(devServerConfig as Record<string, unknown>)[key] = envValue
22
22
  }
23
23
  })
24
24
  }
@@ -6,22 +6,28 @@
6
6
  * These tests will fail at compile time if the types are not compatible.
7
7
  */
8
8
 
9
- import type { RspackPlugin, RspackPluginInstance } from '../types'
9
+ import type { RspackPlugin, RspackPluginInstance } from "../types"
10
10
 
11
11
  // Test 1: RspackPlugin should be assignable to RspackPluginInstance
12
- const testPluginToInstance = (plugin: RspackPlugin): RspackPluginInstance => plugin
12
+ const testPluginToInstance = (plugin: RspackPlugin): RspackPluginInstance =>
13
+ plugin
13
14
 
14
15
  // Test 2: RspackPluginInstance should be assignable to RspackPlugin
15
- const testInstanceToPlugin = (instance: RspackPluginInstance): RspackPlugin => instance
16
+ const testInstanceToPlugin = (instance: RspackPluginInstance): RspackPlugin =>
17
+ instance
16
18
 
17
19
  // Test 3: Array compatibility
18
- const testArrayCompatibility = (plugins: RspackPlugin[]): RspackPluginInstance[] => plugins
20
+ const testArrayCompatibility = (
21
+ plugins: RspackPlugin[]
22
+ ): RspackPluginInstance[] => plugins
19
23
  const testArrayCompatibilityReverse = (
20
24
  instances: RspackPluginInstance[]
21
25
  ): RspackPlugin[] => instances
22
26
 
23
27
  // Test 4: Optional parameter compatibility
24
- const testOptionalParam = (plugin?: RspackPlugin): RspackPluginInstance | undefined => plugin
28
+ const testOptionalParam = (
29
+ plugin?: RspackPlugin
30
+ ): RspackPluginInstance | undefined => plugin
25
31
  const testOptionalParamReverse = (
26
32
  instance?: RspackPluginInstance
27
33
  ): RspackPlugin | undefined => instance
@@ -53,13 +53,13 @@ const getEntryObject = (): Entry => {
53
53
  if (config.source_entry_path === "/" && config.nested_entries) {
54
54
  throw new Error(
55
55
  `Invalid Shakapacker configuration detected!\n\n` +
56
- `You have set source_entry_path to '/' with nested_entries enabled.\n` +
57
- `This would create webpack entry points for EVERY file in your source directory,\n` +
58
- `which would severely impact build performance.\n\n` +
59
- `To fix this issue, either:\n` +
60
- `1. Set 'nested_entries: false' in your shakapacker.yml\n` +
61
- `2. Change 'source_entry_path' to a specific subdirectory (e.g., 'packs')\n` +
62
- `3. Or use both options for better organization of your entry points`
56
+ `You have set source_entry_path to '/' with nested_entries enabled.\n` +
57
+ `This would create webpack entry points for EVERY file in your source directory,\n` +
58
+ `which would severely impact build performance.\n\n` +
59
+ `To fix this issue, either:\n` +
60
+ `1. Set 'nested_entries: false' in your shakapacker.yml\n` +
61
+ `2. Change 'source_entry_path' to a specific subdirectory (e.g., 'packs')\n` +
62
+ `3. Or use both options for better organization of your entry points`
63
63
  )
64
64
  }
65
65
 
@@ -73,7 +73,7 @@ const getEntryObject = (): Entry => {
73
73
  const previousPaths = entries[name]
74
74
  if (previousPaths) {
75
75
  const pathArray = Array.isArray(previousPaths)
76
- ? previousPaths as string[]
76
+ ? (previousPaths as string[])
77
77
  : [previousPaths as string]
78
78
  pathArray.push(assetPath)
79
79
  entries[name] = pathArray
@@ -89,7 +89,9 @@ const getModulePaths = (): string[] => {
89
89
  const result = [resolve(config.source_path)]
90
90
 
91
91
  if (config.additional_paths) {
92
- config.additional_paths.forEach((path: string) => result.push(resolve(path)))
92
+ config.additional_paths.forEach((path: string) =>
93
+ result.push(resolve(path))
94
+ )
93
95
  }
94
96
  result.push("node_modules")
95
97
 
@@ -108,9 +110,13 @@ const baseConfig: Configuration = {
108
110
  publicPath: config.publicPath,
109
111
 
110
112
  // This is required for SRI to work.
111
- crossOriginLoading: config.integrity && config.integrity.enabled
112
- ? (config.integrity.cross_origin as "anonymous" | "use-credentials" | false)
113
- : false
113
+ crossOriginLoading:
114
+ config.integrity && config.integrity.enabled
115
+ ? (config.integrity.cross_origin as
116
+ | "anonymous"
117
+ | "use-credentials"
118
+ | false)
119
+ : false
114
120
  },
115
121
  entry: getEntryObject(),
116
122
  resolve: {
@@ -135,4 +141,3 @@ const baseConfig: Configuration = {
135
141
  }
136
142
 
137
143
  export = baseConfig
138
-
@@ -9,7 +9,7 @@ const baseConfig = require("./base")
9
9
  const webpackDevServerConfig = require("../webpackDevServerConfig")
10
10
  const { runningWebpackDevServer } = require("../env")
11
11
  const { moduleExists } = require("../utils/helpers")
12
- import type {
12
+ import type {
13
13
  WebpackConfigWithDevServer,
14
14
  RspackConfigWithDevServer,
15
15
  ReactRefreshWebpackPlugin,
@@ -11,7 +11,10 @@ const { merge } = require("webpack-merge")
11
11
  const baseConfig = require("./base")
12
12
  const { moduleExists } = require("../utils/helpers")
13
13
  const config = require("../config")
14
- import type { Configuration as WebpackConfiguration, WebpackPluginInstance } from "webpack"
14
+ import type {
15
+ Configuration as WebpackConfiguration,
16
+ WebpackPluginInstance
17
+ } from "webpack"
15
18
  import type { CompressionPluginConstructor } from "./types"
16
19
 
17
20
  const optimizationPath = resolve(
data/package/index.d.ts CHANGED
@@ -1,3 +1,50 @@
1
- declare const _default: any;
2
- export = _default;
3
- //# sourceMappingURL=index.d.ts.map
1
+ /**
2
+ * Manual type definitions for Shakapacker package exports.
3
+ *
4
+ * This file is manually maintained because TypeScript cannot infer types
5
+ * from the `export =` syntax with dynamic require() calls in index.ts.
6
+ *
7
+ * When adding/modifying exports in index.ts, update this file accordingly.
8
+ */
9
+
10
+ import type { Configuration, RuleSetRule } from "webpack"
11
+ import type { Config, DevServerConfig, Env } from "./types"
12
+
13
+ /**
14
+ * The shape of the Shakapacker module exports.
15
+ * This interface represents the object exported via CommonJS `export =`.
16
+ */
17
+ interface ShakapackerExports {
18
+ /** Shakapacker configuration from shakapacker.yml */
19
+ config: Config
20
+ /** Development server configuration */
21
+ devServer: DevServerConfig
22
+ /** Base webpack/rspack configuration */
23
+ baseConfig: Configuration
24
+ /** Environment configuration (railsEnv, nodeEnv, etc.) */
25
+ env: Env
26
+ /** Array of webpack/rspack loader rules */
27
+ rules: RuleSetRule[]
28
+ /** Check if a module exists in node_modules */
29
+ moduleExists: (packageName: string) => boolean
30
+ /** Process a file if a specific loader is available */
31
+ canProcess: <T = unknown>(
32
+ rule: string,
33
+ fn: (modulePath: string) => T
34
+ ) => T | null
35
+ /** Whether CSS should be inlined (dev server with HMR) */
36
+ inliningCss: boolean
37
+ /** Generate webpack configuration with optional custom config */
38
+ generateWebpackConfig: (extraConfig?: Configuration) => Configuration
39
+ /** webpack-merge's merge function */
40
+ merge: typeof import("webpack-merge").merge
41
+ /** webpack-merge's mergeWithCustomize function */
42
+ mergeWithCustomize: typeof import("webpack-merge").mergeWithCustomize
43
+ /** webpack-merge's mergeWithRules function */
44
+ mergeWithRules: typeof import("webpack-merge").mergeWithRules
45
+ /** webpack-merge's unique function */
46
+ unique: typeof import("webpack-merge").unique
47
+ }
48
+
49
+ declare const shakapacker: ShakapackerExports
50
+ export = shakapacker