Update HMR. Make it true HMR. Add URL to page server props

This commit is contained in:
2026-03-20 11:19:22 +01:00
parent 7804a34951
commit 52dde6c0ab
52 changed files with 1548 additions and 708 deletions
+24
View File
@@ -0,0 +1,24 @@
import grabDirNames from "../../utils/grab-dir-names";
import path from "path";
import isDevelopment from "../../utils/is-development";
import { existsSync } from "fs";
const { PUBLIC_DIR } = grabDirNames();
export default async function ({ req, server }) {
try {
const is_dev = isDevelopment();
const url = new URL(req.url);
const file_path = path.join(PUBLIC_DIR, url.pathname);
if (!existsSync(file_path)) {
return new Response(`File Doesn't Exist`, {
status: 404,
});
}
const file = Bun.file(file_path);
return new Response(file);
}
catch (error) {
return new Response(`File Not Found`, {
status: 404,
});
}
}
+54
View File
@@ -0,0 +1,54 @@
import grabDirNames from "../../utils/grab-dir-names";
import { AppData } from "../../data/app-data";
import path from "path";
import grabRootFile from "./web-pages/grab-root-file";
import grabPageBundledReactComponent from "./web-pages/grab-page-bundled-react-component";
import writeHMRTsxModule from "./web-pages/write-hmr-tsx-module";
const { PUBLIC_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
export default async function ({ req, server }) {
try {
const url = new URL(req.url);
const target_href = url.searchParams.get("href");
if (!target_href) {
return new Response(`No HREF passed to /${AppData["ClientHMRPath"]}`, { status: 404 });
}
const target_href_url = new URL(target_href);
const match = global.ROUTER.match(target_href_url.pathname);
if (!match?.filePath) {
return new Response(`No pages file matched for this path`, {
status: 404,
});
}
const out_file = path.join(BUNX_HYDRATION_SRC_DIR, target_href_url.pathname, "index.js");
const { root_file } = grabRootFile();
const { tsx } = (await grabPageBundledReactComponent({
file_path: match.filePath,
root_file,
})) || {};
if (!tsx) {
throw new Error(`Couldn't grab txt string`);
}
const artifact = await writeHMRTsxModule({
tsx,
out_file,
});
const file = Bun.file(out_file);
if (await file.exists()) {
return new Response(file, {
headers: {
"Content-Type": "text/javascript",
},
});
}
return new Response("Not found", {
status: 404,
});
}
catch (error) {
const error_msg = error.message;
console.error(error_msg);
return new Response(error_msg || "HMR Error", {
status: 404,
});
}
}
+34
View File
@@ -0,0 +1,34 @@
import grabRouteParams from "../../utils/grab-route-params";
import grabConstants from "../../utils/grab-constants";
import grabRouter from "../../utils/grab-router";
export default async function ({ req, server }) {
const referer_url = new URL(req.headers.get("referer") || "");
const match = global.ROUTER.match(referer_url.pathname);
const target_map = match?.filePath
? global.BUNDLER_CTX_MAP?.find((m) => m.local_path == match.filePath)
: undefined;
let controller;
const stream = new ReadableStream({
start(c) {
controller = c;
global.HMR_CONTROLLERS.push({
controller: c,
page_url: referer_url.href,
target_map,
});
},
cancel() {
const targetControllerIndex = global.HMR_CONTROLLERS.findIndex((c) => c.controller == controller);
if (typeof targetControllerIndex == "number" &&
targetControllerIndex >= 0) {
global.HMR_CONTROLLERS.splice(targetControllerIndex, 1);
}
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
Connection: "keep-alive",
},
});
}
+25
View File
@@ -0,0 +1,25 @@
import grabDirNames from "../../utils/grab-dir-names";
import path from "path";
import isDevelopment from "../../utils/is-development";
import { existsSync } from "fs";
const { PUBLIC_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
export default async function ({ req, server }) {
try {
const is_dev = isDevelopment();
const url = new URL(req.url);
const file_path = path.join(PUBLIC_DIR, url.pathname.replace(/^\/public/, ""));
if (!existsSync(file_path)) {
return new Response(`Public File Doesn't Exist`, {
status: 404,
});
}
const file = Bun.file(file_path);
let res_opts = {};
return new Response(file, res_opts);
}
catch (error) {
return new Response(`Public File Not Found`, {
status: 404,
});
}
}
+26 -75
View File
@@ -1,21 +1,22 @@
import path from "path";
import grabAppPort from "../../utils/grab-app-port";
import grabDirNames from "../../utils/grab-dir-names";
import handleWebPages from "./web-pages/handle-web-pages";
import handleRoutes from "./handle-routes";
import isDevelopment from "../../utils/is-development";
import grabConstants from "../../utils/grab-constants";
import { AppData } from "../../data/app-data";
import { existsSync } from "fs";
import handleHmr from "./handle-hmr";
import handleHmrUpdate from "./handle-hmr-update";
import handlePublic from "./handle-public";
import handleFiles from "./handle-files";
export default async function (params) {
const port = grabAppPort();
const { PUBLIC_DIR } = grabDirNames();
const is_dev = isDevelopment();
return {
async fetch(req, server) {
try {
const url = new URL(req.url);
const { config } = grabConstants();
let response = undefined;
if (config?.middleware) {
const middleware_res = await config.middleware({
req,
@@ -26,81 +27,31 @@ export default async function (params) {
return middleware_res;
}
}
if (url.pathname === "/__hmr" && is_dev) {
const referer_url = new URL(req.headers.get("referer") || "");
const match = global.ROUTER.match(referer_url.pathname);
const target_map = match?.filePath
? global.BUNDLER_CTX_MAP?.find((m) => m.local_path == match.filePath)
: undefined;
let controller;
const stream = new ReadableStream({
start(c) {
controller = c;
global.HMR_CONTROLLERS.push({
controller: c,
page_url: referer_url.href,
target_map,
});
},
cancel() {
const targetControllerIndex = global.HMR_CONTROLLERS.findIndex((c) => c.controller == controller);
if (typeof targetControllerIndex == "number" &&
targetControllerIndex >= 0) {
global.HMR_CONTROLLERS.splice(targetControllerIndex, 1);
}
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
if (url.pathname == `/${AppData["ClientHMRPath"]}`) {
response = await handleHmrUpdate({ req, server });
}
if (url.pathname.startsWith("/api/")) {
return await handleRoutes({ req, server });
else if (url.pathname === "/__hmr" && is_dev) {
response = await handleHmr({ req, server });
}
if (url.pathname.startsWith("/public/")) {
try {
const file_path = path.join(PUBLIC_DIR, url.pathname.replace(/^\/public/, ""));
if (!existsSync(file_path)) {
return new Response(`Public File Doesn't Exist`, {
status: 404,
});
}
const file = Bun.file(file_path);
let res_opts = {};
if (!is_dev && url.pathname.match(/__bunext/)) {
res_opts.headers = {
"Cache-Control": `public, max-age=${AppData["BunextStaticFilesCacheExpiry"]}, must-revalidate`,
};
}
return new Response(file, res_opts);
}
catch (error) {
return new Response(`Public File Not Found`, {
status: 404,
});
}
else if (url.pathname.startsWith("/api/")) {
response = await handleRoutes({ req, server });
}
// if (url.pathname.startsWith("/favicon.") ) {
if (url.pathname.match(/\..*$/)) {
try {
const file_path = path.join(PUBLIC_DIR, url.pathname);
if (!existsSync(file_path)) {
return new Response(`File Doesn't Exist`, {
status: 404,
});
}
const file = Bun.file(file_path);
return new Response(file);
}
catch (error) {
return new Response(`File Not Found`, { status: 404 });
}
else if (url.pathname.startsWith("/public/")) {
response = await handlePublic({ req, server });
}
return await handleWebPages({ req });
else if (url.pathname.match(/\..*$/)) {
response = await handleFiles({ req, server });
}
else {
response = await handleWebPages({ req });
}
if (!response) {
throw new Error(`No Response generated`);
}
if (is_dev) {
response.headers.set("Cache-Control", "no-cache, no-store, must-revalidate");
}
return response;
}
catch (error) {
return new Response(`Server Error: ${error.message}`, {
+8 -1
View File
@@ -5,7 +5,7 @@ import rebuildBundler from "./rebuild-bundler";
import { log } from "../../utils/log";
const { SRC_DIR } = grabDirNames();
export default function watcher() {
watch(SRC_DIR, {
const pages_src_watcher = watch(SRC_DIR, {
recursive: true,
persistent: true,
}, async (event, filename) => {
@@ -13,6 +13,8 @@ export default function watcher() {
return;
if (event !== "rename")
return;
if (!filename.match(/^pages\//))
return;
if (global.RECOMPILING)
return;
const fullPath = path.join(SRC_DIR, filename);
@@ -28,5 +30,10 @@ export default function watcher() {
finally {
global.RECOMPILING = false;
}
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
watcher();
}
});
global.PAGES_SRC_WATCHER = pages_src_watcher;
}
+10 -2
View File
@@ -5,10 +5,18 @@ import EJSON from "../../../utils/ejson";
import isDevelopment from "../../../utils/is-development";
import grabWebPageHydrationScript from "./grab-web-page-hydration-script";
import grabWebMetaHTML from "./grab-web-meta-html";
export default async function genWebHTML({ component, pageProps, bundledMap, head: Head, module, meta, routeParams, }) {
import { log } from "../../../utils/log";
import { AppData } from "../../../data/app-data";
export default async function genWebHTML({ component, pageProps, bundledMap, head: Head, module, meta, routeParams, debug, }) {
const { ClientRootElementIDName, ClientWindowPagePropsName } = grabContants();
const { renderToString } = await import(path.join(process.cwd(), "node_modules", "react-dom", "server"));
if (debug) {
log.info("component", component);
}
const componentHTML = renderToString(component);
if (debug) {
log.info("componentHTML", componentHTML);
}
const headHTML = Head
? renderToString(_jsx(Head, { serverRes: pageProps, ctx: routeParams }))
: "";
@@ -25,7 +33,7 @@ export default async function genWebHTML({ component, pageProps, bundledMap, hea
}
html += ` <script>window.${ClientWindowPagePropsName} = ${EJSON.stringify(pageProps || {}) || "{}"}</script>\n`;
if (bundledMap?.path) {
html += ` <script src="/${bundledMap.path}" type="module" async></script>\n`;
html += ` <script src="/${bundledMap.path}" type="module" id="${AppData["BunextClientHydrationScriptID"]}" async></script>\n`;
}
if (isDevelopment()) {
html += `<script defer>\n${await grabWebPageHydrationScript({ bundledMap })}\n</script>\n`;
@@ -0,0 +1,55 @@
import isDevelopment from "../../../utils/is-development";
import { log } from "../../../utils/log";
import writeCache from "../../cache/write-cache";
import genWebHTML from "./generate-web-html";
export default async function generateWebPageResponseFromComponentReturn({ component, module, bundledMap, head, meta, routeParams, serverRes, debug, }) {
const html = await genWebHTML({
component,
pageProps: serverRes,
bundledMap,
module,
meta,
head,
routeParams,
debug,
});
if (debug) {
log.info("html", html);
}
if (serverRes?.redirect?.destination) {
return Response.redirect(serverRes.redirect.destination, serverRes.redirect.permanent
? 301
: serverRes.redirect.status_code || 302);
}
const res_opts = {
...serverRes?.responseOptions,
headers: {
"Content-Type": "text/html",
...serverRes?.responseOptions?.headers,
},
};
if (isDevelopment()) {
res_opts.headers = {
...res_opts.headers,
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
};
}
const cache_page = module.config?.cachePage || serverRes?.cachePage || false;
const expiry_seconds = module.config?.cacheExpiry || serverRes?.cacheExpiry;
if (cache_page && routeParams?.url) {
const key = routeParams.url.pathname + (routeParams.url.search || "");
writeCache({
key,
value: html,
paradigm: "html",
expiry_seconds,
});
}
const res = new Response(html, res_opts);
if (routeParams?.resTransform) {
return await routeParams.resTransform(res);
}
return res;
}
+5 -18
View File
@@ -5,25 +5,12 @@ import tailwindcss from "@tailwindcss/postcss";
import { readFile } from "fs/promises";
import grabDirNames from "../../../utils/grab-dir-names";
import path from "path";
const tailwindPlugin = {
name: "tailwindcss",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const source = await readFile(args.path, "utf-8");
const result = await postcss([tailwindcss()]).process(source, {
from: args.path,
});
return {
contents: result.css,
loader: "css",
};
});
},
};
export default async function grabFilePathModule({ file_path, }) {
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
export default async function grabFilePathModule({ file_path, out_file, }) {
const dev = isDevelopment();
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
const target_cache_file_path = path.join(BUNX_CWD_MODULE_CACHE_DIR, `${path.basename(file_path)}.js`);
const target_cache_file_path = out_file ||
path.join(BUNX_CWD_MODULE_CACHE_DIR, `${path.basename(file_path)}.js`);
await esbuild.build({
entryPoints: [file_path],
bundle: true,
@@ -36,7 +23,7 @@ export default async function grabFilePathModule({ file_path, }) {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
metafile: true,
plugins: [tailwindPlugin],
plugins: [tailwindEsbuildPlugin],
jsx: "automatic",
outfile: target_cache_file_path,
});
@@ -13,10 +13,10 @@ export default async function grabPageBundledReactComponent({ file_path, root_fi
tsx += `const props = JSON.parse("${server_res_json}")\n\n`;
tsx += ` return (\n`;
if (root_file) {
tsx += ` <Root {...props}><Page {...props} /></Root>\n`;
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
}
else {
tsx += ` <Page {...props} />\n`;
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
}
tsx += ` )\n`;
tsx += `}\n`;
@@ -26,6 +26,7 @@ export default async function grabPageBundledReactComponent({ file_path, root_fi
return {
component,
server_res,
tsx,
};
}
catch (error) {
+26 -20
View File
@@ -1,17 +1,14 @@
import grabDirNames from "../../../utils/grab-dir-names";
import grabRouteParams from "../../../utils/grab-route-params";
import path from "path";
import AppNames from "../../../utils/grab-app-names";
import { existsSync } from "fs";
import grabPageErrorComponent from "./grab-page-error-component";
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
import _ from "lodash";
import { log } from "../../../utils/log";
import grabRootFile from "./grab-root-file";
class NotFoundError extends Error {
}
export default async function grabPageComponent({ req, file_path: passed_file_path, }) {
export default async function grabPageComponent({ req, file_path: passed_file_path, debug, }) {
const url = req?.url ? new URL(req.url) : undefined;
const router = global.ROUTER;
const { PAGES_DIR } = grabDirNames();
let routeParams = undefined;
try {
routeParams = req ? await grabRouteParams({ req }) : undefined;
@@ -19,11 +16,17 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
if (url_path && url?.search) {
url_path += url.search;
}
if (debug) {
log.info(`url_path:`, url_path);
}
const match = url_path ? router.match(url_path) : undefined;
if (!match?.filePath && url?.pathname) {
throw new NotFoundError(`Page ${url.pathname} not found`);
}
const file_path = match?.filePath || passed_file_path;
if (debug) {
log.info(`file_path:`, file_path);
}
if (!file_path) {
const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`;
// console.error(errMsg);
@@ -35,21 +38,14 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
console.error(errMsg);
throw new Error(errMsg);
}
const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`;
const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`;
const root_pages_component_js_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.js`;
const root_pages_component_jsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.jsx`;
const root_file = existsSync(root_pages_component_tsx_file)
? root_pages_component_tsx_file
: existsSync(root_pages_component_ts_file)
? root_pages_component_ts_file
: existsSync(root_pages_component_jsx_file)
? root_pages_component_jsx_file
: existsSync(root_pages_component_js_file)
? root_pages_component_js_file
: undefined;
const now = Date.now();
if (debug) {
log.info(`bundledMap:`, bundledMap);
}
const { root_file } = grabRootFile();
const module = await import(file_path);
if (debug) {
log.info(`module:`, module);
}
const serverRes = await (async () => {
const default_props = {
url: {
@@ -88,6 +84,9 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
};
}
})();
if (debug) {
log.info(`serverRes:`, serverRes);
}
const meta = module.meta
? typeof module.meta == "function" && routeParams
? await module.meta({
@@ -98,6 +97,9 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
? module.meta
: undefined
: undefined;
if (debug) {
log.info(`meta:`, meta);
}
const Head = module.Head;
const { component } = (await grabPageBundledReactComponent({
file_path,
@@ -107,6 +109,9 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
if (!component) {
throw new Error(`Couldn't grab page component`);
}
if (debug) {
log.info(`component:`, component);
}
return {
component,
serverRes,
@@ -118,6 +123,7 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
};
}
catch (error) {
console.error(`Error Grabbing Page Component: ${error.message}`);
return await grabPageErrorComponent({
error,
routeParams,
+21
View File
@@ -0,0 +1,21 @@
import grabDirNames from "../../../utils/grab-dir-names";
import path from "path";
import AppNames from "../../../utils/grab-app-names";
import { existsSync } from "fs";
export default function grabRootFile() {
const { PAGES_DIR } = grabDirNames();
const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`;
const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`;
const root_pages_component_js_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.js`;
const root_pages_component_jsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.jsx`;
const root_file = existsSync(root_pages_component_tsx_file)
? root_pages_component_tsx_file
: existsSync(root_pages_component_ts_file)
? root_pages_component_ts_file
: existsSync(root_pages_component_jsx_file)
? root_pages_component_jsx_file
: existsSync(root_pages_component_js_file)
? root_pages_component_js_file
: undefined;
return { root_file };
}
+2 -16
View File
@@ -6,21 +6,7 @@ import { readFile } from "fs/promises";
import grabDirNames from "../../../utils/grab-dir-names";
import path from "path";
import { execSync } from "child_process";
const tailwindPlugin = {
name: "tailwindcss",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const source = await readFile(args.path, "utf-8");
const result = await postcss([tailwindcss()]).process(source, {
from: args.path,
});
return {
contents: result.css,
loader: "css",
};
});
},
};
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
export default async function grabTsxStringModule({ tsx, file_path, }) {
const dev = isDevelopment();
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
@@ -44,7 +30,7 @@ export default async function grabTsxStringModule({ tsx, file_path, }) {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
metafile: true,
plugins: [tailwindPlugin],
plugins: [tailwindEsbuildPlugin],
jsx: "automatic",
write: true,
outfile: out_file_path,
@@ -1,55 +1,109 @@
import grabDirNames from "../../../utils/grab-dir-names";
const { BUNX_HYDRATION_SRC_DIR } = grabDirNames();
import { AppData } from "../../../data/app-data";
export default async function ({ bundledMap }) {
let script = "";
// script += `import React from "react";\n`;
// script += `import { hydrateRoot } from "react-dom/client";\n`;
// script += `import App from "${page_file}";\n`;
// script += `declare global {\n`;
// script += ` interface Window {\n`;
// script += ` ${ClientWindowPagePropsName}: any;\n`;
// script += ` }\n`;
// script += `}\n`;
// script += `let root: any = null;\n\n`;
// script += `const component = <App {...window.${ClientWindowPagePropsName}} />;\n\n`;
// script += `const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
// script += `if (container) {\n`;
// script += ` root = hydrateRoot(container, component);\n`;
// script += `}\n\n`;
script += `console.log(\`Development Environment\`);\n`;
// script += `console.log(import.meta);\n`;
// script += `if (import.meta.hot) {\n`;
// script += ` console.log(\`HMR active\`);\n`;
// script += ` import.meta.hot.dispose(() => {\n`;
// script += ` console.log("dispose");\n`;
// script += ` });\n`;
// script += `}\n`;
script += `console.log(\`Development Environment\`);\n\n`;
script += `const hmr = new EventSource("/__hmr");\n`;
script += `hmr.addEventListener("update", async (event) => {\n`;
// script += ` console.log(\`HMR even received:\`, event);\n`;
script += ` if (event.data) {\n`;
script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`;
// script += ` console.log("event", event);\n`;
// script += ` console.log("window.${ClientRootComponentWindowName}", window.${ClientRootComponentWindowName});\n\n`;
// script += ` const event_data = JSON.parse(event.data);\n\n`;
// script += ` const new_js_path = \`/\${event_data.target_map.path}\`;\n\n`;
// script += ` console.log("event_data", event_data);\n\n`;
// script += ` console.log("new_js_path", new_js_path);\n\n`;
// script += ` if (window.${ClientRootComponentWindowName}) {\n`;
// script += ` const new_component = await import(new_js_path);\n`;
// script += ` window.${ClientRootComponentWindowName}.render(new_component);\n`;
// script += ` }\n`;
// script += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`;
// script += ` root.render(module.default);\n`;
// script += ` })\n`;
// script += ` console.log("root", root);\n`;
// script += ` root.unmount();\n`;
// script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
// script += ` root = hydrateRoot(container!, component);\n`;
// script += ` window.history.pushState({ page: 1 }, "New Page Title", \`\${window.location.pathname}?v=\${Date.now()}\`);\n`;
// script += ` root.render(component);\n`;
script += ` window.location.reload();\n`;
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
script += ` try {\n`;
script += ` const data = JSON.parse(event.data);\n`;
// script += ` console.log("data", data);\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 += ` console.log("Fetching updated module ...", modulePath);\n\n`;
// script += ` const newModule = await import(modulePath);\n\n`;
// script += ` console.log("newModule", newModule);\n\n`;
// script += ` if (window.__BUNEXT_RERENDER__ && newModule.default) {\n`;
// script += ` window.__BUNEXT_RERENDER__(newModule.default);\n`;
// script += ` console.log(\`HMR: Component updated in-place\`);\n`;
// script += ` } else {\n`;
// script += ` console.warn(\`HMR: No re-render helper found, falling back to reload\`);\n`;
// // script += ` window.location.reload();\n`;
// script += ` }\n\n`;
script += ` if (data.target_map.css_path) {\n`;
script += ` const oldLink = document.querySelector('link[rel="stylesheet"]');\n`;
script += ` const newLink = document.createElement("link");\n`;
script += ` newLink.rel = "stylesheet";\n`;
script += ` newLink.href = \`/\${data.target_map.css_path}?t=\${Date.now()}\`;\n`;
script += ` newLink.onload = () => oldLink?.remove();\n`;
script += ` document.head.appendChild(newLink);\n`;
script += ` }\n`;
script += ` const newScriptPath = \`/\${data.target_map.path}?t=\${Date.now()}\`;\n\n`;
script += ` const oldScript = document.getElementById("${AppData["BunextClientHydrationScriptID"]}");\n`;
script += ` if (oldScript) {\n`;
script += ` oldScript.remove();\n`;
script += ` }\n\n`;
script += ` const newScript = document.createElement("script");\n`;
script += ` newScript.id = "${AppData["BunextClientHydrationScriptID"]}";\n`;
script += ` newScript.type = "module";\n`;
script += ` newScript.src = newScriptPath;\n`;
// script += ` console.log("newScript", newScript);\n`;
script += ` document.head.appendChild(newScript);\n\n`;
script += ` } catch (err) {\n`;
script += ` console.error("HMR update failed, falling back to reload:", err.message);\n`;
// script += ` window.location.reload();\n`;
script += ` }\n`;
script += ` }\n`;
script += ` });\n`;
script += `});\n`;
return script;
}
// import grabDirNames from "../../../utils/grab-dir-names";
// import type { BundlerCTXMap, PageDistGenParams } from "../../../types";
// const { BUNX_HYDRATION_SRC_DIR } = grabDirNames();
// type Params = {
// bundledMap?: BundlerCTXMap;
// };
// export default async function ({ bundledMap }: Params) {
// let script = "";
// // script += `import React from "react";\n`;
// // script += `import { hydrateRoot } from "react-dom/client";\n`;
// // script += `import App from "${page_file}";\n`;
// // script += `declare global {\n`;
// // script += ` interface Window {\n`;
// // script += ` ${ClientWindowPagePropsName}: any;\n`;
// // script += ` }\n`;
// // script += `}\n`;
// // script += `let root: any = null;\n\n`;
// // script += `const component = <App {...window.${ClientWindowPagePropsName}} />;\n\n`;
// // script += `const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
// // script += `if (container) {\n`;
// // script += ` root = hydrateRoot(container, component);\n`;
// // script += `}\n\n`;
// script += `console.log(\`Development Environment\`);\n`;
// // script += `console.log(import.meta);\n`;
// // script += `if (import.meta.hot) {\n`;
// // script += ` console.log(\`HMR active\`);\n`;
// // script += ` import.meta.hot.dispose(() => {\n`;
// // script += ` console.log("dispose");\n`;
// // script += ` });\n`;
// // script += `}\n`;
// script += `const hmr = new EventSource("/__hmr");\n`;
// script += `hmr.addEventListener("update", async (event) => {\n`;
// // script += ` console.log(\`HMR even received:\`, event);\n`;
// script += ` if (event.data) {\n`;
// script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`;
// // script += ` console.log("event", event);\n`;
// // script += ` console.log("window.${ClientRootComponentWindowName}", window.${ClientRootComponentWindowName});\n\n`;
// // script += ` const event_data = JSON.parse(event.data);\n\n`;
// // script += ` const new_js_path = \`/\${event_data.target_map.path}\`;\n\n`;
// // script += ` console.log("event_data", event_data);\n\n`;
// // script += ` console.log("new_js_path", new_js_path);\n\n`;
// // script += ` if (window.${ClientRootComponentWindowName}) {\n`;
// // script += ` const new_component = await import(new_js_path);\n`;
// // script += ` window.${ClientRootComponentWindowName}.render(new_component);\n`;
// // script += ` }\n`;
// // script += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`;
// // script += ` root.render(module.default);\n`;
// // script += ` })\n`;
// // script += ` console.log("root", root);\n`;
// // script += ` root.unmount();\n`;
// // script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
// // script += ` root = hydrateRoot(container!, component);\n`;
// // script += ` window.history.pushState({ page: 1 }, "New Page Title", \`\${window.location.pathname}?v=\${Date.now()}\`);\n`;
// // script += ` root.render(component);\n`;
// script += ` window.location.reload();\n`;
// script += ` }\n`;
// script += ` });\n`;
// return script;
// }
+12 -53
View File
@@ -1,7 +1,6 @@
import isDevelopment from "../../../utils/is-development";
import getCache from "../../cache/get-cache";
import writeCache from "../../cache/write-cache";
import genWebHTML from "./generate-web-html";
import generateWebPageResponseFromComponentReturn from "./generate-web-page-response-from-component-return";
import grabPageComponent from "./grab-page-component";
import grabPageErrorComponent from "./grab-page-error-component";
export default async function handleWebPages({ req, }) {
@@ -20,58 +19,18 @@ export default async function handleWebPages({ req, }) {
return new Response(existing_cache, res_opts);
}
}
const componentRes = await grabPageComponent({ req });
return await generateRes(componentRes);
}
catch (error) {
const componentRes = await grabPageErrorComponent({ error });
return await generateRes(componentRes);
}
}
async function generateRes({ component, module, bundledMap, head, meta, routeParams, serverRes, }) {
const html = await genWebHTML({
component,
pageProps: serverRes,
bundledMap,
module,
meta,
head,
routeParams,
});
if (serverRes?.redirect?.destination) {
return Response.redirect(serverRes.redirect.destination, serverRes.redirect.permanent
? 301
: serverRes.redirect.status_code || 302);
}
const res_opts = {
...serverRes?.responseOptions,
headers: {
"Content-Type": "text/html",
...serverRes?.responseOptions?.headers,
},
};
if (isDevelopment()) {
res_opts.headers = {
...res_opts.headers,
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
};
}
const cache_page = module.config?.cachePage || serverRes?.cachePage || false;
const expiry_seconds = module.config?.cacheExpiry || serverRes?.cacheExpiry;
if (cache_page && routeParams?.url) {
const key = routeParams.url.pathname + (routeParams.url.search || "");
writeCache({
key,
value: html,
paradigm: "html",
expiry_seconds,
const componentRes = await grabPageComponent({
req,
});
return await generateWebPageResponseFromComponentReturn({
...componentRes,
});
}
const res = new Response(html, res_opts);
if (routeParams?.resTransform) {
return await routeParams.resTransform(res);
catch (error) {
console.error(`Error Handling Web Page: ${error.message}`);
const componentRes = await grabPageErrorComponent({
error,
});
return await generateWebPageResponseFromComponentReturn(componentRes);
}
return res;
}
@@ -0,0 +1,20 @@
import * as esbuild from "esbuild";
import postcss from "postcss";
import tailwindcss from "@tailwindcss/postcss";
import { readFile } from "fs/promises";
const tailwindEsbuildPlugin = {
name: "tailwindcss",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const source = await readFile(args.path, "utf-8");
const result = await postcss([tailwindcss()]).process(source, {
from: args.path,
});
return {
contents: result.css,
loader: "css",
};
});
},
};
export default tailwindEsbuildPlugin;
+106
View File
@@ -0,0 +1,106 @@
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;
// }
// }