diff --git a/config/build-plugins/dev-rolldown-runtime-env.ts b/config/build-plugins/dev-rolldown-runtime-env.ts new file mode 100644 index 00000000000..af4730598be --- /dev/null +++ b/config/build-plugins/dev-rolldown-runtime-env.ts @@ -0,0 +1,13 @@ +import type { Plugin } from 'vite' +import { removeDevRolldownRuntimeEnv } from '../scripts/dev-rolldown-runtime-env.mjs' + +/** electron-vite starts Electron after the renderer server, so this runs once Rolldown has sized its pools. */ +export function createDevRolldownRuntimeEnvPlugin(): Plugin { + return { + name: 'orca-dev-rolldown-runtime-env', + apply: 'serve', + config() { + removeDevRolldownRuntimeEnv(process.env) + } + } +} diff --git a/config/build-plugins/dev-serve-config.ts b/config/build-plugins/dev-serve-config.ts index 59d95dbf722..3c024b0489a 100644 --- a/config/build-plugins/dev-serve-config.ts +++ b/config/build-plugins/dev-serve-config.ts @@ -1,4 +1,6 @@ import type { UserConfig } from 'electron-vite' +import { DEV_RUNTIME_ENV_KEYS } from '../scripts/dev-rolldown-runtime-env.mjs' +import { createDevRolldownRuntimeEnvPlugin } from './dev-rolldown-runtime-env' import { withBundledRendererDev } from './renderer-bundled-dev' /** Development-server overrides; Electron Vite also uses Vite's build command for the dev main process. */ @@ -8,14 +10,27 @@ export function createDevServeConfig( ): UserConfig { // run-electron-vite-dev.mjs probes a per-worktree port and reports it; strict keeps that report true. const rendererPort = Number.parseInt(env.ORCA_DEV_RENDERER_PORT ?? '', 10) + const hasPort = Number.isInteger(rendererPort) + // Only the dev wrapper sets runtime keys; without them there is nothing to strip. + const stripsRuntimeEnv = Boolean(env[DEV_RUNTIME_ENV_KEYS]) return withBundledRendererDev( { ...config, renderer: - Number.isInteger(rendererPort) && config.renderer + config.renderer && (hasPort || stripsRuntimeEnv) ? { ...config.renderer, - server: { ...config.renderer.server, port: rendererPort, strictPort: true } + ...(hasPort + ? { server: { ...config.renderer.server, port: rendererPort, strictPort: true } } + : {}), + ...(stripsRuntimeEnv + ? { + plugins: [ + ...(config.renderer.plugins ?? []), + createDevRolldownRuntimeEnvPlugin() + ] + } + : {}) } : config.renderer, main: { diff --git a/config/scripts/dev-rolldown-runtime-env.mjs b/config/scripts/dev-rolldown-runtime-env.mjs new file mode 100644 index 00000000000..f5771c7de32 --- /dev/null +++ b/config/scripts/dev-rolldown-runtime-env.mjs @@ -0,0 +1,39 @@ +import os from 'node:os' + +export const DEV_RUNTIME_ENV_KEYS = 'ORCA_DEV_RUNTIME_ENV_KEYS' + +// Measured idle electron-vite RSS 4.6GB -> 3.0GB with no HMR or startup regression. +const DEV_THREADS = '4' +const DEV_RUNTIME_DEFAULTS = { + // mimalloc otherwise keeps freed Rolldown arenas resident for the whole dev session. + MIMALLOC_PURGE_DELAY: '0', + ROLLDOWN_WORKER_THREADS: DEV_THREADS, + RAYON_NUM_THREADS: DEV_THREADS +} + +/** Sizes the dev server's Rolldown runtime; records added keys so Electron's environment can drop them. */ +export function withDevRolldownRuntimeEnv(env, availableParallelism = os.availableParallelism()) { + const added = Object.entries(DEV_RUNTIME_DEFAULTS).filter( + ([key]) => + env[key] === undefined && + (key === 'MIMALLOC_PURGE_DELAY' || availableParallelism > Number(DEV_THREADS)) + ) + if (added.length === 0) { + return env + } + return { + ...env, + ...Object.fromEntries(added), + [DEV_RUNTIME_ENV_KEYS]: added.map(([key]) => key).join(',') + } +} + +/** Rolldown has read these before the renderer server starts; dev Electron's terminals must not inherit them. */ +export function removeDevRolldownRuntimeEnv(env) { + for (const key of (env[DEV_RUNTIME_ENV_KEYS] ?? '').split(',')) { + if (key) { + delete env[key] + } + } + delete env[DEV_RUNTIME_ENV_KEYS] +} diff --git a/config/scripts/dev-rolldown-runtime-env.test.mjs b/config/scripts/dev-rolldown-runtime-env.test.mjs new file mode 100644 index 00000000000..881e4a824cc --- /dev/null +++ b/config/scripts/dev-rolldown-runtime-env.test.mjs @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' +import { + removeDevRolldownRuntimeEnv, + withDevRolldownRuntimeEnv +} from './dev-rolldown-runtime-env.mjs' + +describe('dev Rolldown runtime env', () => { + it('removes only the keys it added, keeping caller choices', () => { + const env = withDevRolldownRuntimeEnv({ RAYON_NUM_THREADS: '2', PATH: '/bin' }, 24) + expect(env).toMatchObject({ + MIMALLOC_PURGE_DELAY: '0', + ROLLDOWN_WORKER_THREADS: '4', + RAYON_NUM_THREADS: '2' + }) + removeDevRolldownRuntimeEnv(env) + expect(env).toEqual({ RAYON_NUM_THREADS: '2', PATH: '/bin' }) + }) + + it('keeps Rolldown thread defaults on small hosts', () => { + expect(withDevRolldownRuntimeEnv({}, 4)).toEqual({ + MIMALLOC_PURGE_DELAY: '0', + ORCA_DEV_RUNTIME_ENV_KEYS: 'MIMALLOC_PURGE_DELAY' + }) + }) +}) diff --git a/config/scripts/run-electron-vite-dev.mjs b/config/scripts/run-electron-vite-dev.mjs index a97a879648e..a02c13b3145 100644 --- a/config/scripts/run-electron-vite-dev.mjs +++ b/config/scripts/run-electron-vite-dev.mjs @@ -39,6 +39,7 @@ import { recordDevWebClientInputs } from './dev-web-client-freshness.mjs' import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs' +import { withDevRolldownRuntimeEnv } from './dev-rolldown-runtime-env.mjs' import { copyPrivateTree } from './space-sharing-copy.mjs' import { DEV_BUNDLE_ID, @@ -584,7 +585,7 @@ if (!isHelpOrVersion) { const forwardedArgs = ['dev', ...forwardedRaw, ...forwardedExtras] const child = spawn(process.execPath, [electronViteCli, ...forwardedArgs], { stdio: 'inherit', - env: process.env, + env: withDevRolldownRuntimeEnv(process.env), // Own process group so Ctrl+C terminates electron-vite’s Electron descendants on macOS too. detached: process.platform !== 'win32' })