Update HMR. Make it true HMR. Add URL to page server props
This commit is contained in:
+18
-70
@@ -1,56 +1,23 @@
|
||||
import { existsSync, writeFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { writeFileSync } from "fs";
|
||||
import * as esbuild from "esbuild";
|
||||
import postcss from "postcss";
|
||||
import tailwindcss from "@tailwindcss/postcss";
|
||||
import { readFile } from "fs/promises";
|
||||
import grabAllPages from "../../utils/grab-all-pages";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import AppNames from "../../utils/grab-app-names";
|
||||
import isDevelopment from "../../utils/is-development";
|
||||
import { execSync } from "child_process";
|
||||
import grabConstants from "../../utils/grab-constants";
|
||||
import { log } from "../../utils/log";
|
||||
const { HYDRATION_DST_DIR, PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
|
||||
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 "../server/web-pages/tailwind-esbuild-plugin";
|
||||
import grabClientHydrationScript from "./grab-client-hydration-script";
|
||||
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
|
||||
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
|
||||
export default async function allPagesBundler(params) {
|
||||
const pages = grabAllPages({ exclude_api: true });
|
||||
const { ClientRootElementIDName, ClientRootComponentWindowName } = grabConstants();
|
||||
const virtualEntries = {};
|
||||
const dev = isDevelopment();
|
||||
const root_component_path = path.join(PAGES_DIR, `${AppNames["RootPagesComponentName"]}.tsx`);
|
||||
const does_root_exist = existsSync(root_component_path);
|
||||
for (const page of pages) {
|
||||
const key = page.local_path;
|
||||
let txt = ``;
|
||||
txt += `import { hydrateRoot } from "react-dom/client";\n`;
|
||||
if (does_root_exist) {
|
||||
txt += `import Root from "${root_component_path}";\n`;
|
||||
}
|
||||
txt += `import Page from "${page.local_path}";\n\n`;
|
||||
txt += `const pageProps = window.__PAGE_PROPS__ || {};\n`;
|
||||
if (does_root_exist) {
|
||||
txt += `const component = <Root {...pageProps}><Page {...pageProps} /></Root>\n`;
|
||||
}
|
||||
else {
|
||||
txt += `const component = <Page {...pageProps} />\n`;
|
||||
}
|
||||
txt += `const root = hydrateRoot(document.getElementById("${ClientRootElementIDName}"), component);\n\n`;
|
||||
txt += `window.${ClientRootComponentWindowName} = root;\n`;
|
||||
const txt = grabClientHydrationScript({
|
||||
page_local_path: page.local_path,
|
||||
});
|
||||
virtualEntries[key] = txt;
|
||||
}
|
||||
const virtualPlugin = {
|
||||
@@ -77,38 +44,18 @@ export default async function allPagesBundler(params) {
|
||||
build.onEnd((result) => {
|
||||
if (result.errors.length > 0)
|
||||
return;
|
||||
const artifacts = Object.entries(result.metafile.outputs)
|
||||
.filter(([, meta]) => meta.entryPoint)
|
||||
.map(([outputPath, meta]) => {
|
||||
const target_page = pages.find((p) => {
|
||||
return (meta.entryPoint === `virtual:${p.local_path}`);
|
||||
});
|
||||
if (!target_page || !meta.entryPoint) {
|
||||
return undefined;
|
||||
}
|
||||
const { file_name, local_path, url_path } = target_page;
|
||||
const cssPath = meta.cssBundle || undefined;
|
||||
return {
|
||||
path: outputPath,
|
||||
hash: path.basename(outputPath, path.extname(outputPath)),
|
||||
type: outputPath.endsWith(".css")
|
||||
? "text/css"
|
||||
: "text/javascript",
|
||||
entrypoint: meta.entryPoint,
|
||||
css_path: cssPath,
|
||||
file_name,
|
||||
local_path,
|
||||
url_path,
|
||||
};
|
||||
const artifacts = grabArtifactsFromBundledResults({
|
||||
pages,
|
||||
result,
|
||||
});
|
||||
if (artifacts.length > 0) {
|
||||
const final_artifacts = artifacts.filter((a) => Boolean(a?.entrypoint));
|
||||
global.BUNDLER_CTX_MAP = final_artifacts;
|
||||
params?.post_build_fn?.({ artifacts: final_artifacts });
|
||||
if (artifacts?.[0] && artifacts.length > 0) {
|
||||
global.BUNDLER_CTX_MAP = artifacts;
|
||||
global.PAGE_FILES = pages;
|
||||
params?.post_build_fn?.({ artifacts });
|
||||
writeFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, JSON.stringify(artifacts));
|
||||
}
|
||||
const elapsed = (performance.now() - buildStart).toFixed(0);
|
||||
log.success(`Built in ${elapsed}ms`);
|
||||
log.success(`[Built] in ${elapsed}ms`);
|
||||
if (params?.exit_after_first_build) {
|
||||
process.exit();
|
||||
}
|
||||
@@ -129,9 +76,10 @@ export default async function allPagesBundler(params) {
|
||||
},
|
||||
entryNames: "[dir]/[name]/[hash]",
|
||||
metafile: true,
|
||||
plugins: [tailwindPlugin, virtualPlugin, artifactTracker],
|
||||
plugins: [tailwindEsbuildPlugin, virtualPlugin, artifactTracker],
|
||||
jsx: "automatic",
|
||||
splitting: true,
|
||||
logLevel: "silent",
|
||||
});
|
||||
await ctx.rebuild();
|
||||
if (params?.watch) {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import path from "path";
|
||||
import * as esbuild from "esbuild";
|
||||
export default function grabArtifactsFromBundledResults({ result, pages, }) {
|
||||
if (result.errors.length > 0)
|
||||
return;
|
||||
const artifacts = Object.entries(result.metafile.outputs)
|
||||
.filter(([, meta]) => meta.entryPoint)
|
||||
.map(([outputPath, meta]) => {
|
||||
const target_page = pages.find((p) => {
|
||||
return meta.entryPoint === `virtual:${p.local_path}`;
|
||||
});
|
||||
if (!target_page || !meta.entryPoint) {
|
||||
return undefined;
|
||||
}
|
||||
const { file_name, local_path, url_path } = target_page;
|
||||
const cssPath = meta.cssBundle || undefined;
|
||||
return {
|
||||
path: outputPath,
|
||||
hash: path.basename(outputPath, path.extname(outputPath)),
|
||||
type: outputPath.endsWith(".css")
|
||||
? "text/css"
|
||||
: "text/javascript",
|
||||
entrypoint: meta.entryPoint,
|
||||
css_path: cssPath,
|
||||
file_name,
|
||||
local_path,
|
||||
url_path,
|
||||
};
|
||||
});
|
||||
if (artifacts.length > 0) {
|
||||
const final_artifacts = artifacts.filter((a) => Boolean(a?.entrypoint));
|
||||
return final_artifacts;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { existsSync } from "fs";
|
||||
import path from "path";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import AppNames from "../../utils/grab-app-names";
|
||||
import grabConstants from "../../utils/grab-constants";
|
||||
const { PAGES_DIR } = grabDirNames();
|
||||
export default function grabClientHydrationScript({ page_local_path }) {
|
||||
const { ClientRootElementIDName, ClientRootComponentWindowName } = grabConstants();
|
||||
const root_component_path = path.join(PAGES_DIR, `${AppNames["RootPagesComponentName"]}.tsx`);
|
||||
const does_root_exist = existsSync(root_component_path);
|
||||
// let txt = ``;
|
||||
// txt += `import { hydrateRoot } from "react-dom/client";\n`;
|
||||
// if (does_root_exist) {
|
||||
// txt += `import Root from "${root_component_path}";\n`;
|
||||
// }
|
||||
// txt += `import Page from "${page.local_path}";\n\n`;
|
||||
// txt += `const pageProps = window.__PAGE_PROPS__ || {};\n`;
|
||||
// if (does_root_exist) {
|
||||
// txt += `const component = <Root {...pageProps}><Page {...pageProps} /></Root>\n`;
|
||||
// } else {
|
||||
// txt += `const component = <Page {...pageProps} />\n`;
|
||||
// }
|
||||
// txt += `const root = hydrateRoot(document.getElementById("${ClientRootElementIDName}"), component);\n\n`;
|
||||
// txt += `window.${ClientRootComponentWindowName} = root;\n`;
|
||||
let txt = ``;
|
||||
// txt += `import * as React from "react";\n`;
|
||||
// txt += `import * as ReactDOM from "react-dom";\n`;
|
||||
// txt += `import * as ReactDOMClient from "react-dom/client";\n`;
|
||||
// txt += `import * as JSXRuntime from "react/jsx-runtime";\n`;
|
||||
txt += `import { hydrateRoot, createElement } from "react-dom/client";\n`;
|
||||
if (does_root_exist) {
|
||||
txt += `import Root from "${root_component_path}";\n`;
|
||||
}
|
||||
txt += `import Page from "${page_local_path}";\n\n`;
|
||||
// txt += `window.__REACT__ = React;\n`;
|
||||
// txt += `window.__REACT_DOM__ = ReactDOM;\n`;
|
||||
// txt += `window.__REACT_DOM_CLIENT__ = ReactDOMClient;\n`;
|
||||
// txt += `window.__JSX_RUNTIME__ = JSXRuntime;\n\n`;
|
||||
txt += `const pageProps = window.__PAGE_PROPS__ || {};\n`;
|
||||
if (does_root_exist) {
|
||||
txt += `const component = <Root {...pageProps}><Page {...pageProps} /></Root>\n`;
|
||||
}
|
||||
else {
|
||||
txt += `const component = <Page {...pageProps} />\n`;
|
||||
}
|
||||
txt += `if (window.${ClientRootComponentWindowName}?.render) {\n`;
|
||||
txt += ` window.${ClientRootComponentWindowName}.render(component);\n`;
|
||||
txt += `} else {\n`;
|
||||
txt += ` const root = hydrateRoot(document.getElementById("${ClientRootElementIDName}"), component);\n\n`;
|
||||
txt += ` window.${ClientRootComponentWindowName} = root;\n`;
|
||||
txt += ` window.__BUNEXT_RERENDER__ = (NewPage) => {\n`;
|
||||
txt += ` const props = window.__PAGE_PROPS__ || {};\n`;
|
||||
txt += ` root.render(<NewPage {...props} />);\n`;
|
||||
txt += ` };\n`;
|
||||
txt += `}\n`;
|
||||
// // HMR re-render helper
|
||||
// if (does_root_exist) {
|
||||
// txt += `window.__BUNEXT_RERENDER__ = (NewPage) => {\n`;
|
||||
// txt += ` const props = window.__PAGE_PROPS__ || {};\n`;
|
||||
// txt += ` root.render(<Root {...props}><NewPage {...props} /></Root>);\n`;
|
||||
// txt += `};\n`;
|
||||
// } else {
|
||||
// }
|
||||
return txt;
|
||||
}
|
||||
Vendored
+6
@@ -1,10 +1,16 @@
|
||||
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
||||
import grabDirNames from "../utils/grab-dir-names";
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
export default async function () {
|
||||
const dirNames = grabDirNames();
|
||||
execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`);
|
||||
execSync(`rm -rf ${dirNames.BUNX_CWD_MODULE_CACHE_DIR}`);
|
||||
try {
|
||||
const current_version = (await Bun.file(path.resolve(__dirname, "../../package.json")).json()).version;
|
||||
global.CURRENT_VERSION = current_version;
|
||||
}
|
||||
catch (error) { }
|
||||
const keys = Object.keys(dirNames);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
|
||||
+24
@@ -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
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Vendored
+34
@@ -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
@@ -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
@@ -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}`, {
|
||||
|
||||
Vendored
+8
-1
@@ -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
@@ -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`;
|
||||
|
||||
+55
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
+101
-47
@@ -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
@@ -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;
|
||||
@@ -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;
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user