Files
bunext/dist/functions/server/server-post-build-fn.js
T
tbenandClaude Opus 5.5 4d4a79a6dd 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]>
2026-11-05 07:32:33 +01:00

122 lines
4.7 KiB
JavaScript

import _ from "lodash";
import grabPageComponent from "./web-pages/grab-page-component";
/**
* # Push a client-only HMR update
* Sends the new client bundle to every controller whose page is in
* `changed_pages`, without re-running server functions. The payload carries
* no `page_props`, so the browser keeps its current props and only swaps the
* hydration script/CSS. Used right after the hydration build so the browser
* doesn't wait for the SSR rebuild.
*/
export function pushClientUpdate(changed_pages) {
if (!global.BUNEXT_HMR_CONTROLLERS?.[0] || !global.BUNEXT_BUNDLER_CTX_MAP) {
return;
}
const changed_set = new Set(changed_pages);
for (let i = global.BUNEXT_HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.BUNEXT_HMR_CONTROLLERS[i];
const local_path = controller?.target_map?.local_path;
if (!controller || !local_path || !changed_set.has(local_path)) {
continue;
}
const target_artifact = global.BUNEXT_BUNDLER_CTX_MAP[local_path];
const payload = target_artifact?.path
? { target_map: target_artifact }
: { reload: true };
try {
controller.controller.enqueue(`event: update\ndata: ${JSON.stringify(payload)}\n\n`);
if (target_artifact?.path) {
controller.target_map = target_artifact;
}
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
}
}
export default async function serverPostBuildFn(params) {
if (!global.BUNEXT_HMR_CONTROLLERS?.[0] || !global.BUNEXT_BUNDLER_CTX_MAP) {
return;
}
const changed_set = params?.changed_pages && params.changed_pages.length > 0
? new Set(params.changed_pages)
: undefined;
const reload_payload = { reload: true };
const reload_enqueue = `event: update\ndata: ${JSON.stringify(reload_payload)}\n\n`;
for (let i = global.BUNEXT_HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.BUNEXT_HMR_CONTROLLERS[i];
if (!controller) {
continue;
}
if (!controller.target_map?.local_path) {
continue;
}
// Only touch controllers whose page actually changed.
if (changed_set && !changed_set.has(controller.target_map.local_path)) {
continue;
}
if (params?.reload_all_controllers) {
try {
controller.controller.enqueue(reload_enqueue);
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
continue;
}
const target_artifact = global.BUNEXT_BUNDLER_CTX_MAP[controller.target_map.local_path];
if (!target_artifact?.local_path) {
try {
controller.controller.enqueue(reload_enqueue);
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
continue;
}
// Re-render with this tab's own URL and cookie, never another
// client's request.
const mock_req = new Request(controller.page_url);
if (controller.page_cookie) {
mock_req.headers.set("cookie", controller.page_cookie);
}
const page_component = await grabPageComponent({
req: mock_req,
return_server_res_only: true,
is_hydration: true,
// This runs inside serverPostBuildFn (post-build of fullRebuild);
// letting it trigger another rebuild/retry would cascade.
no_retry: true,
});
if (page_component instanceof Response) {
continue;
}
const { serverRes } = page_component || {};
const final_artifact = {
// `page_cookie` holds the raw Cookie header (incl. HttpOnly
// cookies), so it must never be sent back to page JS.
..._.omit(controller, ["controller", "page_cookie", "page_url"]),
target_map: target_artifact,
};
if (!target_artifact) {
delete final_artifact.target_map;
}
// Always replace so prior error props cannot linger
final_artifact.page_props = serverRes || {};
try {
let final_data = {};
if (global.BUNEXT_ROOT_FILE_UPDATED) {
final_data = reload_payload;
}
else {
final_data = final_artifact;
}
controller.controller.enqueue(`event: update\ndata: ${JSON.stringify(final_data)}\n\n`);
global.BUNEXT_ROOT_FILE_UPDATED = false;
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
}
}