perf(desktop): serve the renderer's first assets before evaluating the main bundle (#50109)

This commit is contained in:
Luke Parker
2026-09-20 16:18:50 +10:00
committed by GitHub
parent 7fc3f68007
commit af592fb779
2 changed files with 40 additions and 7 deletions
+11 -5
View File
@@ -2,7 +2,8 @@
import { marks } from "./lifecycle/marks"
import { app } from "electron"
import { acquireApplicationLock, configureApplication } from "./lifecycle/configure"
import { createEarlyWindow } from "./windows/early"
import { createEarlyWindow } from "./windows/early"
import { rendererAssetsServed } from "./windows/protocol"
import { registerRendererScheme } from "./windows/scheme"
// This module stays small on purpose. Electron holds the ready event until the entry module has
@@ -14,10 +15,15 @@ if (acquireApplicationLock()) {
registerRendererScheme()
// Window first, then the bundle: starting the import before ready delays ready itself, because the
// module graph evaluates on the same thread Chromium needs to finish initialising.
void app.whenReady().then(() => {
marks.ready = Date.now()
createEarlyWindow()
marks.window = Date.now()
void app.whenReady().then(async () => {
marks.ready = Date.now()
createEarlyWindow()
marks.window = Date.now()
// The window's renderer is already loading. Its HTML and preloaded chunks are served from this
// thread, so the bundle waits for that burst to be answered (or a cap) before it evaluates.
if (!process.env.ELECTRON_RENDERER_URL) await rendererAssetsServed({ quietMs: 40, capMs: 400 })
marks.served = Date.now()
return import("./desktop")
})
}
+29 -2
View File
@@ -14,11 +14,39 @@ export function setProtocolReporter(reporter: ProtocolReport) {
report = reporter
}
// Requests in flight and when the last one arrived. The entry module holds the main bundle back
// until the renderer's initial burst of asset requests has been answered, because this handler
// runs on the main thread and a 100 ms module evaluation would otherwise sit between the renderer
// and its HTML.
let inflight = 0
let served = 0
let lastRequest = 0
export function rendererAssetsServed(options: { quietMs: number; capMs: number }) {
const start = Date.now()
return new Promise<void>((resolve) => {
const check = () => {
const now = Date.now()
if (now - start >= options.capMs) return resolve()
if (served > 0 && inflight === 0 && now - lastRequest >= options.quietMs) return resolve()
setTimeout(check, 5)
}
check()
})
}
export function registerRendererProtocol(rendererRoot: string) {
if (protocol.isProtocolHandled(rendererProtocol)) return
protocol.handle(rendererProtocol, async (request) => {
return serve(request, rendererRoot)
inflight++
lastRequest = Date.now()
try {
return await serve(request, rendererRoot)
} finally {
inflight--
served++
}
})
}
@@ -60,4 +88,3 @@ function addDocumentPolicy(response: Response, file: string) {
headers.set(documentPolicyHeader, jsCallStacksDocumentPolicy)
return new Response(response.body, { status: response.status, statusText: response.statusText, headers })
}