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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
- data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
- data/.github/workflows/claude-code-review.yml +4 -5
- data/.github/workflows/claude.yml +1 -2
- data/.github/workflows/dummy.yml +4 -4
- data/.github/workflows/generator.yml +9 -9
- data/.github/workflows/node.yml +11 -2
- data/.github/workflows/ruby.yml +16 -16
- data/.github/workflows/test-bundlers.yml +9 -9
- data/.gitignore +7 -0
- data/CHANGELOG.md +50 -4
- data/CLAUDE.md +6 -1
- data/CONTRIBUTING.md +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +35 -14
- data/TODO.md +10 -2
- data/TODO_v9.md +13 -3
- data/bin/export-bundler-config +11 -0
- data/conductor-setup.sh +1 -1
- data/conductor.json +1 -1
- data/docs/cdn_setup.md +13 -8
- data/docs/common-upgrades.md +2 -1
- data/docs/configuration.md +630 -0
- data/docs/css-modules-export-mode.md +120 -100
- data/docs/customizing_babel_config.md +16 -16
- data/docs/deployment.md +68 -6
- data/docs/developing_shakapacker.md +6 -0
- data/docs/optional-peer-dependencies.md +9 -4
- data/docs/peer-dependencies.md +17 -6
- data/docs/precompile_hook.md +342 -0
- data/docs/react.md +57 -47
- data/docs/releasing.md +195 -0
- data/docs/rspack.md +25 -21
- data/docs/rspack_migration_guide.md +363 -8
- data/docs/sprockets.md +1 -0
- data/docs/style_loader_vs_mini_css.md +12 -12
- data/docs/subresource_integrity.md +13 -7
- data/docs/transpiler-performance.md +40 -19
- data/docs/troubleshooting.md +122 -23
- data/docs/typescript-migration.md +48 -39
- data/docs/typescript.md +12 -8
- data/docs/using_esbuild_loader.md +10 -10
- data/docs/v6_upgrade.md +33 -20
- data/docs/v7_upgrade.md +8 -6
- data/docs/v8_upgrade.md +13 -12
- data/docs/v9_upgrade.md +2 -1
- data/eslint.config.fast.js +134 -0
- data/eslint.config.js +140 -0
- data/knip.ts +54 -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/install/config/shakapacker.yml +16 -5
- data/lib/shakapacker/bundler_switcher.rb +7 -0
- data/lib/shakapacker/compiler.rb +80 -0
- data/lib/shakapacker/configuration.rb +56 -2
- data/lib/shakapacker/dev_server_runner.rb +140 -1
- data/lib/shakapacker/doctor.rb +302 -57
- data/lib/shakapacker/instance.rb +8 -3
- data/lib/shakapacker/rspack_runner.rb +1 -1
- data/lib/shakapacker/runner.rb +245 -9
- 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/doctor.rake +42 -2
- data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
- data/package/babel/preset.ts +7 -4
- data/package/config.ts +42 -30
- data/package/configExporter/cli.ts +1274 -0
- data/package/configExporter/configDocs.ts +102 -0
- data/package/configExporter/configFile.ts +520 -0
- data/package/configExporter/fileWriter.ts +96 -0
- data/package/configExporter/index.ts +13 -0
- data/package/configExporter/types.ts +70 -0
- data/package/configExporter/yamlSerializer.ts +280 -0
- data/package/dev_server.ts +1 -1
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
- data/package/environments/base.ts +18 -13
- data/package/environments/development.ts +1 -1
- data/package/environments/production.ts +4 -1
- data/package/index.d.ts +50 -3
- data/package/index.d.ts.template +50 -0
- data/package/index.ts +7 -7
- data/package/loaders.d.ts +2 -2
- data/package/optimization/rspack.ts +1 -1
- data/package/plugins/rspack.ts +15 -4
- data/package/plugins/webpack.ts +7 -3
- data/package/rspack/index.ts +10 -2
- data/package/rules/raw.ts +3 -2
- data/package/rules/sass.ts +1 -1
- data/package/types/README.md +15 -13
- data/package/types/index.ts +5 -5
- data/package/types.ts +0 -1
- data/package/utils/defaultConfigPath.ts +4 -1
- data/package/utils/errorCodes.ts +129 -100
- data/package/utils/errorHelpers.ts +34 -29
- data/package/utils/getStyleRule.ts +5 -2
- data/package/utils/helpers.ts +21 -11
- data/package/utils/pathValidation.ts +43 -35
- data/package/utils/requireOrError.ts +1 -1
- data/package/utils/snakeToCamelCase.ts +1 -1
- data/package/utils/typeGuards.ts +132 -83
- data/package/utils/validateDependencies.ts +1 -1
- data/package/webpack-types.d.ts +3 -3
- data/package/webpackDevServerConfig.ts +22 -10
- data/package-lock.json +2 -2
- data/package.json +37 -28
- data/scripts/type-check-no-emit.js +1 -1
- data/test/configExporter/configFile.test.js +392 -0
- data/test/configExporter/integration.test.js +275 -0
- data/test/helpers.js +1 -1
- data/test/package/configExporter.test.js +154 -0
- data/test/package/helpers.test.js +2 -2
- data/test/package/rules/sass-version-parsing.test.js +71 -0
- data/test/package/rules/sass.test.js +2 -4
- data/test/package/rules/sass1.test.js +1 -3
- data/test/package/rules/sass16.test.js +23 -0
- data/tools/README.md +15 -5
- data/tsconfig.eslint.json +2 -9
- data/yarn.lock +1635 -1442
- metadata +29 -3
- 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
|
+
}
|
data/package/dev_server.ts
CHANGED
|
@@ -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
|
|
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 =>
|
|
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 =>
|
|
16
|
+
const testInstanceToPlugin = (instance: RspackPluginInstance): RspackPlugin =>
|
|
17
|
+
instance
|
|
16
18
|
|
|
17
19
|
// Test 3: Array compatibility
|
|
18
|
-
const testArrayCompatibility = (
|
|
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 = (
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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) =>
|
|
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:
|
|
112
|
-
|
|
113
|
-
|
|
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 {
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|