Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,32 @@ function createStartupDiagnosticsBanner(chunkName: string): string {
`
}

function createCompileCacheBanner(): string {
// Why here and not in main-process-preflight: module.enableCompileCache() is not
// retroactive — by preflight time the entire static import graph of index.ts has
// already been evaluated, so nothing of the main process's own startup would be
// cached. Prepending to the bundle entry caches the real startup graph.
// No-arg call uses Node's default cache dir (per-user, outside userData), so the
// later dev/E2E userData redirect cannot misplace it; entries are keyed by chunk
// content hash, which is identical across profiles.
return `
;(() => {
try {
const nodeModule = require('node:module')
if (typeof nodeModule.enableCompileCache === 'function') {
const result = nodeModule.enableCompileCache()
if (result && result.directory && typeof process !== 'undefined' && process.env) {
// Why: child processes (daemon, plugin-host, sidecars) inherit env and reuse the cache.
process.env.NODE_COMPILE_CACHE = result.directory
}
}
} catch {
// A cache-dir failure must never block startup.
}
})();
`
}

function createMainBootstrapPlugin() {
return {
name: 'orca-main-bootstrap',
Expand All @@ -194,6 +220,7 @@ function createMainBootstrapPlugin() {
// prelude, too late to handle a missing bootstrap dependency.
mainChunk.code =
createBootstrapFatalExitBanner() +
createCompileCacheBanner() +
createStartupDiagnosticsBanner(mainChunk.fileName) +
mainChunk.code
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/startup/main-process-preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
import { getDevInstanceIdentity, shouldApplyPreReadyAppName } from './dev-instance-identity'
import { enableRendererHeapHeadroom } from './renderer-heap-headroom'
import { configureLinuxDevShmUsage } from './linux-dev-shm-policy'
import { enableMainProcessCompileCache } from './native-code-cache'
import { isStartupDiagnosticsEnabled, logStartupDiagnostic } from './startup-diagnostics'
import { startEventLoopStallProbe } from './event-loop-stall-probe'
import {
Expand Down Expand Up @@ -365,6 +366,9 @@ function initializeMainProcessPreflight(options: MainProcessPreflightOptions): b
startCrashpadCapture()
state.crashReports = CrashReportStore.fromUserData()
state.gpuCrashDiagnostics = createGpuCrashDiagnosticsRecorder()
// Why: the main graph is already cached by the build-time banner; this only pins
// NODE_COMPILE_CACHE for forked children and covers banner-less entry paths.
enableMainProcessCompileCache()
Comment thread
pullfrog[bot] marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🤖 get_repo_knowledge executed:

get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions

Length of output: 37238


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- changed file context ---'
sed -n '1,80p;270,325p' src/main/startup/main-process-preflight.ts
printf '%s\n' '--- helper definition and usages ---'
rg -n -C 8 'enableMainProcessCompileCache' .
printf '%s\n' '--- main entry candidates and imports ---'
rg -n -C 4 'main-process-preflight|bootstrap|startup' src/main package.json 2>/dev/null | head -240

Repository: stablyai/orca

Length of output: 38689


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- compile-cache implementation ---'
cat -n src/main/startup/native-code-cache.ts
printf '%s\n' '--- preflight declaration and call sites ---'
rg -n -C 12 'export (async )?function|function .*preflight|runMainProcessPreflight|main-process-preflight' src/main --glob '*.ts' --glob '!*.test.ts'
printf '%s\n' '--- package entry configuration ---'
rg -n -C 8 '"main"|electron|src/main' package.json electron-builder.yml* forge.config.* config src --glob '*.json' --glob '*.yml' --glob '*.yaml' --glob '*.ts' --glob '*.mjs' 2>/dev/null | head -260

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- compile-cache implementation ---'
cat -n src/main/startup/native-code-cache.ts
printf '%s\n' '--- exact preflight references ---'
rg -n --glob '*.ts' --glob '*.tsx' --glob '*.mjs' --glob '*.js' 'main-process-preflight' . | head -80
printf '%s\n' '--- package main fields ---'
rg -n -C 3 '"main"|"build"|"files"' package.json
printf '%s\n' '--- likely main entry files ---'
git ls-files 'src/main/*' | head -80

Repository: stablyai/orca

Length of output: 10888


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- main entry ---'
cat -n src/main/index.ts | sed -n '1,90p'
printf '%s\n' '--- preflight declaration and relevant beginning ---'
rg -n -C 10 'runMainProcessPreflight|export const|export function|export async function' src/main/startup/main-process-preflight.ts | head -120

Repository: stablyai/orca

Length of output: 5647


🌐 Web query:

Node.js module.enableCompileCache modules compiled after enabled official documentation v24

💡 Result:

In Node.js v24, the module compile cache is a feature that allows Node.js to persist V8 code cache on disk for CommonJS, ECMAScript, and TypeScript modules to speed up subsequent compilations [1][2]. Once enabled, it improves the performance of module loading by reducing repeated compilation work, though it may introduce a slight overhead during the initial load of a module graph [1][3]. To enable this feature, you can use the module.enableCompileCache method or the NODE_COMPILE_CACHE environment variable [1][4]. Method Usage: module.enableCompileCache([options]) The method accepts an optional object: - directory (string, optional): The location to store the compile cache. If not provided, Node.js defaults to the path specified by the NODE_COMPILE_CACHE environment variable, or path.join(os.tmpdir, 'node-compile-cache') if that variable is unset [1][3]. - portable (boolean, optional): If set to true, this enables portable compile cache, allowing the cache to be reused even if the project directory is moved (a best-effort feature) [1][2]. Recommendations and Behavior: - For general use, it is recommended to call module.enableCompileCache without arguments to allow the directory to be overridden by the NODE_COMPILE_CACHE environment variable when necessary [1][4]. - Enabling the cache only affects the current Node.js instance. To extend it to child worker threads, you must either call the method within those threads or set the NODE_COMPILE_CACHE environment variable in the parent process, which can be inherited by the children [1][4][2]. - The API is designed not to throw exceptions; it is intended to be a quiet optimization. The method returns an object containing information such as the status (one of module.constants.compileCacheStatus) and the directory being used [1][5]. - To locate the compile cache directory used by a running instance, you can use the module.getCompileCacheDir method [4][2]. - You can clear the cache at any time by deleting the directory specified for storage; it will be recreated automatically the next time it is needed [1][6].

Citations:


Enable Node’s compile cache before importing the main startup graph.

src/main/index.ts imports runMainProcessPreflight, which imports enableMainProcessCompileCache. Line 305 enables the cache only after this graph has loaded, so warm launches cannot use it for the main startup modules. Initialize the cache in an entry shim before importing the bootstrap modules.

recordCrashBreadcrumb('app_started', {
packaged: app.isPackaged,
platform: process.platform,
Expand Down
49 changes: 49 additions & 0 deletions src/main/startup/native-code-cache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { enableMainProcessCompileCache } from './native-code-cache'

describe('enableMainProcessCompileCache', () => {
const originalEnv = process.env.NODE_COMPILE_CACHE

beforeEach(() => {
delete process.env.NODE_COMPILE_CACHE
})

afterEach(() => {
if (originalEnv !== undefined) {
process.env.NODE_COMPILE_CACHE = originalEnv
} else {
delete process.env.NODE_COMPILE_CACHE
}
vi.restoreAllMocks()
})

it('enables compile cache and sets process.env.NODE_COMPILE_CACHE', () => {
const result = enableMainProcessCompileCache(join(tmpdir(), 'orca-compile-cache-test'))
expect(result.enabled).toBe(true)
expect(result.directory).toBeTruthy()
expect(process.env.NODE_COMPILE_CACHE).toBe(result.directory)
})

it('is idempotent: repeat calls keep reporting the enabled state without throwing', () => {
const cacheDir = join(tmpdir(), 'orca-compile-cache-test')
const first = enableMainProcessCompileCache(cacheDir)
const second = enableMainProcessCompileCache(cacheDir)
expect(first.enabled).toBe(true)
// Node returns ALREADY_ENABLED with the original directory; the wrapper must
// still report enabled so callers cannot mistake the state for a failure.
expect(second.enabled).toBe(true)
expect(second.directory).toBeTruthy()
})

it('exposes the runtime enable result shape for the build-banner fallback path', () => {
// The build-time banner (electron.vite.config.ts) enables the cache before the
// main graph evaluates; this module is the in-process fallback. Both must agree
// on the contract: enabled + directory, never throw on a missing cache dir.
const result = enableMainProcessCompileCache()
expect(result.enabled).toBe(true)
expect(typeof result.directory).toBe('string')
expect(result.error).toBeUndefined()
})
})
46 changes: 46 additions & 0 deletions src/main/startup/native-code-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { join } from 'node:path'
import { app } from 'electron'

export type NativeCodeCacheResult = {
enabled: boolean
directory: string | null
error?: string
}

// Why: the main-process graph itself is cached by the build-time compile-cache banner
// prepended to out/main/index.js (see electron.vite.config.ts) — this runtime call is
// only the idempotent fallback for entry paths that skip the banner. It pins the
// resolved cache dir into NODE_COMPILE_CACHE so forked children reuse the same cache.
export function enableMainProcessCompileCache(customCacheDir?: string): NativeCodeCacheResult {
try {
const nodeModule = require('node:module') as {
enableCompileCache?: (dir?: string) => { status: number; directory: string }
}

if (typeof nodeModule.enableCompileCache !== 'function') {
return { enabled: false, directory: null }
}

const cacheDir =
customCacheDir ??
process.env.NODE_COMPILE_CACHE ??
(typeof app?.getPath === 'function'
? join(app.getPath('userData'), 'compile-cache')
: undefined)

const result = nodeModule.enableCompileCache(cacheDir)
if (result && result.directory) {
// Why: child processes (daemon, plugin-host, watcher) inherit env and automatically use compile cache.
process.env.NODE_COMPILE_CACHE = result.directory
return { enabled: true, directory: result.directory }
}

return { enabled: false, directory: null }
} catch (error) {
return {
enabled: false,
directory: null,
error: error instanceof Error ? error.message : String(error)
}
}
}
Loading