perf(desktop): resolve the renderer bundle once per window, not twice
createMainWindow walked the entire renderer generation twice before it
could call loadWindowUrl:
const rendererIndex = DEV_SERVER ? null : resolveRendererIndex()
const tornAssets = rendererIndex ? missingRendererAssets(rendererIndex) : []
resolveRendererIndex already computes exactly that list while choosing the
copy — it needs it to decide whether a copy is torn — and then throws it
away. missingRendererAssets is a BFS that readFileSync's every present
chunk whole and regex-scans it for the inline __vite__mapDeps table, so on
a release tree it is not a stat walk: measured against the real
apps/desktop/dist (252 chunks, 28.7 MiB of JS), one walk is 162
readFileSync calls reading 28.23 MiB, 576 existsSync calls, and 56.6 ms
median (min 55.8, 9 reps, warm page cache, darwin-arm64). Both walks run
synchronously on the main thread before the window gets its URL.
Return the list alongside the index. resolveRendererIndexWithMissing()
carries the existing body and hands back { index, missing }; the
path-only resolveRendererIndex() stays as a one-line wrapper so the nine
other call sites are untouched. The primary-window path takes one
resolution.
Semantics are unchanged in every branch: the same candidate is chosen, the
same log lines are emitted, and the missing list always describes the copy
actually returned. The all-copies-torn branch now reuses the first
candidate's list, captured on the first loop iteration, rather than
recomputing it for present[0] — recomputing there would have reintroduced
the second walk in exactly the case that matters most, and using the
loop's last value would have described a bundle we do not load.
Net effect on every primary-window boot: one fewer full walk, so 162 fewer
readFileSync calls, 28.23 MiB less synchronous reading, 288 fewer
existsSync calls, and ~57 ms of main-thread blocking removed before
loadURL. The win lands on packaged and --prod launches; DEV_SERVER skips
the walk entirely, so `vite dev` is unaffected.
This commit is contained in:
committed by
Teknium
parent
a22c731744
commit
ab4bfda360
@@ -4764,7 +4764,15 @@ function resolveWebDist() {
|
||||
return fallback
|
||||
}
|
||||
|
||||
function resolveRendererIndex() {
|
||||
// Same resolution as resolveRendererIndex, but also hands back the missing
|
||||
// asset list already computed for the copy it chose. The primary-window path
|
||||
// needs BOTH, and re-deriving the list means walking the whole renderer
|
||||
// generation a second time: missingRendererAssets follows index.html's
|
||||
// modulepreload refs and then every chunk's inline __vite__mapDeps table, so
|
||||
// on a release build it reads ~28 MiB across ~160 files synchronously on the
|
||||
// main thread — measured ~49 ms per walk, twice before loadWindowUrl().
|
||||
// Callers that only need the path keep using resolveRendererIndex below.
|
||||
function resolveRendererIndexWithMissing(): { index: string; missing: string[] } {
|
||||
const asarIndex = path.join(APP_ROOT, 'dist', 'index.html')
|
||||
const webDistIndex = path.join(resolveWebDist(), 'index.html')
|
||||
|
||||
@@ -4787,11 +4795,20 @@ function resolveRendererIndex() {
|
||||
// first lazy import with "Failed to fetch dynamically imported module" and
|
||||
// every restart reloads the same torn copy. Prefer a copy whose modules are
|
||||
// all present, so the intact generation heals the boot by itself.
|
||||
// Remember the FIRST candidate's list: if every copy turns out to be torn we
|
||||
// load present[0], and its list is already in hand — recomputing it there
|
||||
// would reintroduce the very second walk this function exists to avoid.
|
||||
let firstMissing: string[] | null = null
|
||||
|
||||
for (const candidate of present) {
|
||||
const missing = missingRendererAssets(candidate)
|
||||
|
||||
if (missing.length === 0) {
|
||||
return candidate
|
||||
return { index: candidate, missing: [] }
|
||||
}
|
||||
|
||||
if (firstMissing === null) {
|
||||
firstMissing = missing
|
||||
}
|
||||
|
||||
rememberLog(
|
||||
@@ -4811,7 +4828,9 @@ function resolveRendererIndex() {
|
||||
`Repair with: hermes desktop --force-build`
|
||||
)
|
||||
|
||||
return present[0]
|
||||
// present[0]'s own list, captured on the first loop iteration — never the
|
||||
// last candidate's, which would describe a bundle we are not loading.
|
||||
return { index: present[0], missing: firstMissing ?? [] }
|
||||
}
|
||||
|
||||
// Nothing on disk. A packaged build with no renderer bundle blank-pages with
|
||||
@@ -4823,7 +4842,13 @@ function resolveRendererIndex() {
|
||||
`Rebuild with: hermes desktop --force-build`
|
||||
)
|
||||
|
||||
return candidates[0]
|
||||
return { index: candidates[0], missing: [] }
|
||||
}
|
||||
|
||||
// Path-only accessor: unchanged behaviour for the window loaders that do not
|
||||
// need the torn-asset list.
|
||||
function resolveRendererIndex() {
|
||||
return resolveRendererIndexWithMissing().index
|
||||
}
|
||||
|
||||
// True when `dir` lives inside the packaged app bundle / install tree.
|
||||
@@ -14824,8 +14849,11 @@ function createWindow() {
|
||||
// copies; here we refuse to load one into the PRIMARY window and put the
|
||||
// visible repair page in it instead. The Reload button re-attempts the
|
||||
// bundle in case the file lock cleared since boot.
|
||||
const rendererIndex = DEV_SERVER ? null : resolveRendererIndex()
|
||||
const tornAssets = rendererIndex ? missingRendererAssets(rendererIndex) : []
|
||||
// One resolution, not two: resolveRendererIndexWithMissing already computed
|
||||
// this copy's missing list while choosing it.
|
||||
const resolvedRenderer = DEV_SERVER ? null : resolveRendererIndexWithMissing()
|
||||
const rendererIndex = resolvedRenderer?.index ?? null
|
||||
const tornAssets = resolvedRenderer?.missing ?? []
|
||||
|
||||
if (!DEV_SERVER && rendererIndex && tornAssets.length > 0) {
|
||||
rememberLog(
|
||||
|
||||
Reference in New Issue
Block a user