mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
perf(cli): warm the preview route before the browser opens
The browser's first request for a project's preview paid the server's cold compiler import and first bundle in the request path, after the studio shell's own first-layout stall, so both costs landed on the user's time-to-first-frame in series. The CLI now issues one fire-and-forget request for the preview route from openStudioBrowser, the single funnel for every launch path, before the --no-open early return so pasted URLs benefit too. Measured on the demo project: first client request 231ms before, 101ms after; the ETag 304 path afterwards is under 1ms. The fetch carries a 10s abort so an unsettled connection cannot keep an otherwise-finished CLI process alive, matching the package's existing convention. The shader query params the player appends are not part of the route's cache key, so the plain route URL warms the same entry.
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
foregroundPreviewReadyPayload,
|
foregroundPreviewReadyPayload,
|
||||||
handlePreviewKillAll,
|
handlePreviewKillAll,
|
||||||
handlePreviewList,
|
handlePreviewList,
|
||||||
|
openStudioBrowser,
|
||||||
previewLaunchMode,
|
previewLaunchMode,
|
||||||
previewLaunchModeError,
|
previewLaunchModeError,
|
||||||
previewPortError,
|
previewPortError,
|
||||||
@@ -21,12 +22,19 @@ import {
|
|||||||
studioSummaryUrls,
|
studioSummaryUrls,
|
||||||
waitForStudioChildClose,
|
waitForStudioChildClose,
|
||||||
} from "./preview.js";
|
} from "./preview.js";
|
||||||
|
import { openBrowser } from "../utils/openBrowser.js";
|
||||||
|
|
||||||
|
vi.mock("../utils/openBrowser.js", async (importOriginal) => ({
|
||||||
|
...(await importOriginal<typeof import("../utils/openBrowser.js")>()),
|
||||||
|
openBrowser: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const tempDirs: string[] = [];
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
process.exitCode = undefined;
|
process.exitCode = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -493,3 +501,30 @@ describe("waitForStudioChildClose", () => {
|
|||||||
expect(signalTarget.off).toHaveBeenCalledTimes(2);
|
expect(signalTarget.off).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("openStudioBrowser", () => {
|
||||||
|
it("warms the preview route before the browser launch, and also under --no-open", () => {
|
||||||
|
const order: string[] = [];
|
||||||
|
vi.stubGlobal(
|
||||||
|
"fetch",
|
||||||
|
vi.fn((url: string) => {
|
||||||
|
order.push(`fetch ${url}`);
|
||||||
|
return Promise.resolve({ body: null } as Response);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
vi.mocked(openBrowser)
|
||||||
|
.mockReset()
|
||||||
|
.mockImplementation(() => {
|
||||||
|
order.push("openBrowser");
|
||||||
|
});
|
||||||
|
|
||||||
|
openStudioBrowser("http://127.0.0.1:4567", "Launch #1", "/tmp/launch");
|
||||||
|
openStudioBrowser("http://127.0.0.1:4567", "demo", "/tmp/demo", { noOpen: true });
|
||||||
|
|
||||||
|
expect(order).toEqual([
|
||||||
|
"fetch http://127.0.0.1:4567/api/projects/Launch%20%231/preview",
|
||||||
|
"openBrowser",
|
||||||
|
"fetch http://127.0.0.1:4567/api/projects/demo/preview",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1258,12 +1258,20 @@ function previewLifecycleSession(options: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function openStudioBrowser(
|
export function openStudioBrowser(
|
||||||
url: string,
|
url: string,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
options?: BrowserLaunchOptions,
|
options?: BrowserLaunchOptions,
|
||||||
): void {
|
): void {
|
||||||
|
// Pay the route's cold compiler import here so the browser's first request
|
||||||
|
// doesn't. The shader query params the player appends aren't in the route's
|
||||||
|
// ETag; the timeout stops an unsettled fetch holding the CLI's event loop.
|
||||||
|
void fetch(`${url}/api/projects/${encodeURIComponent(projectName)}/preview`, {
|
||||||
|
signal: AbortSignal.timeout(10_000),
|
||||||
|
})
|
||||||
|
.then((res) => res.body?.cancel())
|
||||||
|
.catch(() => {});
|
||||||
if (options?.noOpen) return;
|
if (options?.noOpen) return;
|
||||||
openBrowser(studioDeepLink(url, projectName, projectDir), {
|
openBrowser(studioDeepLink(url, projectName, projectDir), {
|
||||||
browserPath: options?.browserPath,
|
browserPath: options?.browserPath,
|
||||||
|
|||||||
Reference in New Issue
Block a user