Update HMR. Make page reload when __root.tsx file is changed.

This commit is contained in:
Benjamin Toby 2026-03-22 12:09:24 +01:00
parent 49d6781170
commit c3e1341ff8
17 changed files with 60 additions and 258 deletions

View File

@ -22,5 +22,6 @@ declare global {
var PAGES_SRC_WATCHER: FSWatcher | undefined; var PAGES_SRC_WATCHER: FSWatcher | undefined;
var CURRENT_VERSION: string | undefined; var CURRENT_VERSION: string | undefined;
var PAGE_FILES: PageFiles[]; var PAGE_FILES: PageFiles[];
var ROOT_FILE_UPDATED: boolean;
} }
export default function bunextInit(): Promise<void>; export default function bunextInit(): Promise<void>;

View File

@ -1,2 +1 @@
import type { ServeOptions } from "bun"; export default function (): Promise<Bun.Serve.Options<any>>;
export default function (): Promise<ServeOptions>;

View File

@ -25,7 +25,16 @@ export default async function serverPostBuildFn({ artifacts }) {
final_artifact.page_props = serverRes; final_artifact.page_props = serverRes;
} }
try { try {
controller.controller.enqueue(`event: update\ndata: ${JSON.stringify(final_artifact)}\n\n`); let final_data = {};
console.log("global.ROOT_FILE_UPDATED", global.ROOT_FILE_UPDATED);
if (global.ROOT_FILE_UPDATED) {
final_data = { reload: true };
}
else {
final_data = final_artifact;
}
controller.controller.enqueue(`event: update\ndata: ${JSON.stringify(final_data)}\n\n`);
global.ROOT_FILE_UPDATED = false;
} }
catch { catch {
global.HMR_CONTROLLERS.splice(i, 1); global.HMR_CONTROLLERS.splice(i, 1);

View File

@ -1 +1 @@
export default function startServer(): Promise<Bun.Server<undefined>>; export default function startServer(): Promise<Bun.Server<any>>;

View File

@ -37,6 +37,9 @@ export default async function watcher() {
if (global.RECOMPILING) if (global.RECOMPILING)
return; return;
global.RECOMPILING = true; global.RECOMPILING = true;
if (full_file_path.match(/\_\_root\.tsx?$/)) {
global.ROOT_FILE_UPDATED = true;
}
await rewritePagesModule({ page_url: full_file_path }); await rewritePagesModule({ page_url: full_file_path });
await global.BUNDLER_CTX.rebuild(); await global.BUNDLER_CTX.rebuild();
} }

View File

@ -25,13 +25,18 @@ export default async function (params) {
script += `window.addEventListener("beforeunload", () => hmr.close());\n`; script += `window.addEventListener("beforeunload", () => hmr.close());\n`;
script += `hmr.addEventListener("update", async (event) => {\n`; script += `hmr.addEventListener("update", async (event) => {\n`;
script += ` if (event?.data) {\n`; script += ` if (event?.data) {\n`;
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
script += ` try {\n`; script += ` try {\n`;
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`; script += ` document.getElementById("__bunext_error_overlay")?.remove();\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 += ` if (data.reload) {\n`;
script += ` console.log(\`Root Changes Detected. Reloading Page ...\`);\n`;
script += ` window.location.reload();\n`;
script += ` return;\n`;
script += ` }\n`;
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
script += ` if (data.page_props) {\n`; script += ` if (data.page_props) {\n`;
script += ` window.${ClientWindowPagePropsName} = data.page_props\n`; script += ` window.${ClientWindowPagePropsName} = data.page_props;\n`;
script += ` }\n`; script += ` }\n`;
script += ` const oldCSSLink = document.querySelector('link[rel="stylesheet"]');\n`; script += ` const oldCSSLink = document.querySelector('link[rel="stylesheet"]');\n`;
script += ` if (data.target_map.css_path) {\n`; script += ` if (data.target_map.css_path) {\n`;

View File

@ -1,7 +0,0 @@
import type { BundlerCTXMap } from "../../../types";
type Params = {
tsx: string;
out_file: string;
};
export default function writeHMRTsxModule({ tsx, out_file }: Params): Promise<Pick<BundlerCTXMap, "path" | "css_path" | "hash" | "type"> | undefined>;
export {};

View File

@ -1,106 +0,0 @@
import * as esbuild from "esbuild";
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
import path from "path";
export default async function writeHMRTsxModule({ tsx, out_file }) {
try {
const build = await esbuild.build({
stdin: {
contents: tsx,
resolveDir: process.cwd(),
loader: "tsx",
},
bundle: true,
format: "esm",
target: "es2020",
platform: "browser",
external: [
"react",
"react-dom",
"react/jsx-runtime",
"react-dom/client",
],
minify: true,
jsx: "automatic",
outfile: out_file,
plugins: [tailwindEsbuildPlugin],
metafile: true,
});
const artifacts = Object.entries(build.metafile.outputs)
.filter(([, meta]) => meta.entryPoint)
.map(([outputPath, meta]) => {
const cssPath = meta.cssBundle || undefined;
return {
path: outputPath,
hash: path.basename(outputPath, path.extname(outputPath)),
type: outputPath.endsWith(".css")
? "text/css"
: "text/javascript",
css_path: cssPath,
};
});
return artifacts?.[0];
}
catch (error) {
return undefined;
}
}
// import * as esbuild from "esbuild";
// import path from "path";
// import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
// const hmrExternalsPlugin: esbuild.Plugin = {
// name: "hmr-globals",
// setup(build) {
// const mapping: Record<string, string> = {
// react: "__REACT__",
// "react-dom": "__REACT_DOM__",
// "react-dom/client": "__REACT_DOM_CLIENT__",
// "react/jsx-runtime": "__JSX_RUNTIME__",
// };
// const filter = new RegExp(
// `^(${Object.keys(mapping)
// .map((k) => k.replace("/", "\\/"))
// .join("|")})$`,
// );
// build.onResolve({ filter }, (args) => {
// return { path: args.path, namespace: "hmr-global" };
// });
// build.onLoad({ filter: /.*/, namespace: "hmr-global" }, (args) => {
// const globalName = mapping[args.path];
// return {
// contents: `module.exports = window.${globalName};`,
// loader: "js",
// };
// });
// },
// };
// type Params = {
// tsx: string;
// file_path: string;
// out_file: string;
// };
// export default async function writeHMRTsxModule({
// tsx,
// file_path,
// out_file,
// }: Params) {
// try {
// await esbuild.build({
// stdin: {
// contents: tsx,
// resolveDir: path.dirname(file_path),
// loader: "tsx",
// },
// bundle: true,
// format: "esm",
// target: "es2020",
// platform: "browser",
// minify: true,
// jsx: "automatic",
// outfile: out_file,
// plugins: [hmrExternalsPlugin, tailwindEsbuildPlugin],
// });
// return true;
// } catch (error) {
// return false;
// }
// }

View File

@ -1,4 +1,4 @@
import type { MatchedRoute, ServeOptions, Server, WebSocketHandler } from "bun"; import type { MatchedRoute, Server, WebSocketHandler } from "bun";
import type { FC, JSX, PropsWithChildren, ReactNode } from "react"; import type { FC, JSX, PropsWithChildren, ReactNode } from "react";
export type ServerProps = { export type ServerProps = {
params: Record<string, string>; params: Record<string, string>;
@ -47,7 +47,7 @@ export type BunextConfig = {
middleware?: (params: BunextConfigMiddlewareParams) => Promise<Response | Request | undefined> | Response | Request | undefined; middleware?: (params: BunextConfigMiddlewareParams) => Promise<Response | Request | undefined> | Response | Request | undefined;
defaultCacheExpiry?: number; defaultCacheExpiry?: number;
websocket?: WebSocketHandler<any>; websocket?: WebSocketHandler<any>;
serverOptions?: ServeOptions; serverOptions?: Bun.Serve.Options<any>;
}; };
export type BunextConfigMiddlewareParams = { export type BunextConfigMiddlewareParams = {
req: Request; req: Request;

View File

@ -2,7 +2,7 @@
"name": "@moduletrace/bunext", "name": "@moduletrace/bunext",
"module": "index.ts", "module": "index.ts",
"type": "module", "type": "module",
"version": "1.0.12", "version": "1.0.13",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"exports": { "exports": {

View File

@ -37,6 +37,7 @@ declare global {
var PAGES_SRC_WATCHER: FSWatcher | undefined; var PAGES_SRC_WATCHER: FSWatcher | undefined;
var CURRENT_VERSION: string | undefined; var CURRENT_VERSION: string | undefined;
var PAGE_FILES: PageFiles[]; var PAGE_FILES: PageFiles[];
var ROOT_FILE_UPDATED: boolean;
} }
export default async function bunextInit() { export default async function bunextInit() {

View File

@ -1,11 +1,10 @@
import type { ServeOptions } from "bun";
import grabAppPort from "../../utils/grab-app-port"; import grabAppPort from "../../utils/grab-app-port";
import isDevelopment from "../../utils/is-development"; import isDevelopment from "../../utils/is-development";
import bunextRequestHandler from "./bunext-req-handler"; import bunextRequestHandler from "./bunext-req-handler";
import grabConfig from "../grab-config"; import grabConfig from "../grab-config";
import _ from "lodash"; import _ from "lodash";
export default async function (): Promise<ServeOptions> { export default async function (): Promise<Bun.Serve.Options<any>> {
const port = grabAppPort(); const port = grabAppPort();
const development = isDevelopment(); const development = isDevelopment();
@ -20,5 +19,5 @@ export default async function (): Promise<ServeOptions> {
development, development,
websocket: config?.websocket, websocket: config?.websocket,
..._.omit(config?.serverOptions || {}, ["fetch"]), ..._.omit(config?.serverOptions || {}, ["fetch"]),
} as ServeOptions; } as Bun.Serve.Options<any>;
} }

View File

@ -42,9 +42,21 @@ export default async function serverPostBuildFn({ artifacts }: Params) {
} }
try { try {
let final_data: { [k: string]: any } = {};
console.log("global.ROOT_FILE_UPDATED", global.ROOT_FILE_UPDATED);
if (global.ROOT_FILE_UPDATED) {
final_data = { reload: true };
} else {
final_data = final_artifact;
}
controller.controller.enqueue( controller.controller.enqueue(
`event: update\ndata: ${JSON.stringify(final_artifact)}\n\n`, `event: update\ndata: ${JSON.stringify(final_data)}\n\n`,
); );
global.ROOT_FILE_UPDATED = false;
} catch { } catch {
global.HMR_CONTROLLERS.splice(i, 1); global.HMR_CONTROLLERS.splice(i, 1);
} }

View File

@ -48,6 +48,11 @@ export default async function watcher() {
if (filename.match(target_files_match) && global.BUNDLER_CTX) { if (filename.match(target_files_match) && global.BUNDLER_CTX) {
if (global.RECOMPILING) return; if (global.RECOMPILING) return;
global.RECOMPILING = true; global.RECOMPILING = true;
if (full_file_path.match(/\_\_root\.tsx?$/)) {
global.ROOT_FILE_UPDATED = true;
}
await rewritePagesModule({ page_url: full_file_path }); await rewritePagesModule({ page_url: full_file_path });
await global.BUNDLER_CTX.rebuild(); await global.BUNDLER_CTX.rebuild();
} }

View File

@ -35,14 +35,21 @@ export default async function (params?: Params) {
script += `window.addEventListener("beforeunload", () => hmr.close());\n`; script += `window.addEventListener("beforeunload", () => hmr.close());\n`;
script += `hmr.addEventListener("update", async (event) => {\n`; script += `hmr.addEventListener("update", async (event) => {\n`;
script += ` if (event?.data) {\n`; script += ` if (event?.data) {\n`;
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
script += ` try {\n`; script += ` try {\n`;
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`; script += ` document.getElementById("__bunext_error_overlay")?.remove();\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 += ` if (data.reload) {\n`;
script += ` console.log(\`Root Changes Detected. Reloading Page ...\`);\n`;
script += ` window.location.reload();\n`;
script += ` return;\n`;
script += ` }\n`;
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
script += ` if (data.page_props) {\n`; script += ` if (data.page_props) {\n`;
script += ` window.${ClientWindowPagePropsName} = data.page_props\n`; script += ` window.${ClientWindowPagePropsName} = data.page_props;\n`;
script += ` }\n`; script += ` }\n`;
script += ` const oldCSSLink = document.querySelector('link[rel="stylesheet"]');\n`; script += ` const oldCSSLink = document.querySelector('link[rel="stylesheet"]');\n`;

View File

@ -1,126 +0,0 @@
import * as esbuild from "esbuild";
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
import type { BundlerCTXMap } from "../../../types";
import path from "path";
type Params = {
tsx: string;
out_file: string;
};
export default async function writeHMRTsxModule({ tsx, out_file }: Params) {
try {
const build = await esbuild.build({
stdin: {
contents: tsx,
resolveDir: process.cwd(),
loader: "tsx",
},
bundle: true,
format: "esm",
target: "es2020",
platform: "browser",
external: [
"react",
"react-dom",
"react/jsx-runtime",
"react-dom/client",
],
minify: true,
jsx: "automatic",
outfile: out_file,
plugins: [tailwindEsbuildPlugin],
metafile: true,
});
const artifacts: (
| Pick<BundlerCTXMap, "path" | "hash" | "css_path" | "type">
| undefined
)[] = Object.entries(build.metafile!.outputs)
.filter(([, meta]) => meta.entryPoint)
.map(([outputPath, meta]) => {
const cssPath = meta.cssBundle || undefined;
return {
path: outputPath,
hash: path.basename(outputPath, path.extname(outputPath)),
type: outputPath.endsWith(".css")
? "text/css"
: "text/javascript",
css_path: cssPath,
};
});
return artifacts?.[0];
} catch (error) {
return undefined;
}
}
// import * as esbuild from "esbuild";
// import path from "path";
// import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
// const hmrExternalsPlugin: esbuild.Plugin = {
// name: "hmr-globals",
// setup(build) {
// const mapping: Record<string, string> = {
// react: "__REACT__",
// "react-dom": "__REACT_DOM__",
// "react-dom/client": "__REACT_DOM_CLIENT__",
// "react/jsx-runtime": "__JSX_RUNTIME__",
// };
// const filter = new RegExp(
// `^(${Object.keys(mapping)
// .map((k) => k.replace("/", "\\/"))
// .join("|")})$`,
// );
// build.onResolve({ filter }, (args) => {
// return { path: args.path, namespace: "hmr-global" };
// });
// build.onLoad({ filter: /.*/, namespace: "hmr-global" }, (args) => {
// const globalName = mapping[args.path];
// return {
// contents: `module.exports = window.${globalName};`,
// loader: "js",
// };
// });
// },
// };
// type Params = {
// tsx: string;
// file_path: string;
// out_file: string;
// };
// export default async function writeHMRTsxModule({
// tsx,
// file_path,
// out_file,
// }: Params) {
// try {
// await esbuild.build({
// stdin: {
// contents: tsx,
// resolveDir: path.dirname(file_path),
// loader: "tsx",
// },
// bundle: true,
// format: "esm",
// target: "es2020",
// platform: "browser",
// minify: true,
// jsx: "automatic",
// outfile: out_file,
// plugins: [hmrExternalsPlugin, tailwindEsbuildPlugin],
// });
// return true;
// } catch (error) {
// return false;
// }
// }

View File

@ -1,4 +1,4 @@
import type { MatchedRoute, ServeOptions, Server, WebSocketHandler } from "bun"; import type { MatchedRoute, Server, WebSocketHandler } from "bun";
import type { FC, JSX, PropsWithChildren, ReactNode } from "react"; import type { FC, JSX, PropsWithChildren, ReactNode } from "react";
export type ServerProps = { export type ServerProps = {
@ -57,7 +57,7 @@ export type BunextConfig = {
| undefined; | undefined;
defaultCacheExpiry?: number; defaultCacheExpiry?: number;
websocket?: WebSocketHandler<any>; websocket?: WebSocketHandler<any>;
serverOptions?: ServeOptions; serverOptions?: Bun.Serve.Options<any>;
}; };
export type BunextConfigMiddlewareParams = { export type BunextConfigMiddlewareParams = {