perf(build): keep bundled scripts one-byte in V8 to cut idle memory (#33786)

V8 stores a script source as UTF-16 when any character is above U+00FF, so a
single em dash in a hot chunk doubled that chunk's resident source. Emit ASCII
(escaped) output for main, preload, renderer and worker bundles, escape the
CJK sentence marks in the prose URL regex source, and fail the renderer
boot-graph check if a boot chunk regresses to two-byte.

Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
OrcaWin
2026-10-26 02:36:45 -07:00
committed by GitHub
co-authored by m4air
parent 10cc84f324
commit 4d9c32a84b
3 changed files with 29 additions and 2 deletions
+16
View File
@@ -84,6 +84,13 @@ export function findForbiddenBootPayloads(rendererDir, payloads) {
return violations
}
// V8 keeps a whole script as UTF-16 when any char is above U+00FF, doubling that boot chunk's memory.
export function findTwoByteBootChunks(rendererDir) {
return readRendererBootGraph(rendererDir)
.chunks.filter((chunk) => /[\u0100-\uffff]/.test(fs.readFileSync(chunk.file, 'utf8')))
.map((chunk) => path.basename(chunk.file))
}
export function verifyRendererBootGraph(root = process.cwd()) {
const rendererDir = path.join(root, RENDERER_BUILD_DIR)
const { chunks, totalBytes } = readRendererBootGraph(rendererDir)
@@ -91,6 +98,15 @@ export function verifyRendererBootGraph(root = process.cwd()) {
console.log(
`Renderer boot graph: ${chunks.length} chunks, ${(totalBytes / 1024).toFixed(1)} KB minified.`
)
const twoByteChunks = findTwoByteBootChunks(rendererDir)
if (twoByteChunks.length > 0) {
console.error('Boot chunks contain chars above U+00FF that output.charset cannot escape:')
for (const chunk of twoByteChunks) {
console.error(` ${chunk}`)
}
console.error('Escape them in the source (e.g. \\u2014 inside String.raw regex sources).')
return 1
}
if (violations.length === 0) {
return 0
}
+11 -1
View File
@@ -227,6 +227,8 @@ export const electronViteConfig: UserConfig = {
preserveEntrySignatures: 'exports-only',
// Electron and sidecar launchers require stable CommonJS paths despite Rolldown’s SSR ESM default.
output: {
// V8 stores a whole script as UTF-16 when any char is above U+00FF; escaping keeps sources one-byte (half the memory).
charset: 'ascii',
format: 'cjs',
entryFileNames: '[name].js',
chunkFileNames: 'chunks/[name]-[hash].js'
@@ -260,6 +262,10 @@ export const electronViteConfig: UserConfig = {
build: {
externalizeDeps: {
exclude: ['zod']
},
rollupOptions: {
// Unminified preload keeps dependency JSDoc whose em dashes would force two-byte storage in every renderer.
output: { charset: 'ascii', comments: { legal: true, annotation: true, jsdoc: false } }
}
}
},
@@ -284,7 +290,10 @@ export const electronViteConfig: UserConfig = {
createViteCacheRetentionPlugin()
],
worker: {
format: 'es'
format: 'es',
rollupOptions: {
output: { charset: 'ascii' }
}
},
build: {
manifest: true,
@@ -296,6 +305,7 @@ export const electronViteConfig: UserConfig = {
rollupOptions: {
// Shared chunks must not import HTML entries that mount another React root.
preserveEntrySignatures: 'strict',
output: { charset: 'ascii' },
input: {
index: resolve('src/renderer/index.html'),
popout: resolve('src/renderer/popout.html'),
+2 -1
View File
@@ -1,6 +1,7 @@
// Why not all non-ascii punctuation: titles keep `・`, `–` and `·` unencoded in a url
// (`/wiki/ジョン・スミス`), so only brackets, quotes, spaces and sentence marks end one.
const PROSE_URL_TERMINATOR_SOURCE = String.raw`(?![\0-\x7f])[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Z}、。,.!?:;。、…‥—]`
// Escaped CJK sentence marks 、。,.!?:;。、…‥— keep this module's chunk one-byte in V8.
const PROSE_URL_TERMINATOR_SOURCE = String.raw`(?![\0-\x7f])[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Z}\u3001\u3002\uFF0C\uFF0E\uFF01\uFF1F\uFF1A\uFF1B\uFF61\uFF64\u2026\u2025\u2014]`
const NOT_PROSE_URL_TERMINATOR = `(?!${PROSE_URL_TERMINATOR_SOURCE})`