Files
a723540163 perf(build): minify the CLI bundle (whitespace + syntax, keep identifiers) (#1743)
dist/cli.mjs shipped unminified at 21.7MB; whitespace+syntax minification
cuts it to ~16MB (-26%) and shaves V8 parse time on every invocation.
Identifier mangling stays off because the codebase matches
constructor.name (errors.ts, toolExecution.ts, useCanUseTool). The SDK
bundle stays unminified — its React/Ink leak check greps import syntax
that minification would rewrite.

The bundle guard's missing-module tripwire relied on Bun's
`// missing-module-stub:<path>` module-boundary comments, which
minification strips. The stub loader now also emits the marker as a
side-effecting string push (survives treeshaking and syntax-minify), and
the guard parses both forms.

Review fix (CodeRabbit + jatmn): the marker parser previously truncated
paths at the first backslash or space, so a JSON-escaped Windows marker
like "missing-module-stub:C:\\Users\\Jane Doe\\...\\src\\...\\foo.js" was
captured as a useless `C:` (or `C:\\Users\\Jane`) fragment and canonicalized
to the wrong key — letting a newly stubbed module slip past the tripwire on
Windows/spaced build hosts. Parse each marker form to its correct
terminator instead: the string literal runs to its matching (back-ref)
closing quote consuming escaped pairs, and Bun's comment runs to end of
line. Extract canonicalStub() + the parser into scripts/stubMarkerGuard.ts
so the logic is unit-testable, and add regression tests for Windows,
spaced, comment-form, and multi-marker-per-line cases.

Verified: build green, bundle ~16MB minified, guard passes against the real
bundle, stub-guard tests pass, --version works through the minified bundle.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-06-25 12:34:59 +08:00

66 lines
3.0 KiB
TypeScript

/**
* Pure helpers for the post-build "missing-module stub" tripwire in build.ts.
*
* Extracted so the marker parsing can be unit-tested against synthetic bundle
* text (e.g. Windows-style paths that never appear in a macOS/Linux build).
*/
/**
* Canonicalize a `missing-module-stub:` marker to a host-stable, src-relative
* key. The per-importer scanner records each stub as the resolved absolute
* source path, which differs only by the repo-root prefix across build hosts
* (and uses `\` separators on Windows). Keying on the path from `src/` onward
* without extension is stable across hosts yet still path-specific, so a stub
* named `constants.ts` in one directory cannot mask a different `constants.ts`
* elsewhere (a basename-only key would).
*/
export function canonicalStub(marker: string): string {
const normalized = marker.split(/[\\/]/).join('/')
const srcIdx = normalized.lastIndexOf('/src/')
const fromSrc = srcIdx >= 0 ? normalized.slice(srcIdx + 1) : normalized
return fromSrc.replace(/\.(?:[cm]?[jt]sx?)$/, '')
}
// The marker appears in two forms and each has its own terminator, so each is
// matched with a delimiter-correct pattern rather than a single character class.
// Matching to the right delimiter (not "stop at the first space/backslash") is
// what keeps paths containing spaces (`C:\\Users\\Jane Doe\\...`) or backslashes
// intact for canonicalStub().
// Form 1 — the string literal the stub loader emits via
// `JSON.stringify(\`missing-module-stub:${path}\`)`, which survives minification.
// Capture from the opening quote to the matching (back-referenced) closing quote,
// consuming escaped pairs (`\\.`) so an escaped quote/backslash never ends the
// match early. Bun may re-quote with ' or " when minifying, hence the backref.
const STUB_MARKER_STRING_PATTERN =
/(["'])missing-module-stub:((?:\\.|(?!\1).)*)\1/g
// Form 2 — Bun's module-boundary comment in unminified builds. The path is raw
// (single separators, no escaping) and runs to end of line.
const STUB_MARKER_COMMENT_PATTERN = /\/\/[^\S\n]*missing-module-stub:([^\n]*)/g
// Reverse the JS/JSON string escaping applied to Form 1 (`\\` -> `\`, `\"` -> `"`)
// so canonicalStub() sees real path separators instead of doubled backslashes.
function unescapeStringLiteral(value: string): string {
return value.replace(/\\(.)/g, '$1')
}
/**
* Extract every missing-module stub marker from a built bundle, mapping each
* canonical src-relative key to the raw (separator-normalized) marker text,
* which is kept for human-readable diagnostics.
*/
export function collectBundleStubs(bundleText: string): Map<string, string> {
const stubbed = new Map<string, string>()
const record = (marker: string): void => {
stubbed.set(canonicalStub(marker), marker)
}
for (const m of bundleText.matchAll(STUB_MARKER_STRING_PATTERN)) {
record(unescapeStringLiteral(m[2]!))
}
for (const m of bundleText.matchAll(STUB_MARKER_COMMENT_PATTERN)) {
record(m[1]!.replace(/\s+$/, ''))
}
return stubbed
}