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:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user