shakapacker 9.0.0 → 9.2.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/.gitignore +3 -0
- data/CHANGELOG.md +108 -11
- data/Gemfile.lock +1 -1
- data/README.md +182 -107
- data/bin/export-bundler-config +11 -0
- data/docs/common-upgrades.md +615 -0
- data/docs/deployment.md +52 -8
- data/docs/releasing.md +197 -0
- data/docs/rspack_migration_guide.md +120 -17
- data/docs/transpiler-migration.md +21 -0
- data/docs/troubleshooting.md +124 -23
- data/docs/typescript-migration.md +2 -1
- data/docs/using_swc_loader.md +108 -8
- data/docs/v9_upgrade.md +45 -0
- data/lib/install/bin/export-bundler-config +11 -0
- data/lib/install/bin/shakapacker +1 -1
- data/lib/install/bin/shakapacker-dev-server +1 -1
- data/lib/shakapacker/bundler_switcher.rb +329 -0
- data/lib/shakapacker/configuration.rb +28 -2
- data/lib/shakapacker/doctor.rb +65 -4
- data/lib/shakapacker/rspack_runner.rb +1 -1
- data/lib/shakapacker/runner.rb +1 -1
- data/lib/shakapacker/swc_migrator.rb +14 -6
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/webpack_runner.rb +1 -1
- data/lib/shakapacker.rb +10 -0
- data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
- data/lib/tasks/shakapacker/switch_bundler.rake +82 -0
- data/lib/tasks/shakapacker.rake +2 -1
- data/package/configExporter/cli.ts +683 -0
- data/package/configExporter/configDocs.ts +102 -0
- data/package/configExporter/fileWriter.ts +92 -0
- data/package/configExporter/index.ts +5 -0
- data/package/configExporter/types.ts +36 -0
- data/package/configExporter/yamlSerializer.ts +266 -0
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +30 -0
- data/package/environments/types.ts +22 -14
- data/package/index.ts +12 -9
- data/package/swc/index.ts +5 -3
- data/package/types/README.md +2 -1
- data/package/types/index.ts +1 -0
- data/package/utils/debug.ts +5 -5
- data/package-lock.json +13047 -0
- data/package.json +7 -1
- data/yarn.lock +261 -389
- metadata +17 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation mapping for webpack/rspack configuration keys.
|
|
3
|
+
* Used to add inline comments when exporting configs with --annotate flag.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const CONFIG_DOCS: Record<string, string> = {
|
|
7
|
+
mode: "Controls webpack optimization: 'development' (fast builds, detailed errors), 'production' (optimized, minified), or 'none'",
|
|
8
|
+
output: "Configuration for output bundles",
|
|
9
|
+
"output.filename":
|
|
10
|
+
"Bundle name template. [name]=entry name, [contenthash]=content-based hash for caching, [chunkhash]=chunk hash",
|
|
11
|
+
"output.path": "Absolute directory path where bundles are written",
|
|
12
|
+
"output.publicPath":
|
|
13
|
+
"URL prefix for loading assets in the browser (used by webpack for code splitting and asset loading)",
|
|
14
|
+
"output.chunkFilename":
|
|
15
|
+
"Template for non-entry chunk files created by code splitting",
|
|
16
|
+
"output.assetModuleFilename":
|
|
17
|
+
"Template for asset module filenames (images, fonts, etc.)",
|
|
18
|
+
"output.crossOriginLoading":
|
|
19
|
+
"Cross-origin loading setting for script tags: 'anonymous', 'use-credentials', or false",
|
|
20
|
+
"output.globalObject":
|
|
21
|
+
"Global object reference for UMD builds (e.g., 'this', 'window', 'global')",
|
|
22
|
+
devtool:
|
|
23
|
+
"Source map style: 'source-map' (full, slow), 'eval-source-map' (full, fast rebuild), 'cheap-source-map' (fast, less detail), false (none)",
|
|
24
|
+
optimization: "Code optimization settings",
|
|
25
|
+
"optimization.minimize":
|
|
26
|
+
"Enable/disable minification (true in production mode)",
|
|
27
|
+
"optimization.minimizer":
|
|
28
|
+
"Array of minimizer plugins (e.g., TerserPlugin, CssMinimizerPlugin)",
|
|
29
|
+
"optimization.splitChunks":
|
|
30
|
+
"Code splitting configuration - extracts common dependencies into separate chunks",
|
|
31
|
+
"optimization.runtimeChunk":
|
|
32
|
+
"Extract webpack runtime into separate chunk: 'single' (one runtime for all), true (one per entry), false (inline)",
|
|
33
|
+
"optimization.moduleIds":
|
|
34
|
+
"Module ID generation strategy: 'deterministic' (stable), 'named' (readable), 'natural' (numeric order)",
|
|
35
|
+
"optimization.chunkIds":
|
|
36
|
+
"Chunk ID generation strategy: 'deterministic', 'named', 'natural'",
|
|
37
|
+
module: "Configures how different file types are processed",
|
|
38
|
+
"module.rules":
|
|
39
|
+
"Array of rules defining loaders and processing for different file types",
|
|
40
|
+
plugins:
|
|
41
|
+
"Array of webpack plugins to apply (e.g., HtmlWebpackPlugin, MiniCssExtractPlugin)",
|
|
42
|
+
resolve: "Module resolution configuration",
|
|
43
|
+
"resolve.extensions":
|
|
44
|
+
"File extensions to try when resolving modules (e.g., ['.js', '.jsx', '.ts', '.tsx'])",
|
|
45
|
+
"resolve.modules":
|
|
46
|
+
"Directories to search when resolving modules (e.g., ['node_modules', 'app/javascript'])",
|
|
47
|
+
"resolve.alias":
|
|
48
|
+
"Create import aliases for modules (e.g., @components -> ./src/components)",
|
|
49
|
+
resolveLoader: "Configuration for resolving loaders",
|
|
50
|
+
"resolveLoader.modules": "Directories to search for loaders",
|
|
51
|
+
entry:
|
|
52
|
+
"Entry points for the application - where webpack starts building the dependency graph",
|
|
53
|
+
devServer: "Webpack dev server configuration (HMR, proxying, HTTPS, etc.)",
|
|
54
|
+
"devServer.port": "Port number for dev server (default: 8080)",
|
|
55
|
+
"devServer.host": "Host for dev server (e.g., 'localhost', '0.0.0.0')",
|
|
56
|
+
"devServer.hot": "Enable Hot Module Replacement (HMR)",
|
|
57
|
+
"devServer.https": "Enable HTTPS for dev server",
|
|
58
|
+
stats:
|
|
59
|
+
"Controls bundle information display: 'normal', 'verbose', 'minimal', 'errors-only', 'none'",
|
|
60
|
+
bail: "Fail the build on first error (true) or continue and report all errors (false)",
|
|
61
|
+
performance: "Performance budget configuration",
|
|
62
|
+
"performance.maxAssetSize":
|
|
63
|
+
"Maximum size (in bytes) for individual assets before webpack warns",
|
|
64
|
+
"performance.maxEntrypointSize":
|
|
65
|
+
"Maximum size (in bytes) for entry point bundles before webpack warns",
|
|
66
|
+
target:
|
|
67
|
+
"Build target environment: 'web' (browser), 'node' (Node.js), 'webworker', etc.",
|
|
68
|
+
externals:
|
|
69
|
+
"Dependencies to exclude from bundle (assumed to be available in runtime environment)",
|
|
70
|
+
cache:
|
|
71
|
+
"Build caching configuration: false (disabled), { type: 'memory' }, or { type: 'filesystem' }",
|
|
72
|
+
watch: "Enable watch mode - rebuild on file changes",
|
|
73
|
+
watchOptions: "Watch mode configuration (polling, ignored files, etc.)"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get documentation for a specific config key path.
|
|
78
|
+
* Supports nested paths like 'output.filename'.
|
|
79
|
+
*/
|
|
80
|
+
export function getDocForKey(keyPath: string): string | undefined {
|
|
81
|
+
return CONFIG_DOCS[keyPath]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get documentation for a key, trying parent paths if exact match not found.
|
|
86
|
+
* E.g., 'output.filename' -> tries 'output.filename', then 'output'
|
|
87
|
+
*/
|
|
88
|
+
export function getDocForKeyWithFallback(keyPath: string): string | undefined {
|
|
89
|
+
// Try exact match first
|
|
90
|
+
if (CONFIG_DOCS[keyPath]) {
|
|
91
|
+
return CONFIG_DOCS[keyPath]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Try parent key
|
|
95
|
+
const parts = keyPath.split(".")
|
|
96
|
+
if (parts.length > 1) {
|
|
97
|
+
const parentKey = parts.slice(0, -1).join(".")
|
|
98
|
+
return CONFIG_DOCS[parentKey]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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, quiet = false): 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
|
+
if (!quiet && process.env.VERBOSE) {
|
|
43
|
+
console.log(`[Config Exporter] Config exported to: ${filePath}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate filename for a config export
|
|
49
|
+
* Format: {bundler}-{env}-{type}.{ext}
|
|
50
|
+
* Examples:
|
|
51
|
+
* webpack-development-client.yaml
|
|
52
|
+
* rspack-production-server.yaml
|
|
53
|
+
* webpack-test-all.json
|
|
54
|
+
*/
|
|
55
|
+
generateFilename(
|
|
56
|
+
bundler: string,
|
|
57
|
+
env: string,
|
|
58
|
+
configType: "client" | "server" | "all",
|
|
59
|
+
format: "yaml" | "json" | "inspect"
|
|
60
|
+
): string {
|
|
61
|
+
const ext = format === "yaml" ? "yaml" : format === "json" ? "json" : "txt"
|
|
62
|
+
return `${bundler}-${env}-${configType}.${ext}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private writeFile(filePath: string, content: string): void {
|
|
66
|
+
writeFileSync(filePath, content, "utf8")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private ensureDirectory(dir: string): void {
|
|
70
|
+
if (!existsSync(dir)) {
|
|
71
|
+
mkdirSync(dir, { recursive: true })
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate output path and warn if writing outside cwd
|
|
77
|
+
*/
|
|
78
|
+
validateOutputPath(outputPath: string): void {
|
|
79
|
+
const absPath = resolve(outputPath)
|
|
80
|
+
const cwd = process.cwd()
|
|
81
|
+
|
|
82
|
+
const isWithin = (base: string, target: string) => {
|
|
83
|
+
const rel = relative(base, target)
|
|
84
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))
|
|
85
|
+
}
|
|
86
|
+
if (!isWithin(cwd, absPath) && !isWithin(tmpdir(), absPath)) {
|
|
87
|
+
console.warn(
|
|
88
|
+
`[Config Exporter] Warning: Writing to ${absPath} which is outside current directory (${cwd}) or temp (${tmpdir()})`
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface ExportOptions {
|
|
2
|
+
doctor?: boolean
|
|
3
|
+
save?: boolean
|
|
4
|
+
saveDir?: string
|
|
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
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ConfigMetadata {
|
|
18
|
+
exportedAt: string
|
|
19
|
+
bundler: string
|
|
20
|
+
environment: string
|
|
21
|
+
configFile: string
|
|
22
|
+
configType: "client" | "server" | "all"
|
|
23
|
+
configCount: number
|
|
24
|
+
environmentVariables: {
|
|
25
|
+
NODE_ENV?: string
|
|
26
|
+
RAILS_ENV?: string
|
|
27
|
+
CLIENT_BUNDLE_ONLY?: string
|
|
28
|
+
SERVER_BUNDLE_ONLY?: string
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FileOutput {
|
|
33
|
+
filename: string
|
|
34
|
+
content: string
|
|
35
|
+
metadata: ConfigMetadata
|
|
36
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
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
|
+
serialized
|
|
164
|
+
.split("\n")
|
|
165
|
+
.filter((line: string) => line.trim().length > 0)
|
|
166
|
+
.forEach((line: string) => {
|
|
167
|
+
lines.push(contentIndent + line)
|
|
168
|
+
})
|
|
169
|
+
} else if (serialized.includes("\n")) {
|
|
170
|
+
// For multiline values, emit marker on its own line and indent content
|
|
171
|
+
lines.push(`${itemIndent}-`)
|
|
172
|
+
serialized
|
|
173
|
+
.split("\n")
|
|
174
|
+
.filter((line: string) => line.trim().length > 0)
|
|
175
|
+
.forEach((line: string) => {
|
|
176
|
+
lines.push(contentIndent + line)
|
|
177
|
+
})
|
|
178
|
+
} else {
|
|
179
|
+
// For simple values, keep on same line
|
|
180
|
+
lines.push(`${itemIndent}- ${serialized}`)
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
return "\n" + lines.join("\n")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private serializeObject(obj: any, indent: number, keyPath: string): string {
|
|
188
|
+
const keys = Object.keys(obj)
|
|
189
|
+
if (keys.length === 0) {
|
|
190
|
+
return "{}"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const lines: string[] = []
|
|
194
|
+
const keyIndent = " ".repeat(indent)
|
|
195
|
+
const valueIndent = " ".repeat(indent + 2)
|
|
196
|
+
|
|
197
|
+
keys.forEach((key) => {
|
|
198
|
+
const value = obj[key]
|
|
199
|
+
const fullKeyPath = keyPath ? `${keyPath}.${key}` : key
|
|
200
|
+
|
|
201
|
+
// Add documentation comment if available and annotation is enabled
|
|
202
|
+
if (this.annotate) {
|
|
203
|
+
const doc = getDocForKey(fullKeyPath)
|
|
204
|
+
if (doc) {
|
|
205
|
+
lines.push(`${keyIndent}# ${doc}`)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Handle multiline strings specially with block scalar
|
|
210
|
+
if (typeof value === "string" && value.includes("\n")) {
|
|
211
|
+
lines.push(`${keyIndent}${key}: |`)
|
|
212
|
+
for (const line of value.split("\n")) {
|
|
213
|
+
lines.push(`${valueIndent}${line}`)
|
|
214
|
+
}
|
|
215
|
+
} else if (
|
|
216
|
+
typeof value === "object" &&
|
|
217
|
+
value !== null &&
|
|
218
|
+
!Array.isArray(value)
|
|
219
|
+
) {
|
|
220
|
+
if (Object.keys(value).length === 0) {
|
|
221
|
+
lines.push(`${keyIndent}${key}: {}`)
|
|
222
|
+
} else {
|
|
223
|
+
lines.push(`${keyIndent}${key}:`)
|
|
224
|
+
const nestedLines = this.serializeObject(
|
|
225
|
+
value,
|
|
226
|
+
indent + 2,
|
|
227
|
+
fullKeyPath
|
|
228
|
+
)
|
|
229
|
+
lines.push(nestedLines)
|
|
230
|
+
}
|
|
231
|
+
} else if (Array.isArray(value)) {
|
|
232
|
+
if (value.length === 0) {
|
|
233
|
+
lines.push(`${keyIndent}${key}: []`)
|
|
234
|
+
} else {
|
|
235
|
+
lines.push(`${keyIndent}${key}:`)
|
|
236
|
+
const arrayLines = this.serializeArray(value, indent + 2, fullKeyPath)
|
|
237
|
+
lines.push(arrayLines)
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
const serialized = this.serializeValue(value, indent + 2, fullKeyPath)
|
|
241
|
+
lines.push(`${keyIndent}${key}: ${serialized}`)
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
return lines.join("\n")
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private makePathRelative(str: string): string {
|
|
249
|
+
if (typeof str !== "string") return str
|
|
250
|
+
if (!isAbsolute(str)) return str
|
|
251
|
+
|
|
252
|
+
// Convert absolute paths to relative paths using path.relative
|
|
253
|
+
const rel = relative(this.appRoot, str)
|
|
254
|
+
|
|
255
|
+
if (rel === "") {
|
|
256
|
+
return "."
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// If path is outside appRoot or already absolute, keep original
|
|
260
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
261
|
+
return str
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return "./" + rel
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time type tests for RspackPlugin backward compatibility
|
|
3
|
+
* This file ensures that RspackPlugin is correctly aliased to RspackPluginInstance
|
|
4
|
+
* and maintains backward compatibility for existing consumers.
|
|
5
|
+
*
|
|
6
|
+
* These tests will fail at compile time if the types are not compatible.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RspackPlugin, RspackPluginInstance } from '../types'
|
|
10
|
+
|
|
11
|
+
// Test 1: RspackPlugin should be assignable to RspackPluginInstance
|
|
12
|
+
const testPluginToInstance = (plugin: RspackPlugin): RspackPluginInstance => plugin
|
|
13
|
+
|
|
14
|
+
// Test 2: RspackPluginInstance should be assignable to RspackPlugin
|
|
15
|
+
const testInstanceToPlugin = (instance: RspackPluginInstance): RspackPlugin => instance
|
|
16
|
+
|
|
17
|
+
// Test 3: Array compatibility
|
|
18
|
+
const testArrayCompatibility = (plugins: RspackPlugin[]): RspackPluginInstance[] => plugins
|
|
19
|
+
const testArrayCompatibilityReverse = (
|
|
20
|
+
instances: RspackPluginInstance[]
|
|
21
|
+
): RspackPlugin[] => instances
|
|
22
|
+
|
|
23
|
+
// Test 4: Optional parameter compatibility
|
|
24
|
+
const testOptionalParam = (plugin?: RspackPlugin): RspackPluginInstance | undefined => plugin
|
|
25
|
+
const testOptionalParamReverse = (
|
|
26
|
+
instance?: RspackPluginInstance
|
|
27
|
+
): RspackPlugin | undefined => instance
|
|
28
|
+
|
|
29
|
+
// Export a dummy value to make this a module
|
|
30
|
+
export const __typeTestsComplete = true
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
* These types are exported for consumer use
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
Configuration as WebpackConfiguration,
|
|
8
|
+
WebpackPluginInstance
|
|
9
|
+
} from "webpack"
|
|
7
10
|
import type { Configuration as DevServerConfiguration } from "webpack-dev-server"
|
|
11
|
+
import type { RspackPluginInstance as ImportedRspackPluginInstance } from "@rspack/core"
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Webpack configuration extended with dev server support
|
|
@@ -15,15 +19,17 @@ export interface WebpackConfigWithDevServer extends WebpackConfiguration {
|
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
|
-
* Rspack plugin interface
|
|
19
|
-
*
|
|
22
|
+
* Rspack plugin instance interface
|
|
23
|
+
* Uses the RspackPluginInstance type from @rspack/core
|
|
20
24
|
*/
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
export type RspackPluginInstance = ImportedRspackPluginInstance
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Rspack plugin type alias
|
|
29
|
+
* @deprecated Use RspackPluginInstance instead
|
|
30
|
+
* Retained for backward compatibility with existing code
|
|
31
|
+
*/
|
|
32
|
+
export type RspackPlugin = RspackPluginInstance
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
35
|
* Rspack dev server configuration
|
|
@@ -52,7 +58,7 @@ export interface RspackConfigWithDevServer {
|
|
|
52
58
|
mode?: "development" | "production" | "none"
|
|
53
59
|
devtool?: string | false
|
|
54
60
|
devServer?: RspackDevServerConfig
|
|
55
|
-
plugins?:
|
|
61
|
+
plugins?: RspackPluginInstance[]
|
|
56
62
|
module?: WebpackConfiguration["module"]
|
|
57
63
|
resolve?: WebpackConfiguration["resolve"]
|
|
58
64
|
entry?: WebpackConfiguration["entry"]
|
|
@@ -76,15 +82,17 @@ export interface CompressionPluginOptions {
|
|
|
76
82
|
/**
|
|
77
83
|
* Compression plugin constructor type
|
|
78
84
|
*/
|
|
79
|
-
export type CompressionPluginConstructor = new (
|
|
85
|
+
export type CompressionPluginConstructor = new (
|
|
86
|
+
options: CompressionPluginOptions
|
|
87
|
+
) => WebpackPluginInstance
|
|
80
88
|
|
|
81
89
|
/**
|
|
82
90
|
* React Refresh plugin types
|
|
83
91
|
*/
|
|
84
92
|
export interface ReactRefreshWebpackPlugin {
|
|
85
|
-
new(options?: Record<string, unknown>): WebpackPluginInstance
|
|
93
|
+
new (options?: Record<string, unknown>): WebpackPluginInstance
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
export interface ReactRefreshRspackPlugin {
|
|
89
|
-
new(options?: Record<string, unknown>):
|
|
90
|
-
}
|
|
97
|
+
new (options?: Record<string, unknown>): RspackPluginInstance
|
|
98
|
+
}
|
data/package/index.ts
CHANGED
|
@@ -16,18 +16,21 @@ const inliningCss = require("./utils/inliningCss")
|
|
|
16
16
|
const rulesPath = resolve(__dirname, "rules", `${config.assets_bundler}.js`)
|
|
17
17
|
const rules = require(rulesPath)
|
|
18
18
|
|
|
19
|
-
const generateWebpackConfig = (
|
|
19
|
+
const generateWebpackConfig = (
|
|
20
|
+
extraConfig: Configuration = {},
|
|
21
|
+
...extraArgs: unknown[]
|
|
22
|
+
): Configuration => {
|
|
20
23
|
if (extraArgs.length > 0) {
|
|
21
24
|
throw new Error(
|
|
22
25
|
`Invalid usage: generateWebpackConfig() accepts only one configuration object.\n\n` +
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
`You passed ${extraArgs.length + 1} arguments. Only one extra config may be passed here - use webpack-merge to merge configs before passing them to Shakapacker.\n\n` +
|
|
27
|
+
`Example:\n` +
|
|
28
|
+
` const { merge } = require('webpack-merge')\n` +
|
|
29
|
+
` const mergedConfig = merge(config1, config2, config3)\n` +
|
|
30
|
+
` const finalConfig = generateWebpackConfig(mergedConfig)\n\n` +
|
|
31
|
+
`Or if using ES6:\n` +
|
|
32
|
+
` import { merge } from 'webpack-merge'\n` +
|
|
33
|
+
` const finalConfig = generateWebpackConfig(merge(config1, config2))`
|
|
31
34
|
)
|
|
32
35
|
}
|
|
33
36
|
|
data/package/swc/index.ts
CHANGED
|
@@ -9,9 +9,11 @@ import type { RuleSetRule } from "webpack"
|
|
|
9
9
|
const JSX_FILE_REGEX = /\.(jsx|tsx)(\.erb)?$/
|
|
10
10
|
const TYPESCRIPT_FILE_REGEX = /\.(ts|tsx)(\.erb)?$/
|
|
11
11
|
|
|
12
|
-
const isJsxFile = (filename: string): boolean =>
|
|
12
|
+
const isJsxFile = (filename: string): boolean =>
|
|
13
|
+
!!filename.match(JSX_FILE_REGEX)
|
|
13
14
|
|
|
14
|
-
const isTypescriptFile = (filename: string): boolean =>
|
|
15
|
+
const isTypescriptFile = (filename: string): boolean =>
|
|
16
|
+
!!filename.match(TYPESCRIPT_FILE_REGEX)
|
|
15
17
|
|
|
16
18
|
const getCustomConfig = (): Partial<RuleSetRule> => {
|
|
17
19
|
const path = resolve("config", "swc.config.js")
|
|
@@ -37,7 +39,7 @@ const getSwcLoaderConfig = (filenameToProcess: string): RuleSetRule => {
|
|
|
37
39
|
syntax: isTs ? "typescript" : "ecmascript",
|
|
38
40
|
[jsxKey]: isJsx
|
|
39
41
|
},
|
|
40
|
-
loose:
|
|
42
|
+
loose: false
|
|
41
43
|
},
|
|
42
44
|
sourceMaps: true,
|
|
43
45
|
env: {
|
data/package/types/README.md
CHANGED
|
@@ -41,7 +41,8 @@ import type {
|
|
|
41
41
|
### Webpack/Rspack Types
|
|
42
42
|
- `WebpackConfigWithDevServer` - Webpack config with dev server
|
|
43
43
|
- `RspackConfigWithDevServer` - Rspack config with dev server
|
|
44
|
-
- `
|
|
44
|
+
- `RspackPluginInstance` - Rspack plugin instance type
|
|
45
|
+
- `RspackPlugin` - **⚠️ Deprecated:** Use `RspackPluginInstance` instead
|
|
45
46
|
- `RspackDevServerConfig` - Rspack dev server configuration
|
|
46
47
|
- `CompressionPluginOptions` - Options for compression plugin
|
|
47
48
|
- `CompressionPluginConstructor` - Constructor type for compression plugin
|