Push HMR updates before the SSR rebuild in dev

Client-only edits are sent to the browser right after the hydration build,
without re-running server functions; SSR rebuilds afterwards and props are
re-fetched only when a .server.ts output changed or after fullRebuild. Page
requests wait for an in-flight SSR rebuild. The SSR build no longer runs
Tailwind, the PostCSS processor is reused across rebuilds, and the watcher
debounce drops from 200ms to 50ms.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-11-05 07:32:33 +01:00
co-authored by Claude Opus 5.5
parent ed788dff97
commit 4d4a79a6dd
23 changed files with 460 additions and 98 deletions
@@ -3,7 +3,9 @@ import { describe, expect, test, beforeEach, afterEach } from "bun:test";
// NOTE: this file runs the REAL grabPageComponent (no module mock). The
// callers use `return_server_res_only` + `no_retry`, so no bundling or
// rebuilds happen as long as the fake router + artifact map are set up.
import serverPostBuildFn from "../../../functions/server/server-post-build-fn";
import serverPostBuildFn, {
pushClientUpdate,
} from "../../../functions/server/server-post-build-fn";
function makeController(page_url: string, target_map?: { local_path: string }) {
const enqueued: string[] = [];
@@ -146,4 +148,53 @@ describe("server-post-build-fn", () => {
expect(changed.enqueued.length).toBe(1);
expect(untouched.enqueued.length).toBe(0);
});
});
describe("pushClientUpdate", () => {
test("pushes the new bundle without page_props to changed pages only", () => {
const changed = makeController("http://localhost/about", {
local_path: "pages/about.tsx",
});
const untouched = makeController("http://localhost/missing", {
local_path: "pages/404.tsx",
});
global.BUNEXT_HMR_CONTROLLERS = [changed as any, untouched as any];
pushClientUpdate(["pages/about.tsx"]);
expect(changed.enqueued.length).toBe(1);
const data = JSON.parse(
changed.enqueued[0]!.replace(/^event: update\ndata: /, ""),
);
expect(data.target_map.path).toBe("pages/about.js");
expect(data.page_props).toBeUndefined();
expect(data.reload).toBeUndefined();
expect(untouched.enqueued.length).toBe(0);
});
test("reloads a changed page that has no bundled artifact", () => {
const c = makeController("http://localhost/new-page", {
local_path: "pages/new-page.tsx",
});
global.BUNEXT_HMR_CONTROLLERS = [c as any];
pushClientUpdate(["pages/new-page.tsx"]);
expect(c.enqueued.length).toBe(1);
expect(c.enqueued[0]).toContain('"reload":true');
});
test("drops controllers whose stream is closed", () => {
const c = makeController("http://localhost/about", {
local_path: "pages/about.tsx",
});
c.controller.enqueue = () => {
throw new Error("closed");
};
global.BUNEXT_HMR_CONTROLLERS = [c as any];
pushClientUpdate(["pages/about.tsx"]);
expect(global.BUNEXT_HMR_CONTROLLERS.length).toBe(0);
});
});
});