shakapacker 9.3.0.beta.7 → 9.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -109
- data/Gemfile.lock +1 -1
- data/README.md +53 -2
- data/docs/configuration.md +28 -0
- data/docs/rspack_migration_guide.md +238 -2
- data/docs/troubleshooting.md +21 -21
- data/eslint.config.fast.js +8 -0
- data/eslint.config.js +47 -10
- data/knip.ts +8 -1
- data/lib/install/config/shakapacker.yml +6 -6
- data/lib/shakapacker/configuration.rb +227 -4
- data/lib/shakapacker/dev_server.rb +88 -1
- data/lib/shakapacker/doctor.rb +4 -4
- data/lib/shakapacker/instance.rb +85 -1
- data/lib/shakapacker/manifest.rb +85 -11
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker.rb +143 -3
- data/lib/tasks/shakapacker/doctor.rake +1 -1
- data/lib/tasks/shakapacker/export_bundler_config.rake +4 -4
- data/package/config.ts +0 -1
- data/package/configExporter/buildValidator.ts +53 -29
- data/package/configExporter/cli.ts +81 -56
- data/package/configExporter/configFile.ts +33 -26
- data/package/configExporter/types.ts +64 -0
- data/package/configExporter/yamlSerializer.ts +118 -43
- data/package/dev_server.ts +2 -1
- data/package/env.ts +1 -1
- data/package/environments/base.ts +4 -4
- data/package/environments/development.ts +7 -6
- data/package/environments/production.ts +6 -7
- data/package/environments/test.ts +2 -1
- data/package/index.ts +28 -4
- data/package/loaders.d.ts +2 -2
- data/package/optimization/webpack.ts +29 -31
- data/package/rspack/index.ts +2 -1
- data/package/rules/file.ts +1 -0
- data/package/rules/jscommon.ts +1 -0
- data/package/utils/helpers.ts +0 -1
- data/package/utils/pathValidation.ts +68 -7
- data/package/utils/requireOrError.ts +10 -2
- data/package/utils/typeGuards.ts +43 -46
- data/package/webpack-types.d.ts +2 -2
- data/package/webpackDevServerConfig.ts +1 -0
- data/package.json +2 -3
- data/test/package/configExporter/cli.test.js +440 -0
- data/test/package/configExporter/types.test.js +163 -0
- data/test/package/configExporter.test.js +264 -0
- data/test/package/yamlSerializer.test.js +204 -0
- data/test/typescript/pathValidation.test.js +44 -0
- data/test/typescript/requireOrError.test.js +49 -0
- data/yarn.lock +0 -32
- metadata +11 -5
- data/.eslintrc.fast.js +0 -40
- data/.eslintrc.js +0 -84
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable names that can be set by build configurations.
|
|
3
|
+
* These are the only environment variables that build configs are allowed to set.
|
|
4
|
+
* This whitelist prevents malicious configs from modifying critical system variables.
|
|
5
|
+
*/
|
|
6
|
+
export const BUILD_ENV_VARS = [
|
|
7
|
+
"NODE_ENV",
|
|
8
|
+
"RAILS_ENV",
|
|
9
|
+
"NODE_OPTIONS",
|
|
10
|
+
"BABEL_ENV",
|
|
11
|
+
"WEBPACK_SERVE",
|
|
12
|
+
"CLIENT_BUNDLE_ONLY",
|
|
13
|
+
"SERVER_BUNDLE_ONLY"
|
|
14
|
+
] as const
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Environment variables that must never be set by build configurations.
|
|
18
|
+
* Setting these could compromise system security or cause unexpected behavior.
|
|
19
|
+
*/
|
|
20
|
+
export const DANGEROUS_ENV_VARS = [
|
|
21
|
+
"PATH",
|
|
22
|
+
"HOME",
|
|
23
|
+
"LD_PRELOAD",
|
|
24
|
+
"LD_LIBRARY_PATH",
|
|
25
|
+
"DYLD_LIBRARY_PATH",
|
|
26
|
+
"DYLD_INSERT_LIBRARIES"
|
|
27
|
+
] as const
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Type predicate to check if a string is in the BUILD_ENV_VARS whitelist
|
|
31
|
+
*
|
|
32
|
+
* Note: The type assertion is necessary because TypeScript's type system cannot
|
|
33
|
+
* infer that .includes() on a readonly const array will properly narrow the type.
|
|
34
|
+
* The assertion is safe because we're only widening the type for the includes() check.
|
|
35
|
+
*/
|
|
36
|
+
export function isBuildEnvVar(
|
|
37
|
+
key: string
|
|
38
|
+
): key is (typeof BUILD_ENV_VARS)[number] {
|
|
39
|
+
return (BUILD_ENV_VARS as readonly string[]).includes(key)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Type predicate to check if a string is in the DANGEROUS_ENV_VARS blacklist
|
|
44
|
+
*
|
|
45
|
+
* Note: The type assertion is necessary because TypeScript's type system cannot
|
|
46
|
+
* infer that .includes() on a readonly const array will properly narrow the type.
|
|
47
|
+
* The assertion is safe because we're only widening the type for the includes() check.
|
|
48
|
+
*/
|
|
49
|
+
export function isDangerousEnvVar(
|
|
50
|
+
key: string
|
|
51
|
+
): key is (typeof DANGEROUS_ENV_VARS)[number] {
|
|
52
|
+
return (DANGEROUS_ENV_VARS as readonly string[]).includes(key)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default directory for config exports when using --doctor or file output modes.
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_EXPORT_DIR = "shakapacker-config-exports"
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Default config file path for bundler build configurations.
|
|
62
|
+
*/
|
|
63
|
+
export const DEFAULT_CONFIG_FILE = "config/shakapacker-builds.yml"
|
|
64
|
+
|
|
1
65
|
export interface ExportOptions {
|
|
2
66
|
doctor?: boolean
|
|
3
67
|
saveDir?: string
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { relative, isAbsolute } from "path"
|
|
1
2
|
import { ConfigMetadata } from "./types"
|
|
2
3
|
import { getDocForKey } from "./configDocs"
|
|
3
|
-
import { relative, isAbsolute } from "path"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Serializes webpack/rspack config to YAML format with optional inline documentation.
|
|
@@ -8,6 +8,7 @@ import { relative, isAbsolute } from "path"
|
|
|
8
8
|
*/
|
|
9
9
|
export class YamlSerializer {
|
|
10
10
|
private annotate: boolean
|
|
11
|
+
|
|
11
12
|
private appRoot: string
|
|
12
13
|
|
|
13
14
|
constructor(options: { annotate: boolean; appRoot: string }) {
|
|
@@ -18,11 +19,11 @@ export class YamlSerializer {
|
|
|
18
19
|
/**
|
|
19
20
|
* Serialize a config object to YAML string with metadata header
|
|
20
21
|
*/
|
|
21
|
-
serialize(config:
|
|
22
|
+
serialize(config: unknown, metadata: ConfigMetadata): string {
|
|
22
23
|
const output: string[] = []
|
|
23
24
|
|
|
24
25
|
// Add metadata header
|
|
25
|
-
output.push(
|
|
26
|
+
output.push(YamlSerializer.createHeader(metadata))
|
|
26
27
|
output.push("")
|
|
27
28
|
|
|
28
29
|
// Serialize the config
|
|
@@ -31,9 +32,9 @@ export class YamlSerializer {
|
|
|
31
32
|
return output.join("\n")
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
private createHeader(metadata: ConfigMetadata): string {
|
|
35
|
+
private static createHeader(metadata: ConfigMetadata): string {
|
|
35
36
|
const lines: string[] = []
|
|
36
|
-
lines.push(
|
|
37
|
+
lines.push(`# ${"=".repeat(77)}`)
|
|
37
38
|
lines.push("# Webpack/Rspack Configuration Export")
|
|
38
39
|
lines.push(`# Generated: ${metadata.exportedAt}`)
|
|
39
40
|
lines.push(`# Environment: ${metadata.environment}`)
|
|
@@ -42,11 +43,15 @@ export class YamlSerializer {
|
|
|
42
43
|
if (metadata.configCount > 1) {
|
|
43
44
|
lines.push(`# Total Configs: ${metadata.configCount}`)
|
|
44
45
|
}
|
|
45
|
-
lines.push(
|
|
46
|
+
lines.push(`# ${"=".repeat(77)}`)
|
|
46
47
|
return lines.join("\n")
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
private serializeValue(
|
|
50
|
+
private serializeValue(
|
|
51
|
+
value: unknown,
|
|
52
|
+
indent: number,
|
|
53
|
+
keyPath: string
|
|
54
|
+
): string {
|
|
50
55
|
if (value === null || value === undefined) {
|
|
51
56
|
return "null"
|
|
52
57
|
}
|
|
@@ -64,11 +69,28 @@ export class YamlSerializer {
|
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
if (typeof value === "function") {
|
|
67
|
-
return this.serializeFunction(
|
|
72
|
+
return this.serializeFunction(
|
|
73
|
+
value as (...args: unknown[]) => unknown,
|
|
74
|
+
indent
|
|
75
|
+
)
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
if (value instanceof RegExp) {
|
|
71
|
-
|
|
79
|
+
// Extract pattern without surrounding slashes: "/pattern/flags" -> "pattern"
|
|
80
|
+
// Include flags as inline comment when present for semantic clarity
|
|
81
|
+
const regexStr = value.toString()
|
|
82
|
+
const lastSlash = regexStr.lastIndexOf("/")
|
|
83
|
+
const pattern = regexStr.slice(1, lastSlash)
|
|
84
|
+
const flags = regexStr.slice(lastSlash + 1)
|
|
85
|
+
|
|
86
|
+
const serializedPattern = this.serializeString(pattern, indent)
|
|
87
|
+
|
|
88
|
+
// Add flags as inline comment if present (e.g., "pattern" # flags: gi)
|
|
89
|
+
if (flags) {
|
|
90
|
+
return `${serializedPattern} # flags: ${flags}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return serializedPattern
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
if (Array.isArray(value)) {
|
|
@@ -76,10 +98,20 @@ export class YamlSerializer {
|
|
|
76
98
|
}
|
|
77
99
|
|
|
78
100
|
if (typeof value === "object") {
|
|
79
|
-
return this.serializeObject(
|
|
101
|
+
return this.serializeObject(
|
|
102
|
+
value as Record<string, unknown>,
|
|
103
|
+
indent,
|
|
104
|
+
keyPath
|
|
105
|
+
)
|
|
80
106
|
}
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
// Handle remaining types explicitly
|
|
109
|
+
if (typeof value === "symbol") return value.toString()
|
|
110
|
+
if (typeof value === "bigint") return value.toString()
|
|
111
|
+
|
|
112
|
+
// All remaining types are primitives (string, number, boolean, null, undefined)
|
|
113
|
+
// that String() handles safely - cast to exclude objects since we've already handled them
|
|
114
|
+
return String(value as string | number | boolean | null | undefined)
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
private serializeString(str: string, indent: number = 0): string {
|
|
@@ -90,15 +122,28 @@ export class YamlSerializer {
|
|
|
90
122
|
if (cleaned.includes("\n")) {
|
|
91
123
|
const lines = cleaned.split("\n")
|
|
92
124
|
const lineIndent = " ".repeat(indent + 2)
|
|
93
|
-
return
|
|
125
|
+
return `|\n${lines.map((line) => lineIndent + line).join("\n")}`
|
|
94
126
|
}
|
|
95
127
|
|
|
96
|
-
// Escape strings that need quoting
|
|
128
|
+
// Escape strings that need quoting in YAML
|
|
129
|
+
// YAML has many special characters that can cause parsing errors:
|
|
130
|
+
// : # ' " (basic delimiters)
|
|
131
|
+
// [ ] { } (flow collections)
|
|
132
|
+
// * & ! @ ` (special constructs: aliases, anchors, tags)
|
|
97
133
|
if (
|
|
98
134
|
cleaned.includes(":") ||
|
|
99
135
|
cleaned.includes("#") ||
|
|
100
136
|
cleaned.includes("'") ||
|
|
101
137
|
cleaned.includes('"') ||
|
|
138
|
+
cleaned.includes("[") ||
|
|
139
|
+
cleaned.includes("]") ||
|
|
140
|
+
cleaned.includes("{") ||
|
|
141
|
+
cleaned.includes("}") ||
|
|
142
|
+
cleaned.includes("*") ||
|
|
143
|
+
cleaned.includes("&") ||
|
|
144
|
+
cleaned.includes("!") ||
|
|
145
|
+
cleaned.includes("@") ||
|
|
146
|
+
cleaned.includes("`") ||
|
|
102
147
|
cleaned.startsWith(" ") ||
|
|
103
148
|
cleaned.endsWith(" ")
|
|
104
149
|
) {
|
|
@@ -109,7 +154,10 @@ export class YamlSerializer {
|
|
|
109
154
|
return cleaned
|
|
110
155
|
}
|
|
111
156
|
|
|
112
|
-
private serializeFunction(
|
|
157
|
+
private serializeFunction(
|
|
158
|
+
fn: (...args: unknown[]) => unknown,
|
|
159
|
+
indent: number = 0
|
|
160
|
+
): string {
|
|
113
161
|
// Get function source code
|
|
114
162
|
const source = fn.toString()
|
|
115
163
|
|
|
@@ -122,21 +170,24 @@ export class YamlSerializer {
|
|
|
122
170
|
const displayLines = truncated ? lines.slice(0, maxLines) : lines
|
|
123
171
|
|
|
124
172
|
// Clean up indentation while preserving structure
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)
|
|
173
|
+
const indentLevels = displayLines
|
|
174
|
+
.filter((l) => l.trim().length > 0)
|
|
175
|
+
.map((l) => l.match(/^\s*/)?.[0].length || 0)
|
|
176
|
+
const minIndent = indentLevels.length > 0 ? Math.min(...indentLevels) : 0
|
|
130
177
|
|
|
131
178
|
const formatted =
|
|
132
179
|
displayLines.map((line) => line.substring(minIndent)).join("\n") +
|
|
133
180
|
(truncated ? "\n..." : "")
|
|
134
181
|
|
|
135
|
-
// Use serializeString to properly handle multiline
|
|
136
|
-
return this.serializeString(formatted)
|
|
182
|
+
// Use serializeString to properly handle multiline with correct indentation
|
|
183
|
+
return this.serializeString(formatted, indent)
|
|
137
184
|
}
|
|
138
185
|
|
|
139
|
-
private serializeArray(
|
|
186
|
+
private serializeArray(
|
|
187
|
+
arr: unknown[],
|
|
188
|
+
indent: number,
|
|
189
|
+
keyPath: string
|
|
190
|
+
): string {
|
|
140
191
|
if (arr.length === 0) {
|
|
141
192
|
return "[]"
|
|
142
193
|
}
|
|
@@ -149,7 +200,7 @@ export class YamlSerializer {
|
|
|
149
200
|
const itemPath = `${keyPath}[${index}]`
|
|
150
201
|
|
|
151
202
|
// Check if this is a plugin object and add its name as a comment
|
|
152
|
-
const pluginName =
|
|
203
|
+
const pluginName = YamlSerializer.getConstructorName(item)
|
|
153
204
|
const isPlugin = pluginName && /(^|\.)plugins\[\d+\]/.test(itemPath)
|
|
154
205
|
const isEmpty =
|
|
155
206
|
typeof item === "object" &&
|
|
@@ -180,11 +231,11 @@ export class YamlSerializer {
|
|
|
180
231
|
.split("\n")
|
|
181
232
|
.filter((line: string) => line.trim().length > 0)
|
|
182
233
|
// Compute minimum leading whitespace to preserve relative indentation
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
(line: string) => line.match(/^\s*/)?.[0].length || 0
|
|
186
|
-
)
|
|
234
|
+
const indentLevels = nonEmptyLines.map(
|
|
235
|
+
(line: string) => line.match(/^\s*/)?.[0].length || 0
|
|
187
236
|
)
|
|
237
|
+
const minIndent =
|
|
238
|
+
indentLevels.length > 0 ? Math.min(...indentLevels) : 0
|
|
188
239
|
nonEmptyLines.forEach((line: string) => {
|
|
189
240
|
// Remove only the common indent, preserving relative indentation
|
|
190
241
|
lines.push(contentIndent + line.substring(minIndent))
|
|
@@ -196,11 +247,11 @@ export class YamlSerializer {
|
|
|
196
247
|
.split("\n")
|
|
197
248
|
.filter((line: string) => line.trim().length > 0)
|
|
198
249
|
// Compute minimum leading whitespace to preserve relative indentation
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
(line: string) => line.match(/^\s*/)?.[0].length || 0
|
|
202
|
-
)
|
|
250
|
+
const indentLevels = nonEmptyLines.map(
|
|
251
|
+
(line: string) => line.match(/^\s*/)?.[0].length || 0
|
|
203
252
|
)
|
|
253
|
+
const minIndent =
|
|
254
|
+
indentLevels.length > 0 ? Math.min(...indentLevels) : 0
|
|
204
255
|
nonEmptyLines.forEach((line: string) => {
|
|
205
256
|
// Remove only the common indent, preserving relative indentation
|
|
206
257
|
lines.push(contentIndent + line.substring(minIndent))
|
|
@@ -211,12 +262,16 @@ export class YamlSerializer {
|
|
|
211
262
|
}
|
|
212
263
|
})
|
|
213
264
|
|
|
214
|
-
return
|
|
265
|
+
return `\n${lines.join("\n")}`
|
|
215
266
|
}
|
|
216
267
|
|
|
217
|
-
private serializeObject(
|
|
218
|
-
|
|
219
|
-
|
|
268
|
+
private serializeObject(
|
|
269
|
+
obj: Record<string, unknown>,
|
|
270
|
+
indent: number,
|
|
271
|
+
keyPath: string
|
|
272
|
+
): string {
|
|
273
|
+
const keys = Object.keys(obj).sort()
|
|
274
|
+
const constructorName = YamlSerializer.getConstructorName(obj)
|
|
220
275
|
|
|
221
276
|
// For empty objects, show constructor name if available
|
|
222
277
|
if (keys.length === 0) {
|
|
@@ -248,6 +303,12 @@ export class YamlSerializer {
|
|
|
248
303
|
for (const line of value.split("\n")) {
|
|
249
304
|
lines.push(`${valueIndent}${line}`)
|
|
250
305
|
}
|
|
306
|
+
} else if (value instanceof RegExp || typeof value === "function") {
|
|
307
|
+
// Handle RegExp and functions explicitly before the generic object check
|
|
308
|
+
// to prevent them from being treated as empty objects (RegExp/functions
|
|
309
|
+
// have no enumerable keys but should serialize as their string representation)
|
|
310
|
+
const serialized = this.serializeValue(value, indent + 2, fullKeyPath)
|
|
311
|
+
lines.push(`${keyIndent}${key}: ${serialized}`)
|
|
251
312
|
} else if (
|
|
252
313
|
typeof value === "object" &&
|
|
253
314
|
value !== null &&
|
|
@@ -258,7 +319,7 @@ export class YamlSerializer {
|
|
|
258
319
|
} else {
|
|
259
320
|
lines.push(`${keyIndent}${key}:`)
|
|
260
321
|
const nestedLines = this.serializeObject(
|
|
261
|
-
value,
|
|
322
|
+
value as Record<string, unknown>,
|
|
262
323
|
indent + 2,
|
|
263
324
|
fullKeyPath
|
|
264
325
|
)
|
|
@@ -282,7 +343,6 @@ export class YamlSerializer {
|
|
|
282
343
|
}
|
|
283
344
|
|
|
284
345
|
private makePathRelative(str: string): string {
|
|
285
|
-
if (typeof str !== "string") return str
|
|
286
346
|
if (!isAbsolute(str)) return str
|
|
287
347
|
|
|
288
348
|
// Convert absolute paths to relative paths using path.relative
|
|
@@ -297,20 +357,35 @@ export class YamlSerializer {
|
|
|
297
357
|
return str
|
|
298
358
|
}
|
|
299
359
|
|
|
300
|
-
return
|
|
360
|
+
return `./${rel}`
|
|
301
361
|
}
|
|
302
362
|
|
|
303
363
|
/**
|
|
304
364
|
* Extracts the constructor name from an object
|
|
305
|
-
* Returns null for plain objects (Object constructor)
|
|
365
|
+
* Returns null for plain objects (Object constructor) or objects without prototypes
|
|
306
366
|
*/
|
|
307
|
-
private getConstructorName(obj:
|
|
367
|
+
private static getConstructorName(obj: unknown): string | null {
|
|
308
368
|
if (!obj || typeof obj !== "object") return null
|
|
309
369
|
if (Array.isArray(obj)) return null
|
|
310
370
|
|
|
311
|
-
|
|
312
|
-
|
|
371
|
+
// Use Object.getPrototypeOf for safer access to constructor
|
|
372
|
+
// This handles Object.create(null) and unusual prototypes correctly
|
|
373
|
+
try {
|
|
374
|
+
const proto = Object.getPrototypeOf(obj) as {
|
|
375
|
+
constructor?: { name?: string }
|
|
376
|
+
} | null
|
|
377
|
+
if (!proto || proto === Object.prototype) return null
|
|
313
378
|
|
|
314
|
-
|
|
379
|
+
const { constructor } = proto
|
|
380
|
+
if (!constructor || typeof constructor !== "function") return null
|
|
381
|
+
|
|
382
|
+
const constructorName = constructor.name
|
|
383
|
+
if (!constructorName || constructorName === "Object") return null
|
|
384
|
+
|
|
385
|
+
return constructorName
|
|
386
|
+
} catch {
|
|
387
|
+
// Handle frozen objects or other edge cases
|
|
388
|
+
return null
|
|
389
|
+
}
|
|
315
390
|
}
|
|
316
391
|
}
|
data/package/dev_server.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// These are the raw shakapacker dev server config settings from the YML file with ENV overrides applied.
|
|
2
|
+
import { DevServerConfig } from "./types"
|
|
3
|
+
|
|
2
4
|
const { isBoolean } = require("./utils/helpers")
|
|
3
5
|
const config = require("./config")
|
|
4
|
-
import { DevServerConfig } from "./types"
|
|
5
6
|
|
|
6
7
|
const envFetch = (key: string): string | boolean | undefined => {
|
|
7
8
|
const value = process.env[key]
|
data/package/env.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { load } from "js-yaml"
|
|
2
2
|
import { readFileSync } from "fs"
|
|
3
|
+
|
|
3
4
|
const defaultConfigPath = require("./utils/defaultConfigPath")
|
|
4
5
|
const configPath = require("./utils/configPath")
|
|
5
6
|
const { isFileNotFoundError } = require("./utils/errorHelpers")
|
|
@@ -76,7 +77,6 @@ const validatedRailsEnv =
|
|
|
76
77
|
initialRailsEnv && initialRailsEnv.match(regex) ? initialRailsEnv : DEFAULT
|
|
77
78
|
|
|
78
79
|
if (initialRailsEnv && validatedRailsEnv !== initialRailsEnv) {
|
|
79
|
-
/* eslint no-console:0 */
|
|
80
80
|
console.warn(
|
|
81
81
|
`[SHAKAPACKER WARNING] Environment '${initialRailsEnv}' not found in the configuration.\n` +
|
|
82
82
|
`Using '${DEFAULT}' configuration as a fallback.`
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* eslint global-require: 0 */
|
|
2
2
|
/* eslint import/no-dynamic-require: 0 */
|
|
3
3
|
|
|
4
|
+
import { Dirent } from "fs"
|
|
5
|
+
import type { Configuration, Entry } from "webpack"
|
|
6
|
+
|
|
4
7
|
const { basename, dirname, join, relative, resolve } = require("path")
|
|
5
8
|
const { existsSync, readdirSync } = require("fs")
|
|
6
|
-
import { Dirent } from "fs"
|
|
7
9
|
const extname = require("path-complete-extname")
|
|
8
|
-
// @ts-ignore: webpack is an optional peer dependency (using type-only import)
|
|
9
|
-
import type { Configuration, Entry } from "webpack"
|
|
10
10
|
const config = require("../config")
|
|
11
11
|
const { isProduction } = require("../env")
|
|
12
12
|
|
|
@@ -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
|
-
?
|
|
76
|
+
? previousPaths
|
|
77
77
|
: [previousPaths as string]
|
|
78
78
|
pathArray.push(assetPath)
|
|
79
79
|
entries[name] = pathArray
|
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
* @module environments/development
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { merge } = require("webpack-merge")
|
|
7
|
-
const config = require("../config")
|
|
8
|
-
const baseConfig = require("./base")
|
|
9
|
-
const webpackDevServerConfig = require("../webpackDevServerConfig")
|
|
10
|
-
const { runningWebpackDevServer } = require("../env")
|
|
11
|
-
const { moduleExists } = require("../utils/helpers")
|
|
12
6
|
import type {
|
|
13
7
|
WebpackConfigWithDevServer,
|
|
14
8
|
RspackConfigWithDevServer,
|
|
@@ -16,6 +10,13 @@ import type {
|
|
|
16
10
|
ReactRefreshRspackPlugin
|
|
17
11
|
} from "./types"
|
|
18
12
|
|
|
13
|
+
const { merge } = require("webpack-merge")
|
|
14
|
+
const config = require("../config")
|
|
15
|
+
const baseConfig = require("./base")
|
|
16
|
+
const webpackDevServerConfig = require("../webpackDevServerConfig")
|
|
17
|
+
const { runningWebpackDevServer } = require("../env")
|
|
18
|
+
const { moduleExists } = require("../utils/helpers")
|
|
19
|
+
|
|
19
20
|
/**
|
|
20
21
|
* Base development configuration shared between webpack and rspack
|
|
21
22
|
*/
|
|
@@ -6,17 +6,18 @@
|
|
|
6
6
|
/* eslint global-require: 0 */
|
|
7
7
|
/* eslint import/no-dynamic-require: 0 */
|
|
8
8
|
|
|
9
|
-
const { resolve } = require("path")
|
|
10
|
-
const { merge } = require("webpack-merge")
|
|
11
|
-
const baseConfig = require("./base")
|
|
12
|
-
const { moduleExists } = require("../utils/helpers")
|
|
13
|
-
const config = require("../config")
|
|
14
9
|
import type {
|
|
15
10
|
Configuration as WebpackConfiguration,
|
|
16
11
|
WebpackPluginInstance
|
|
17
12
|
} from "webpack"
|
|
18
13
|
import type { CompressionPluginConstructor } from "./types"
|
|
19
14
|
|
|
15
|
+
const { resolve } = require("path")
|
|
16
|
+
const { merge } = require("webpack-merge")
|
|
17
|
+
const baseConfig = require("./base")
|
|
18
|
+
const { moduleExists } = require("../utils/helpers")
|
|
19
|
+
const config = require("../config")
|
|
20
|
+
|
|
20
21
|
const optimizationPath = resolve(
|
|
21
22
|
__dirname,
|
|
22
23
|
"..",
|
|
@@ -27,7 +28,6 @@ const { getOptimization } = require(optimizationPath)
|
|
|
27
28
|
|
|
28
29
|
let CompressionPlugin: CompressionPluginConstructor | null = null
|
|
29
30
|
if (moduleExists("compression-webpack-plugin")) {
|
|
30
|
-
// eslint-disable-next-line global-require
|
|
31
31
|
CompressionPlugin = require("compression-webpack-plugin")
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -73,7 +73,6 @@ const productionConfig: Partial<WebpackConfiguration> = {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
if (config.useContentHash === false) {
|
|
76
|
-
// eslint-disable-next-line no-console
|
|
77
76
|
console.warn(`⚠️ WARNING
|
|
78
77
|
Setting 'useContentHash' to 'false' in the production environment (specified by NODE_ENV environment variable) is not allowed!
|
|
79
78
|
Content hashes get added to the filenames regardless of setting useContentHash in 'shakapacker.yml' to false.
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* @module environments/test
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { Configuration as WebpackConfiguration } from "webpack"
|
|
7
|
+
|
|
6
8
|
const { merge } = require("webpack-merge")
|
|
7
9
|
const config = require("../config")
|
|
8
10
|
const baseConfig = require("./base")
|
|
9
|
-
import type { Configuration as WebpackConfiguration } from "webpack"
|
|
10
11
|
|
|
11
12
|
interface TestConfig {
|
|
12
13
|
mode: "development" | "production" | "none"
|
data/package/index.ts
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
import * as webpackMerge from "webpack-merge"
|
|
5
5
|
import { resolve } from "path"
|
|
6
6
|
import { existsSync } from "fs"
|
|
7
|
-
|
|
8
|
-
import type { Configuration } from "webpack"
|
|
7
|
+
import type { Configuration, RuleSetRule } from "webpack"
|
|
9
8
|
import config from "./config"
|
|
10
9
|
import baseConfig from "./environments/base"
|
|
11
10
|
import devServer from "./dev_server"
|
|
@@ -14,8 +13,16 @@ import { moduleExists, canProcess } from "./utils/helpers"
|
|
|
14
13
|
import inliningCss from "./utils/inliningCss"
|
|
15
14
|
|
|
16
15
|
const rulesPath = resolve(__dirname, "rules", `${config.assets_bundler}.js`)
|
|
17
|
-
|
|
16
|
+
/** Array of webpack/rspack loader rules */
|
|
17
|
+
const rules = require(rulesPath) as RuleSetRule[]
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Generate webpack configuration with optional custom config.
|
|
21
|
+
*
|
|
22
|
+
* @param extraConfig - Optional webpack configuration to merge with base config
|
|
23
|
+
* @returns Final webpack configuration
|
|
24
|
+
* @throws {Error} If more than one argument is provided
|
|
25
|
+
*/
|
|
19
26
|
const generateWebpackConfig = (
|
|
20
27
|
extraConfig: Configuration = {},
|
|
21
28
|
...extraArgs: unknown[]
|
|
@@ -41,15 +48,32 @@ const generateWebpackConfig = (
|
|
|
41
48
|
return webpackMerge.merge({}, environmentConfig, extraConfig)
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The Shakapacker module exports.
|
|
53
|
+
* This object is exported via CommonJS `export =`.
|
|
54
|
+
*
|
|
55
|
+
* NOTE: This pattern is temporary and will be replaced with named exports
|
|
56
|
+
* once issue #641 is resolved.
|
|
57
|
+
*/
|
|
44
58
|
export = {
|
|
45
|
-
|
|
59
|
+
/** Shakapacker configuration from shakapacker.yml */
|
|
60
|
+
config,
|
|
61
|
+
/** Development server configuration */
|
|
46
62
|
devServer,
|
|
63
|
+
/** Generate webpack configuration with optional custom config */
|
|
47
64
|
generateWebpackConfig,
|
|
65
|
+
/** Base webpack/rspack configuration */
|
|
48
66
|
baseConfig,
|
|
67
|
+
/** Environment configuration (railsEnv, nodeEnv, etc.) */
|
|
49
68
|
env,
|
|
69
|
+
/** Array of webpack/rspack loader rules */
|
|
50
70
|
rules,
|
|
71
|
+
/** Check if a module exists in node_modules */
|
|
51
72
|
moduleExists,
|
|
73
|
+
/** Process a file if a specific loader is available */
|
|
52
74
|
canProcess,
|
|
75
|
+
/** Whether CSS should be inlined (dev server with HMR) */
|
|
53
76
|
inliningCss,
|
|
77
|
+
/** webpack-merge functions (merge, mergeWithCustomize, mergeWithRules, unique) */
|
|
54
78
|
...webpackMerge
|
|
55
79
|
}
|
data/package/loaders.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// @ts-
|
|
1
|
+
// @ts-expect-error: webpack is an optional peer dependency (using type-only import)
|
|
2
2
|
import type { LoaderDefinitionFunction } from "webpack"
|
|
3
3
|
|
|
4
4
|
export interface ShakapackerLoaderOptions {
|
|
5
|
-
[key: string]:
|
|
5
|
+
[key: string]: unknown
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export interface ShakapackerLoader {
|
|
@@ -19,38 +19,36 @@ interface OptimizationConfig {
|
|
|
19
19
|
minimizer: unknown[]
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const getOptimization = (): OptimizationConfig => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
parse
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ascii_only: true
|
|
48
|
-
}
|
|
22
|
+
const getOptimization = (): OptimizationConfig => ({
|
|
23
|
+
minimizer: [
|
|
24
|
+
tryCssMinimizer(),
|
|
25
|
+
new TerserPlugin({
|
|
26
|
+
// SHAKAPACKER_PARALLEL env var: number of parallel workers, or true for auto (os.cpus().length - 1)
|
|
27
|
+
// If not set or invalid, defaults to true (automatic parallelization)
|
|
28
|
+
parallel: process.env.SHAKAPACKER_PARALLEL
|
|
29
|
+
? Number.parseInt(process.env.SHAKAPACKER_PARALLEL, 10) || true
|
|
30
|
+
: true,
|
|
31
|
+
terserOptions: {
|
|
32
|
+
parse: {
|
|
33
|
+
// Let terser parse ecma 8 code but always output
|
|
34
|
+
// ES5 compliant code for older browsers
|
|
35
|
+
ecma: 8
|
|
36
|
+
},
|
|
37
|
+
compress: {
|
|
38
|
+
ecma: 5,
|
|
39
|
+
warnings: false,
|
|
40
|
+
comparisons: false
|
|
41
|
+
},
|
|
42
|
+
mangle: { safari10: true },
|
|
43
|
+
output: {
|
|
44
|
+
ecma: 5,
|
|
45
|
+
comments: false,
|
|
46
|
+
ascii_only: true
|
|
49
47
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
].filter(Boolean)
|
|
51
|
+
})
|
|
54
52
|
|
|
55
53
|
export = {
|
|
56
54
|
getOptimization
|
data/package/rspack/index.ts
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
// Mixed require/import syntax:
|
|
5
5
|
// - Using require() for compiled JS modules that may not have proper ES module exports
|
|
6
6
|
// - Using import for type-only imports and Node.js built-in modules
|
|
7
|
-
const webpackMerge = require("webpack-merge")
|
|
8
7
|
import { resolve } from "path"
|
|
9
8
|
import { existsSync } from "fs"
|
|
10
9
|
import type { RspackConfigWithDevServer } from "../environments/types"
|
|
10
|
+
|
|
11
|
+
const webpackMerge = require("webpack-merge")
|
|
11
12
|
const config = require("../config")
|
|
12
13
|
const baseConfig = require("../environments/base")
|
|
13
14
|
const devServer = require("../dev_server")
|