From 0888d71e4f6d00f30f24f8ef78710e3bc7029d90 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 28 May 2026 13:24:46 +0200 Subject: [PATCH] chore(build): spawn watcher tools directly instead of via npx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The watch script orchestrates 7 vite watchers + 1 tsc, each spawned via `spawn('npx', [...], { shell: true })`. That produces an extra `sh -c 'npx '` + `npm exec` wrapper per tool, just to launch another Node.js process. Resolve the bin path via `/package.json` and spawn `node ` directly with `shell: false`. Per `npm run watch` (measured on macOS, fresh start, steady state): - Processes: 26 → 17 (−9) - ps RSS sum: 181 MB → 130 MB (−51 MB) - Per wrapper `vmmap` physical_footprint: ~36 MB (8 wrappers × 36 MB ≈ ~290 MB of attributable memory eliminated; most was sitting in the macOS compressor for long-running watches) Behaviorally identical: same args reach vite/tsc. --- utils/build/build.js | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/utils/build/build.js b/utils/build/build.js index 964c2a620fdb8..8d0c491f0f84f 100644 --- a/utils/build/build.js +++ b/utils/build/build.js @@ -80,12 +80,21 @@ function filePath(relative) { } /** - * @param {string} path + * Resolve a CLI shipped by a node_modules package to an absolute path, so we + * can spawn it via `node` directly instead of going through `npx`/`npm exec` + * (which adds a shell + npm wrapper process per concurrent build). + * @param {string} pkg + * @param {string} binName * @returns {string} */ -function quotePath(path) { - return "\"" + path + "\""; +function resolveNodeBin(pkg, binName) { + // Resolve via package.json (always allowed) rather than a subpath that may + // be excluded by the package's `exports` field. + const pkgJson = require.resolve(`${pkg}/package.json`, { paths: [ROOT] }); + return path.join(path.dirname(pkgJson), require(pkgJson).bin[binName]); } +const VITE_BIN = resolveNodeBin('vite', 'vite'); +const TSC_BIN = resolveNodeBin('typescript', 'tsc'); class Step { /** @@ -859,16 +868,16 @@ const pkgSizePlugin = { // Build/watch trace viewer service worker. steps.push(new ProgramStep({ - command: 'npx', + command: process.execPath, args: [ - 'vite', + VITE_BIN, '--config', 'vite.sw.config.ts', 'build', ...(watchMode ? ['--watch', '--minify=false'] : []), ...(withSourceMaps ? ['--sourcemap=inline'] : []), ], - shell: true, + shell: false, cwd: path.join(__dirname, '..', '..', 'packages', 'trace-viewer'), concurrent: true, })); @@ -883,15 +892,15 @@ steps.push(new ProgramStep({ const webPackages = ['html-reporter', 'recorder', 'trace-viewer', 'dashboard']; for (const webPackage of webPackages) { steps.push(new ProgramStep({ - command: 'npx', + command: process.execPath, args: [ - 'vite', + VITE_BIN, 'build', ...(watchMode ? ['--watch', '--minify=false'] : []), ...(withSourceMaps ? ['--sourcemap=inline'] : []), '--clearScreen=false', ], - shell: true, + shell: false, cwd: path.join(__dirname, '..', '..', 'packages', webPackage), concurrent: true, })); @@ -900,9 +909,9 @@ for (const webPackage of webPackages) { // Build/watch extension UI pages and service worker. for (const config of ['vite.config.mts', 'vite.sw.config.mts']) { steps.push(new ProgramStep({ - command: 'npx', + command: process.execPath, args: [ - 'vite', + VITE_BIN, 'build', '--config', config, @@ -910,7 +919,7 @@ for (const config of ['vite.config.mts', 'vite.sw.config.mts']) { ...(withSourceMaps ? ['--sourcemap=inline'] : []), '--clearScreen=false', ], - shell: true, + shell: false, cwd: path.join(__dirname, '..', '..', 'packages', 'extension'), concurrent: true, })); @@ -1035,9 +1044,9 @@ copyFiles.push({ if (watchMode) { // Run TypeScript for type checking. steps.push(new ProgramStep({ - command: 'npx', - args: ['tsc', '-w', '--preserveWatchOutput', '-p', quotePath(filePath('.'))], - shell: true, + command: process.execPath, + args: [TSC_BIN, '-w', '--preserveWatchOutput', '-p', filePath('.')], + shell: false, concurrent: true, })); }