From 8952499f560fbc3cdaf646ba95f21d5726a52477 Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Thu, 30 Jul 2026 15:33:28 +0800 Subject: [PATCH 1/3] perf(dev): bound light webpack runtime --- scripts/dev/webpack-config-light.test.cjs | 35 ++++++++++++++++++ webpack.config.js | 44 +++++++++++++---------- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/scripts/dev/webpack-config-light.test.cjs b/scripts/dev/webpack-config-light.test.cjs index 38301c1927..eab250329c 100644 --- a/scripts/dev/webpack-config-light.test.cjs +++ b/scripts/dev/webpack-config-light.test.cjs @@ -49,6 +49,41 @@ test("light dev disables webpack dev-server browser client", () => { assert.equal(htmlPlugin?.userOptions?.retryMainScriptLoad, false); }); +test("development cache stays memory-only and bounded to one generation", () => { + for (const lightDev of [false, true]) { + const config = withEnv( + { + ORGII_LIGHT_DEV: lightDev ? "true" : null, + FAST_DEV: lightDev ? "true" : null, + DEV_SOURCEMAPS: lightDev ? "false" : null, + }, + () => createWebpackConfig({}, { mode: "development" }) + ); + + assert.deepEqual(config.cache, { + type: "memory", + maxGenerations: 1, + }); + } +}); + +test("production keeps its isolated filesystem cache", () => { + for (const fastProd of [false, true]) { + const config = withEnv( + { + FAST_PROD: fastProd ? "true" : null, + }, + () => createWebpackConfig({}, { mode: "production" }) + ); + + assert.equal(config.cache.type, "filesystem"); + assert.equal( + config.cache.version, + `${fastProd ? "prod-fast" : "prod"}-11` + ); + } +}); + test("retrying main script loader disables static HTML injection in dev", () => { const config = withEnv( { diff --git a/webpack.config.js b/webpack.config.js index 0ad0e91df9..9892332007 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -62,24 +62,32 @@ module.exports = (env, argv) => { chunkFilename: isProduction ? "[name].[contenthash].js" : "[name].js", clean: true, }, - cache: { - type: "filesystem", - // FAST_PROD swaps both the transpiler and minimizer. Keep it in a - // separate filesystem-cache namespace so webpack cannot reuse cached - // runtime-condition code generated by the regular production pipeline. - // Mixing those caches can leave async chunks guarded by a stale runtime - // id (for example `__webpack_require__.j == 9121` while the emitted - // runtime id is `49121`), which turns otherwise valid imports into - // `undefined` only in the packaged app. - version: `${ - isProduction ? (useFastProd ? "prod-fast" : "prod") : "dev" - }-11`, - buildDependencies: { - config: [__filename], - }, - // Don't compress - avoids sass serialization issues - compression: false, - }, + cache: isProduction + ? { + type: "filesystem", + // FAST_PROD swaps both the transpiler and minimizer. Keep it in a + // separate filesystem-cache namespace so webpack cannot reuse cached + // runtime-condition code generated by the regular production pipeline. + // Mixing those caches can leave async chunks guarded by a stale runtime + // id (for example `__webpack_require__.j == 9121` while the emitted + // runtime id is `49121`), which turns otherwise valid imports into + // `undefined` only in the packaged app. + version: `${useFastProd ? "prod-fast" : "prod"}-11`, + buildDependencies: { + config: [__filename], + }, + // Don't compress - avoids sass serialization issues + compression: false, + } + : { + // Webpack's filesystem cache serialized 1–4+ GB pack files for this + // app and deserializing them pushed the long-lived dev server to a + // 7–9 GB physical footprint. Dev mode already keeps the active + // compilation graph in memory, so retain only one unused generation + // for rebuilds and never persist that graph across process restarts. + type: "memory", + maxGenerations: 1, + }, // Snapshot: use timestamps for node_modules instead of content hashing. // node_modules rarely change during a dev session; timestamp checks are much faster. snapshot: { From de08b0cdb28d1945e1f71dd4eb09b8a18ef6db1e Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Mon, 10 Aug 2026 12:00:56 +0800 Subject: [PATCH 2/3] perf(dev): scope bounded cache to light mode --- scripts/dev/webpack-config-light.test.cjs | 42 +++++++++++++++-------- webpack.config.js | 23 +++++++------ 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/scripts/dev/webpack-config-light.test.cjs b/scripts/dev/webpack-config-light.test.cjs index eab250329c..740c0129a3 100644 --- a/scripts/dev/webpack-config-light.test.cjs +++ b/scripts/dev/webpack-config-light.test.cjs @@ -49,22 +49,34 @@ test("light dev disables webpack dev-server browser client", () => { assert.equal(htmlPlugin?.userOptions?.retryMainScriptLoad, false); }); -test("development cache stays memory-only and bounded to one generation", () => { - for (const lightDev of [false, true]) { - const config = withEnv( - { - ORGII_LIGHT_DEV: lightDev ? "true" : null, - FAST_DEV: lightDev ? "true" : null, - DEV_SOURCEMAPS: lightDev ? "false" : null, - }, - () => createWebpackConfig({}, { mode: "development" }) - ); +test("light development cache stays memory-only and bounded", () => { + const config = withEnv( + { + ORGII_LIGHT_DEV: "true", + FAST_DEV: "true", + DEV_SOURCEMAPS: "false", + }, + () => createWebpackConfig({}, { mode: "development" }) + ); - assert.deepEqual(config.cache, { - type: "memory", - maxGenerations: 1, - }); - } + assert.deepEqual(config.cache, { + type: "memory", + maxGenerations: 1, + }); +}); + +test("standard development keeps its filesystem cache", () => { + const config = withEnv( + { + ORGII_LIGHT_DEV: null, + FAST_DEV: null, + DEV_SOURCEMAPS: null, + }, + () => createWebpackConfig({}, { mode: "development" }) + ); + + assert.equal(config.cache.type, "filesystem"); + assert.equal(config.cache.version, "dev-11"); }); test("production keeps its isolated filesystem cache", () => { diff --git a/webpack.config.js b/webpack.config.js index 9892332007..1cd44d3fba 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -62,8 +62,16 @@ module.exports = (env, argv) => { chunkFilename: isProduction ? "[name].[contenthash].js" : "[name].js", clean: true, }, - cache: isProduction + cache: isLightDev ? { + // Webpack's filesystem cache can serialize gigabyte-scale pack files + // for this app and raises the light-dev rebuild RSS ceiling. Retain + // only one unused generation and never persist the light-mode + // compilation graph across restarts. + type: "memory", + maxGenerations: 1, + } + : { type: "filesystem", // FAST_PROD swaps both the transpiler and minimizer. Keep it in a // separate filesystem-cache namespace so webpack cannot reuse cached @@ -72,21 +80,14 @@ module.exports = (env, argv) => { // id (for example `__webpack_require__.j == 9121` while the emitted // runtime id is `49121`), which turns otherwise valid imports into // `undefined` only in the packaged app. - version: `${useFastProd ? "prod-fast" : "prod"}-11`, + version: isProduction + ? `${useFastProd ? "prod-fast" : "prod"}-11` + : "dev-11", buildDependencies: { config: [__filename], }, // Don't compress - avoids sass serialization issues compression: false, - } - : { - // Webpack's filesystem cache serialized 1–4+ GB pack files for this - // app and deserializing them pushed the long-lived dev server to a - // 7–9 GB physical footprint. Dev mode already keeps the active - // compilation graph in memory, so retain only one unused generation - // for rebuilds and never persist that graph across process restarts. - type: "memory", - maxGenerations: 1, }, // Snapshot: use timestamps for node_modules instead of content hashing. // node_modules rarely change during a dev session; timestamp checks are much faster. From 888e4d590eb62679663183df12f82d3aee57370c Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Mon, 10 Aug 2026 13:07:44 +0800 Subject: [PATCH 3/3] perf(dev): cap light webpack heap --- scripts/dev/tauri-dev-processes.cjs | 21 +++++++++++++ scripts/dev/tauri-dev-processes.test.cjs | 38 +++++++++++++++++++++-- scripts/dev/tauri.js | 8 ++--- scripts/dev/webpack-config-light.test.cjs | 5 +-- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/scripts/dev/tauri-dev-processes.cjs b/scripts/dev/tauri-dev-processes.cjs index 066f4cacfa..d473a196db 100644 --- a/scripts/dev/tauri-dev-processes.cjs +++ b/scripts/dev/tauri-dev-processes.cjs @@ -1,6 +1,26 @@ const http = require("node:http"); const https = require("node:https"); +const LIGHT_DEV_WEBPACK_MAX_OLD_SPACE_MIB = 1792; + +function applyLightDevWebpackMemoryLimit(env, { lightDev = false } = {}) { + const nextEnv = { ...env }; + if (!lightDev) return nextEnv; + + const nodeOptions = nextEnv.NODE_OPTIONS?.trim() || ""; + const hasExplicitLimit = + /(?:^|\s)--max[-_]old[-_]space[-_]size(?:=|\s)/u.test(nodeOptions); + if (hasExplicitLimit) return nextEnv; + + nextEnv.NODE_OPTIONS = [ + nodeOptions, + `--max-old-space-size=${LIGHT_DEV_WEBPACK_MAX_OLD_SPACE_MIB}`, + ] + .filter(Boolean) + .join(" "); + return nextEnv; +} + function createTauriArgs({ features = [], devUrl } = {}) { const args = ["dev"]; if (features.length > 0) { @@ -138,6 +158,7 @@ async function waitForDevServerAsset( } module.exports = { + applyLightDevWebpackMemoryLimit, createDevUrl, createFrontendScriptName, createTauriArgs, diff --git a/scripts/dev/tauri-dev-processes.test.cjs b/scripts/dev/tauri-dev-processes.test.cjs index d78df59141..a482ebcd30 100644 --- a/scripts/dev/tauri-dev-processes.test.cjs +++ b/scripts/dev/tauri-dev-processes.test.cjs @@ -3,6 +3,7 @@ const fs = require("node:fs"); const test = require("node:test"); const { + applyLightDevWebpackMemoryLimit, createDevUrl, createTauriArgs, formatElapsedMs, @@ -12,6 +13,33 @@ const { waitForDevServerAsset, } = require("./tauri-dev-processes.cjs"); +test("light webpack gets a bounded default heap without overriding user options", () => { + assert.deepEqual( + applyLightDevWebpackMemoryLimit( + { NODE_OPTIONS: "--trace-warnings", KEEP: "yes" }, + { lightDev: true } + ), + { + NODE_OPTIONS: "--trace-warnings --max-old-space-size=1792", + KEEP: "yes", + } + ); + assert.deepEqual( + applyLightDevWebpackMemoryLimit( + { NODE_OPTIONS: "--max_old_space_size=3072" }, + { lightDev: true } + ), + { NODE_OPTIONS: "--max_old_space_size=3072" } + ); + assert.deepEqual( + applyLightDevWebpackMemoryLimit( + { NODE_OPTIONS: "--trace-warnings" }, + { lightDev: false } + ), + { NODE_OPTIONS: "--trace-warnings" } + ); +}); + const tauriDevSource = fs.readFileSync("scripts/dev/tauri.js", "utf8"); const tauriLauncherSource = fs.readFileSync( "scripts/dev/tauri-launcher.cjs", @@ -81,8 +109,14 @@ test("tauri dev npm scripts use a cross-platform launcher", () => { }); test("tauri dev launcher detaches Unix stdin without requiring setsid", () => { - assert.match(tauriLauncherSource, /detached:\s*process\.platform !== "win32"/); - assert.match(tauriLauncherSource, /stdio:\s*\["ignore",\s*"inherit",\s*"inherit"\]/); + assert.match( + tauriLauncherSource, + /detached:\s*process\.platform !== "win32"/ + ); + assert.match( + tauriLauncherSource, + /stdio:\s*\["ignore",\s*"inherit",\s*"inherit"\]/ + ); assert.match(tauriLauncherSource, /env\.ORGII_LIGHT_DEV = "true"/); assert.match(tauriLauncherSource, /SIGINT:\s*130/); assert.match(tauriLauncherSource, /SIGTERM:\s*143/); diff --git a/scripts/dev/tauri.js b/scripts/dev/tauri.js index 6b58c9bdfa..111220e289 100755 --- a/scripts/dev/tauri.js +++ b/scripts/dev/tauri.js @@ -18,6 +18,7 @@ const { applyDefaultDiagnosticsEndpoint, } = require("../tauri/diagnostics-endpoint.cjs"); const { + applyLightDevWebpackMemoryLimit, createDevUrl, createFrontendScriptName, createTauriArgs, @@ -494,9 +495,8 @@ function pipeProcessLines(childProcess, onLine) { } function startFrontendDev() { - const scriptName = createFrontendScriptName({ - lightDev: process.env.ORGII_LIGHT_DEV === "true", - }); + const lightDev = process.env.ORGII_LIGHT_DEV === "true"; + const scriptName = createFrontendScriptName({ lightDev }); const pnpmCli = createPnpmCliCommand(); setWebpackStatus("starting dev server..."); @@ -508,7 +508,7 @@ function startFrontendDev() { { stdio: ["ignore", "pipe", "pipe"], cwd: rootDir, - env: cleanChildEnv(), + env: applyLightDevWebpackMemoryLimit(cleanChildEnv(), { lightDev }), } ); diff --git a/scripts/dev/webpack-config-light.test.cjs b/scripts/dev/webpack-config-light.test.cjs index 740c0129a3..eb8089e7e0 100644 --- a/scripts/dev/webpack-config-light.test.cjs +++ b/scripts/dev/webpack-config-light.test.cjs @@ -89,10 +89,7 @@ test("production keeps its isolated filesystem cache", () => { ); assert.equal(config.cache.type, "filesystem"); - assert.equal( - config.cache.version, - `${fastProd ? "prod-fast" : "prod"}-11` - ); + assert.equal(config.cache.version, `${fastProd ? "prod-fast" : "prod"}-11`); } });