Refactor HMR server-side watcher. Move to fs.watch instead of the inbuild esbuild.watch

This commit is contained in:
Benjamin Toby 2026-03-21 05:21:51 +01:00
parent 5fbcd0b8d6
commit 71f3598709
9 changed files with 38 additions and 14 deletions

View File

@ -81,6 +81,8 @@ export default async function allPagesBundler(params?: Params) {
const elapsed = (performance.now() - buildStart).toFixed(0); const elapsed = (performance.now() - buildStart).toFixed(0);
log.success(`[Built] in ${elapsed}ms`); log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
if (params?.exit_after_first_build) { if (params?.exit_after_first_build) {
process.exit(); process.exit();
} }
@ -115,6 +117,6 @@ export default async function allPagesBundler(params?: Params) {
if (params?.watch) { if (params?.watch) {
global.BUNDLER_CTX = ctx; global.BUNDLER_CTX = ctx;
global.BUNDLER_CTX.watch(); // global.BUNDLER_CTX.watch();
} }
} }

View File

@ -54,9 +54,9 @@ export default function grabClientHydrationScript({ page_local_path }: Params) {
txt += `const pageProps = window.__PAGE_PROPS__ || {};\n`; txt += `const pageProps = window.__PAGE_PROPS__ || {};\n`;
if (does_root_exist) { if (does_root_exist) {
txt += `const component = <Root {...pageProps}><Page {...pageProps} /></Root>\n`; txt += `const component = <Root suppressHydrationWarning={true} {...pageProps}><Page {...pageProps} /></Root>\n`;
} else { } else {
txt += `const component = <Page {...pageProps} />\n`; txt += `const component = <Page suppressHydrationWarning={true} {...pageProps} />\n`;
} }
txt += `if (window.${ClientRootComponentWindowName}?.render) {\n`; txt += `if (window.${ClientRootComponentWindowName}?.render) {\n`;

View File

@ -3,6 +3,7 @@ import type { BunextServerRouteConfig, BunxRouteParams } from "../../types";
import grabRouteParams from "../../utils/grab-route-params"; import grabRouteParams from "../../utils/grab-route-params";
import grabConstants from "../../utils/grab-constants"; import grabConstants from "../../utils/grab-constants";
import grabRouter from "../../utils/grab-router"; import grabRouter from "../../utils/grab-router";
import isDevelopment from "../../utils/is-development";
type Params = { type Params = {
req: Request; req: Request;
@ -11,6 +12,7 @@ type Params = {
export default async function ({ req, server }: Params): Promise<Response> { export default async function ({ req, server }: Params): Promise<Response> {
const url = new URL(req.url); const url = new URL(req.url);
const is_dev = isDevelopment();
const { MBInBytes, ServerDefaultRequestBodyLimitBytes } = grabConstants(); const { MBInBytes, ServerDefaultRequestBodyLimitBytes } = grabConstants();
@ -37,7 +39,10 @@ export default async function ({ req, server }: Params): Promise<Response> {
const routeParams: BunxRouteParams = await grabRouteParams({ req }); const routeParams: BunxRouteParams = await grabRouteParams({ req });
const module = await import(match.filePath); const now = Date.now();
const import_path = is_dev ? `${match.filePath}?t=${now}` : match.filePath;
const module = await import(import_path);
const config = module.config as BunextServerRouteConfig | undefined; const config = module.config as BunextServerRouteConfig | undefined;
const contentLength = req.headers.get("content-length"); const contentLength = req.headers.get("content-length");
@ -70,5 +75,9 @@ export default async function ({ req, server }: Params): Promise<Response> {
server, server,
} as BunxRouteParams); } as BunxRouteParams);
if (is_dev) {
res.headers.set("Cache-Control", "no-cache, no-store, must-revalidate");
}
return res; return res;
} }

View File

@ -16,7 +16,17 @@ export default function watcher() {
async (event, filename) => { async (event, filename) => {
if (!filename) return; if (!filename) return;
if (event !== "rename") return; if (event !== "rename") {
if (
filename.match(/\.(tsx?|jsx?|css)$/) &&
global.BUNDLER_CTX
) {
if (global.RECOMPILING) return;
global.RECOMPILING = true;
await global.BUNDLER_CTX.rebuild();
}
return;
}
if (!filename.match(/^pages\//)) return; if (!filename.match(/^pages\//)) return;
if (filename.match(/\/(--|\()/)) return; if (filename.match(/\/(--|\()/)) return;

View File

@ -16,9 +16,8 @@ export default async function grabPageBundledReactComponent({
try { try {
let tsx = ``; let tsx = ``;
const server_res_json = EJSON.stringify(server_res || {})?.replace( const server_res_json = JSON.stringify(
/"/g, EJSON.stringify(server_res || {}) ?? "{}",
'\\"',
); );
if (root_file) { if (root_file) {
@ -27,7 +26,7 @@ export default async function grabPageBundledReactComponent({
tsx += `import Page from "${file_path}"\n`; tsx += `import Page from "${file_path}"\n`;
tsx += `export default function Main() {\n\n`; tsx += `export default function Main() {\n\n`;
tsx += `const props = JSON.parse("${server_res_json}")\n\n`; tsx += `const props = JSON.parse(${server_res_json})\n\n`;
tsx += ` return (\n`; tsx += ` return (\n`;
if (root_file) { if (root_file) {
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`; tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;

View File

@ -27,6 +27,7 @@ export default async function grabPageComponent({
}: Params): Promise<GrabPageComponentRes> { }: Params): Promise<GrabPageComponentRes> {
const url = req?.url ? new URL(req.url) : undefined; const url = req?.url ? new URL(req.url) : undefined;
const router = global.ROUTER; const router = global.ROUTER;
const now = Date.now();
let routeParams: BunxRouteParams | undefined = undefined; let routeParams: BunxRouteParams | undefined = undefined;
@ -77,7 +78,7 @@ export default async function grabPageComponent({
const { root_file } = grabRootFile(); const { root_file } = grabRootFile();
const module: BunextPageModule = await import(file_path); const module: BunextPageModule = await import(`${file_path}?t=${now}`);
if (debug) { if (debug) {
log.info(`module:`, module); log.info(`module:`, module);
@ -106,7 +107,10 @@ export default async function grabPageComponent({
try { try {
if (routeParams) { if (routeParams) {
const serverData = await module["server"]?.(routeParams); const serverData = await module["server"]?.({
...routeParams,
query: { ...routeParams.query, ...match?.query },
});
return { return {
...serverData, ...serverData,
...default_props, ...default_props,

View File

@ -16,7 +16,7 @@ export default async function ({ bundledMap }: Params) {
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`; script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
script += ` try {\n`; script += ` try {\n`;
script += ` const data = JSON.parse(event.data);\n`; script += ` const data = JSON.parse(event.data);\n`;
// script += ` console.log("data", data);\n`; script += ` console.log("data", data);\n`;
// script += ` const modulePath = \`/\${data.target_map.path}\`;\n\n`; // script += ` const modulePath = \`/\${data.target_map.path}\`;\n\n`;
// script += ` const modulePath = \`/${AppData["ClientHMRPath"]}?href=\${window.location.href}&t=\${Date.now()}\`;\n\n`; // script += ` const modulePath = \`/${AppData["ClientHMRPath"]}?href=\${window.location.href}&t=\${Date.now()}\`;\n\n`;

View File

@ -221,7 +221,7 @@ export type BunextPageModuleServerReturn<
* Expiry time of the cache in seconds * Expiry time of the cache in seconds
*/ */
cacheExpiry?: number; cacheExpiry?: number;
url: BunextPageModuleServerReturnURLObject; url?: BunextPageModuleServerReturnURLObject;
}; };
export type BunextPageModuleServerReturnURLObject = URL & {}; export type BunextPageModuleServerReturnURLObject = URL & {};

View File

@ -42,7 +42,7 @@ function grabPageDirRecursively({ page_dir }: { page_dir: string }) {
continue; continue;
} }
if (page.match(/\(|\)|--/)) { if (page.match(/\(|\)|--|\/api\//)) {
continue; continue;
} }