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
+6
View File
@@ -5,6 +5,7 @@ import getCache from "../../cache/get-cache";
import generateWebPageResponseFromComponentReturn from "./generate-web-page-response-from-component-return";
import grabPageComponent from "./grab-page-component";
import grabPageErrorComponent from "./grab-page-error-component";
import { awaitSSRRebuild } from "../../bundler/pages-ssr-context-bundler";
export default async function handleWebPages({ req, }) {
try {
const is_dev = isDevelopment();
@@ -31,6 +32,11 @@ export default async function handleWebPages({ req, }) {
}
catch (error) { }
}
// HMR pushes client updates before the SSR rebuild finishes; a page
// load in that window waits so SSR HTML matches the new bundle.
if (is_dev) {
await awaitSSRRebuild();
}
const componentRes = await grabPageComponent({
req,
});
@@ -1,3 +1,16 @@
import * as esbuild from "esbuild";
type Params = {
/**
* Run `.css` files through Tailwind/PostCSS. When `false`, every `.css`
* import loads as an empty stylesheet (for builds whose CSS output is
* never used, e.g. SSR).
*/
process_css?: boolean;
};
export declare function createTailwindEsbuildPlugin({ process_css, }?: Params): esbuild.Plugin;
/**
* Resolves `.css` imports like the Tailwind plugin but loads them empty.
*/
export declare const cssStubEsbuildPlugin: esbuild.Plugin;
declare const tailwindEsbuildPlugin: esbuild.Plugin;
export default tailwindEsbuildPlugin;
+51 -38
View File
@@ -8,42 +8,55 @@ import grabDirNames from "../../../utils/grab-dir-names";
import { log } from "../../../utils/log";
const { ROOT_DIR } = grabDirNames();
let error_logged = false;
const tailwindEsbuildPlugin = {
name: "tailwindcss",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
try {
const source = await readFile(args.path, "utf-8");
const result = await postcss([tailwindcss()]).process(source, {
from: args.path,
});
error_logged = false;
return { contents: result.css, loader: "css" };
}
catch (error) {
return { errors: [{ text: error.message }] };
}
});
build.onResolve({ filter: /\.css$/ }, async (args) => {
const css_path = path.resolve(args.resolveDir, args.path.replace(/\@\//g, ROOT_DIR + "/"));
const does_path_exist = existsSync(css_path);
if (!does_path_exist && !error_logged) {
const err_msg = `CSS Error: ${css_path} file does not exist.`;
log.error(err_msg);
error_logged = true;
// return {
// errors: [
// {
// text: err_msg,
// },
// ],
// pluginName: "tailwindcss",
// };
}
return {
path: css_path,
};
});
},
};
// One processor for the whole process, so the Tailwind plugin's internal
// state survives across rebuilds instead of being recreated per file load.
let css_processor;
function grabCSSProcessor() {
if (!css_processor) {
css_processor = postcss([tailwindcss()]);
}
return css_processor;
}
export function createTailwindEsbuildPlugin({ process_css = true, } = {}) {
return {
name: process_css ? "tailwindcss" : "css-stub",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
if (!process_css) {
return { contents: "", loader: "css" };
}
try {
const source = await readFile(args.path, "utf-8");
const result = await grabCSSProcessor().process(source, {
from: args.path,
});
error_logged = false;
return { contents: result.css, loader: "css" };
}
catch (error) {
return { errors: [{ text: error.message }] };
}
});
build.onResolve({ filter: /\.css$/ }, async (args) => {
const css_path = path.resolve(args.resolveDir, args.path.replace(/\@\//g, ROOT_DIR + "/"));
const does_path_exist = existsSync(css_path);
if (!does_path_exist && !error_logged) {
const err_msg = `CSS Error: ${css_path} file does not exist.`;
log.error(err_msg);
error_logged = true;
}
return {
path: css_path,
};
});
},
};
}
/**
* Resolves `.css` imports like the Tailwind plugin but loads them empty.
*/
export const cssStubEsbuildPlugin = createTailwindEsbuildPlugin({
process_css: false,
});
const tailwindEsbuildPlugin = createTailwindEsbuildPlugin();
export default tailwindEsbuildPlugin;