Update HMR. Make it true HMR. Add URL to page server props
This commit is contained in:
parent
7804a34951
commit
52dde6c0ab
1
dist/commands/start/index.js
vendored
1
dist/commands/start/index.js
vendored
@ -9,6 +9,7 @@ export default function () {
|
|||||||
.action(async () => {
|
.action(async () => {
|
||||||
log.banner();
|
log.banner();
|
||||||
log.info("Starting production server ...");
|
log.info("Starting production server ...");
|
||||||
|
process.env.NODE_ENV = "production";
|
||||||
await init();
|
await init();
|
||||||
const config = await grabConfig();
|
const config = await grabConfig();
|
||||||
global.CONFIG = { ...config };
|
global.CONFIG = { ...config };
|
||||||
|
|||||||
2
dist/data/app-data.js
vendored
2
dist/data/app-data.js
vendored
@ -2,4 +2,6 @@ export const AppData = {
|
|||||||
DefaultCacheExpiryTimeSeconds: 60 * 60,
|
DefaultCacheExpiryTimeSeconds: 60 * 60,
|
||||||
DefaultCronInterval: 30000,
|
DefaultCronInterval: 30000,
|
||||||
BunextStaticFilesCacheExpiry: 60 * 60 * 24 * 7,
|
BunextStaticFilesCacheExpiry: 60 * 60 * 24 * 7,
|
||||||
|
ClientHMRPath: "__bunext_client_hmr__",
|
||||||
|
BunextClientHydrationScriptID: "bunext-client-hydration-script",
|
||||||
};
|
};
|
||||||
|
|||||||
88
dist/functions/bundler/all-pages-bundler.js
vendored
88
dist/functions/bundler/all-pages-bundler.js
vendored
@ -1,56 +1,23 @@
|
|||||||
import { existsSync, writeFileSync } from "fs";
|
import { writeFileSync } from "fs";
|
||||||
import path from "path";
|
|
||||||
import * as esbuild from "esbuild";
|
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 grabAllPages from "../../utils/grab-all-pages";
|
||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
import AppNames from "../../utils/grab-app-names";
|
|
||||||
import isDevelopment from "../../utils/is-development";
|
import isDevelopment from "../../utils/is-development";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import grabConstants from "../../utils/grab-constants";
|
|
||||||
import { log } from "../../utils/log";
|
import { log } from "../../utils/log";
|
||||||
const { HYDRATION_DST_DIR, PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
|
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
|
||||||
const tailwindPlugin = {
|
import grabClientHydrationScript from "./grab-client-hydration-script";
|
||||||
name: "tailwindcss",
|
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
|
||||||
setup(build) {
|
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
|
||||||
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 allPagesBundler(params) {
|
export default async function allPagesBundler(params) {
|
||||||
const pages = grabAllPages({ exclude_api: true });
|
const pages = grabAllPages({ exclude_api: true });
|
||||||
const { ClientRootElementIDName, ClientRootComponentWindowName } = grabConstants();
|
|
||||||
const virtualEntries = {};
|
const virtualEntries = {};
|
||||||
const dev = isDevelopment();
|
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) {
|
for (const page of pages) {
|
||||||
const key = page.local_path;
|
const key = page.local_path;
|
||||||
let txt = ``;
|
const txt = grabClientHydrationScript({
|
||||||
txt += `import { hydrateRoot } from "react-dom/client";\n`;
|
page_local_path: page.local_path,
|
||||||
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`;
|
|
||||||
virtualEntries[key] = txt;
|
virtualEntries[key] = txt;
|
||||||
}
|
}
|
||||||
const virtualPlugin = {
|
const virtualPlugin = {
|
||||||
@ -77,38 +44,18 @@ export default async function allPagesBundler(params) {
|
|||||||
build.onEnd((result) => {
|
build.onEnd((result) => {
|
||||||
if (result.errors.length > 0)
|
if (result.errors.length > 0)
|
||||||
return;
|
return;
|
||||||
const artifacts = Object.entries(result.metafile.outputs)
|
const artifacts = grabArtifactsFromBundledResults({
|
||||||
.filter(([, meta]) => meta.entryPoint)
|
pages,
|
||||||
.map(([outputPath, meta]) => {
|
result,
|
||||||
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) {
|
if (artifacts?.[0] && artifacts.length > 0) {
|
||||||
const final_artifacts = artifacts.filter((a) => Boolean(a?.entrypoint));
|
global.BUNDLER_CTX_MAP = artifacts;
|
||||||
global.BUNDLER_CTX_MAP = final_artifacts;
|
global.PAGE_FILES = pages;
|
||||||
params?.post_build_fn?.({ artifacts: final_artifacts });
|
params?.post_build_fn?.({ artifacts });
|
||||||
writeFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, JSON.stringify(artifacts));
|
writeFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, JSON.stringify(artifacts));
|
||||||
}
|
}
|
||||||
const elapsed = (performance.now() - buildStart).toFixed(0);
|
const elapsed = (performance.now() - buildStart).toFixed(0);
|
||||||
log.success(`Built in ${elapsed}ms`);
|
log.success(`[Built] in ${elapsed}ms`);
|
||||||
if (params?.exit_after_first_build) {
|
if (params?.exit_after_first_build) {
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
@ -129,9 +76,10 @@ export default async function allPagesBundler(params) {
|
|||||||
},
|
},
|
||||||
entryNames: "[dir]/[name]/[hash]",
|
entryNames: "[dir]/[name]/[hash]",
|
||||||
metafile: true,
|
metafile: true,
|
||||||
plugins: [tailwindPlugin, virtualPlugin, artifactTracker],
|
plugins: [tailwindEsbuildPlugin, virtualPlugin, artifactTracker],
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
splitting: true,
|
splitting: true,
|
||||||
|
logLevel: "silent",
|
||||||
});
|
});
|
||||||
await ctx.rebuild();
|
await ctx.rebuild();
|
||||||
if (params?.watch) {
|
if (params?.watch) {
|
||||||
|
|||||||
35
dist/functions/bundler/grab-artifacts-from-bundled-result.js
vendored
Normal file
35
dist/functions/bundler/grab-artifacts-from-bundled-result.js
vendored
Normal file
@ -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;
|
||||||
|
}
|
||||||
65
dist/functions/bundler/grab-client-hydration-script.js
vendored
Normal file
65
dist/functions/bundler/grab-client-hydration-script.js
vendored
Normal file
@ -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;
|
||||||
|
}
|
||||||
6
dist/functions/init.js
vendored
6
dist/functions/init.js
vendored
@ -1,10 +1,16 @@
|
|||||||
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
||||||
import grabDirNames from "../utils/grab-dir-names";
|
import grabDirNames from "../utils/grab-dir-names";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
|
import path from "path";
|
||||||
export default async function () {
|
export default async function () {
|
||||||
const dirNames = grabDirNames();
|
const dirNames = grabDirNames();
|
||||||
execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`);
|
execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`);
|
||||||
execSync(`rm -rf ${dirNames.BUNX_CWD_MODULE_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);
|
const keys = Object.keys(dirNames);
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
const key = keys[i];
|
const key = keys[i];
|
||||||
|
|||||||
24
dist/functions/server/handle-files.js
vendored
Normal file
24
dist/functions/server/handle-files.js
vendored
Normal 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
dist/functions/server/handle-hmr-update.js
vendored
Normal file
54
dist/functions/server/handle-hmr-update.js
vendored
Normal 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
dist/functions/server/handle-hmr.js
vendored
Normal file
34
dist/functions/server/handle-hmr.js
vendored
Normal 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
dist/functions/server/handle-public.js
vendored
Normal file
25
dist/functions/server/handle-public.js
vendored
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
101
dist/functions/server/server-params-gen.js
vendored
101
dist/functions/server/server-params-gen.js
vendored
@ -1,21 +1,22 @@
|
|||||||
import path from "path";
|
|
||||||
import grabAppPort from "../../utils/grab-app-port";
|
import grabAppPort from "../../utils/grab-app-port";
|
||||||
import grabDirNames from "../../utils/grab-dir-names";
|
|
||||||
import handleWebPages from "./web-pages/handle-web-pages";
|
import handleWebPages from "./web-pages/handle-web-pages";
|
||||||
import handleRoutes from "./handle-routes";
|
import handleRoutes from "./handle-routes";
|
||||||
import isDevelopment from "../../utils/is-development";
|
import isDevelopment from "../../utils/is-development";
|
||||||
import grabConstants from "../../utils/grab-constants";
|
import grabConstants from "../../utils/grab-constants";
|
||||||
import { AppData } from "../../data/app-data";
|
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) {
|
export default async function (params) {
|
||||||
const port = grabAppPort();
|
const port = grabAppPort();
|
||||||
const { PUBLIC_DIR } = grabDirNames();
|
|
||||||
const is_dev = isDevelopment();
|
const is_dev = isDevelopment();
|
||||||
return {
|
return {
|
||||||
async fetch(req, server) {
|
async fetch(req, server) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const { config } = grabConstants();
|
const { config } = grabConstants();
|
||||||
|
let response = undefined;
|
||||||
if (config?.middleware) {
|
if (config?.middleware) {
|
||||||
const middleware_res = await config.middleware({
|
const middleware_res = await config.middleware({
|
||||||
req,
|
req,
|
||||||
@ -26,81 +27,31 @@ export default async function (params) {
|
|||||||
return middleware_res;
|
return middleware_res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (url.pathname === "/__hmr" && is_dev) {
|
if (url.pathname == `/${AppData["ClientHMRPath"]}`) {
|
||||||
const referer_url = new URL(req.headers.get("referer") || "");
|
response = await handleHmrUpdate({ req, server });
|
||||||
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.startsWith("/api/")) {
|
else if (url.pathname === "/__hmr" && is_dev) {
|
||||||
return await handleRoutes({ req, server });
|
response = await handleHmr({ req, server });
|
||||||
}
|
}
|
||||||
if (url.pathname.startsWith("/public/")) {
|
else if (url.pathname.startsWith("/api/")) {
|
||||||
try {
|
response = await handleRoutes({ req, server });
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if (url.pathname.startsWith("/favicon.") ) {
|
else if (url.pathname.startsWith("/public/")) {
|
||||||
if (url.pathname.match(/\..*$/)) {
|
response = await handlePublic({ req, server });
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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) {
|
catch (error) {
|
||||||
return new Response(`Server Error: ${error.message}`, {
|
return new Response(`Server Error: ${error.message}`, {
|
||||||
|
|||||||
9
dist/functions/server/watcher.js
vendored
9
dist/functions/server/watcher.js
vendored
@ -5,7 +5,7 @@ import rebuildBundler from "./rebuild-bundler";
|
|||||||
import { log } from "../../utils/log";
|
import { log } from "../../utils/log";
|
||||||
const { SRC_DIR } = grabDirNames();
|
const { SRC_DIR } = grabDirNames();
|
||||||
export default function watcher() {
|
export default function watcher() {
|
||||||
watch(SRC_DIR, {
|
const pages_src_watcher = watch(SRC_DIR, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
persistent: true,
|
persistent: true,
|
||||||
}, async (event, filename) => {
|
}, async (event, filename) => {
|
||||||
@ -13,6 +13,8 @@ export default function watcher() {
|
|||||||
return;
|
return;
|
||||||
if (event !== "rename")
|
if (event !== "rename")
|
||||||
return;
|
return;
|
||||||
|
if (!filename.match(/^pages\//))
|
||||||
|
return;
|
||||||
if (global.RECOMPILING)
|
if (global.RECOMPILING)
|
||||||
return;
|
return;
|
||||||
const fullPath = path.join(SRC_DIR, filename);
|
const fullPath = path.join(SRC_DIR, filename);
|
||||||
@ -28,5 +30,10 @@ export default function watcher() {
|
|||||||
finally {
|
finally {
|
||||||
global.RECOMPILING = false;
|
global.RECOMPILING = false;
|
||||||
}
|
}
|
||||||
|
if (global.PAGES_SRC_WATCHER) {
|
||||||
|
global.PAGES_SRC_WATCHER.close();
|
||||||
|
watcher();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
global.PAGES_SRC_WATCHER = pages_src_watcher;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,18 @@ import EJSON from "../../../utils/ejson";
|
|||||||
import isDevelopment from "../../../utils/is-development";
|
import isDevelopment from "../../../utils/is-development";
|
||||||
import grabWebPageHydrationScript from "./grab-web-page-hydration-script";
|
import grabWebPageHydrationScript from "./grab-web-page-hydration-script";
|
||||||
import grabWebMetaHTML from "./grab-web-meta-html";
|
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 { ClientRootElementIDName, ClientWindowPagePropsName } = grabContants();
|
||||||
const { renderToString } = await import(path.join(process.cwd(), "node_modules", "react-dom", "server"));
|
const { renderToString } = await import(path.join(process.cwd(), "node_modules", "react-dom", "server"));
|
||||||
|
if (debug) {
|
||||||
|
log.info("component", component);
|
||||||
|
}
|
||||||
const componentHTML = renderToString(component);
|
const componentHTML = renderToString(component);
|
||||||
|
if (debug) {
|
||||||
|
log.info("componentHTML", componentHTML);
|
||||||
|
}
|
||||||
const headHTML = Head
|
const headHTML = Head
|
||||||
? renderToString(_jsx(Head, { serverRes: pageProps, ctx: routeParams }))
|
? 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`;
|
html += ` <script>window.${ClientWindowPagePropsName} = ${EJSON.stringify(pageProps || {}) || "{}"}</script>\n`;
|
||||||
if (bundledMap?.path) {
|
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()) {
|
if (isDevelopment()) {
|
||||||
html += `<script defer>\n${await grabWebPageHydrationScript({ bundledMap })}\n</script>\n`;
|
html += `<script defer>\n${await grabWebPageHydrationScript({ bundledMap })}\n</script>\n`;
|
||||||
|
|||||||
55
dist/functions/server/web-pages/generate-web-page-response-from-component-return.js
vendored
Normal file
55
dist/functions/server/web-pages/generate-web-page-response-from-component-return.js
vendored
Normal file
@ -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,25 +5,12 @@ import tailwindcss from "@tailwindcss/postcss";
|
|||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
import grabDirNames from "../../../utils/grab-dir-names";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
const tailwindPlugin = {
|
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
|
||||||
name: "tailwindcss",
|
export default async function grabFilePathModule({ file_path, out_file, }) {
|
||||||
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, }) {
|
|
||||||
const dev = isDevelopment();
|
const dev = isDevelopment();
|
||||||
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
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({
|
await esbuild.build({
|
||||||
entryPoints: [file_path],
|
entryPoints: [file_path],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
@ -36,7 +23,7 @@ export default async function grabFilePathModule({ file_path, }) {
|
|||||||
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
|
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
|
||||||
},
|
},
|
||||||
metafile: true,
|
metafile: true,
|
||||||
plugins: [tailwindPlugin],
|
plugins: [tailwindEsbuildPlugin],
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
outfile: target_cache_file_path,
|
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 += `const props = JSON.parse("${server_res_json}")\n\n`;
|
||||||
tsx += ` return (\n`;
|
tsx += ` return (\n`;
|
||||||
if (root_file) {
|
if (root_file) {
|
||||||
tsx += ` <Root {...props}><Page {...props} /></Root>\n`;
|
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tsx += ` <Page {...props} />\n`;
|
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
|
||||||
}
|
}
|
||||||
tsx += ` )\n`;
|
tsx += ` )\n`;
|
||||||
tsx += `}\n`;
|
tsx += `}\n`;
|
||||||
@ -26,6 +26,7 @@ export default async function grabPageBundledReactComponent({ file_path, root_fi
|
|||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
server_res,
|
server_res,
|
||||||
|
tsx,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
@ -1,17 +1,14 @@
|
|||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
|
||||||
import grabRouteParams from "../../../utils/grab-route-params";
|
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 grabPageErrorComponent from "./grab-page-error-component";
|
||||||
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
|
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { log } from "../../../utils/log";
|
||||||
|
import grabRootFile from "./grab-root-file";
|
||||||
class NotFoundError extends Error {
|
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 url = req?.url ? new URL(req.url) : undefined;
|
||||||
const router = global.ROUTER;
|
const router = global.ROUTER;
|
||||||
const { PAGES_DIR } = grabDirNames();
|
|
||||||
let routeParams = undefined;
|
let routeParams = undefined;
|
||||||
try {
|
try {
|
||||||
routeParams = req ? await grabRouteParams({ req }) : undefined;
|
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) {
|
if (url_path && url?.search) {
|
||||||
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;
|
const match = url_path ? router.match(url_path) : undefined;
|
||||||
if (!match?.filePath && url?.pathname) {
|
if (!match?.filePath && url?.pathname) {
|
||||||
throw new NotFoundError(`Page ${url.pathname} not found`);
|
throw new NotFoundError(`Page ${url.pathname} not found`);
|
||||||
}
|
}
|
||||||
const file_path = match?.filePath || passed_file_path;
|
const file_path = match?.filePath || passed_file_path;
|
||||||
|
if (debug) {
|
||||||
|
log.info(`file_path:`, file_path);
|
||||||
|
}
|
||||||
if (!file_path) {
|
if (!file_path) {
|
||||||
const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`;
|
const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`;
|
||||||
// console.error(errMsg);
|
// console.error(errMsg);
|
||||||
@ -35,21 +38,14 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
|
|||||||
console.error(errMsg);
|
console.error(errMsg);
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`;
|
if (debug) {
|
||||||
const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`;
|
log.info(`bundledMap:`, bundledMap);
|
||||||
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 } = grabRootFile();
|
||||||
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();
|
|
||||||
const module = await import(file_path);
|
const module = await import(file_path);
|
||||||
|
if (debug) {
|
||||||
|
log.info(`module:`, module);
|
||||||
|
}
|
||||||
const serverRes = await (async () => {
|
const serverRes = await (async () => {
|
||||||
const default_props = {
|
const default_props = {
|
||||||
url: {
|
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
|
const meta = module.meta
|
||||||
? typeof module.meta == "function" && routeParams
|
? typeof module.meta == "function" && routeParams
|
||||||
? await module.meta({
|
? await module.meta({
|
||||||
@ -98,6 +97,9 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
|
|||||||
? module.meta
|
? module.meta
|
||||||
: undefined
|
: undefined
|
||||||
: undefined;
|
: undefined;
|
||||||
|
if (debug) {
|
||||||
|
log.info(`meta:`, meta);
|
||||||
|
}
|
||||||
const Head = module.Head;
|
const Head = module.Head;
|
||||||
const { component } = (await grabPageBundledReactComponent({
|
const { component } = (await grabPageBundledReactComponent({
|
||||||
file_path,
|
file_path,
|
||||||
@ -107,6 +109,9 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
|
|||||||
if (!component) {
|
if (!component) {
|
||||||
throw new Error(`Couldn't grab page component`);
|
throw new Error(`Couldn't grab page component`);
|
||||||
}
|
}
|
||||||
|
if (debug) {
|
||||||
|
log.info(`component:`, component);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
serverRes,
|
serverRes,
|
||||||
@ -118,6 +123,7 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(`Error Grabbing Page Component: ${error.message}`);
|
||||||
return await grabPageErrorComponent({
|
return await grabPageErrorComponent({
|
||||||
error,
|
error,
|
||||||
routeParams,
|
routeParams,
|
||||||
|
|||||||
21
dist/functions/server/web-pages/grab-root-file.js
vendored
Normal file
21
dist/functions/server/web-pages/grab-root-file.js
vendored
Normal 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 };
|
||||||
|
}
|
||||||
@ -6,21 +6,7 @@ import { readFile } from "fs/promises";
|
|||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
import grabDirNames from "../../../utils/grab-dir-names";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
const tailwindPlugin = {
|
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
|
||||||
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 grabTsxStringModule({ tsx, file_path, }) {
|
export default async function grabTsxStringModule({ tsx, file_path, }) {
|
||||||
const dev = isDevelopment();
|
const dev = isDevelopment();
|
||||||
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
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"),
|
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
|
||||||
},
|
},
|
||||||
metafile: true,
|
metafile: true,
|
||||||
plugins: [tailwindPlugin],
|
plugins: [tailwindEsbuildPlugin],
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
write: true,
|
write: true,
|
||||||
outfile: out_file_path,
|
outfile: out_file_path,
|
||||||
|
|||||||
@ -1,55 +1,109 @@
|
|||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
import { AppData } from "../../../data/app-data";
|
||||||
const { BUNX_HYDRATION_SRC_DIR } = grabDirNames();
|
|
||||||
export default async function ({ bundledMap }) {
|
export default async function ({ bundledMap }) {
|
||||||
let script = "";
|
let script = "";
|
||||||
// script += `import React from "react";\n`;
|
script += `console.log(\`Development Environment\`);\n\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 += `const hmr = new EventSource("/__hmr");\n`;
|
||||||
script += `hmr.addEventListener("update", async (event) => {\n`;
|
script += `hmr.addEventListener("update", async (event) => {\n`;
|
||||||
// script += ` console.log(\`HMR even received:\`, event);\n`;
|
|
||||||
script += ` if (event.data) {\n`;
|
script += ` if (event.data) {\n`;
|
||||||
script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`;
|
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
|
||||||
// script += ` console.log("event", event);\n`;
|
script += ` try {\n`;
|
||||||
// script += ` console.log("window.${ClientRootComponentWindowName}", window.${ClientRootComponentWindowName});\n\n`;
|
script += ` const data = JSON.parse(event.data);\n`;
|
||||||
// script += ` const event_data = JSON.parse(event.data);\n\n`;
|
// script += ` console.log("data", data);\n`;
|
||||||
// script += ` const new_js_path = \`/\${event_data.target_map.path}\`;\n\n`;
|
// script += ` const modulePath = \`/\${data.target_map.path}\`;\n\n`;
|
||||||
// script += ` console.log("event_data", event_data);\n\n`;
|
// script += ` const modulePath = \`/${AppData["ClientHMRPath"]}?href=\${window.location.href}&t=\${Date.now()}\`;\n\n`;
|
||||||
// script += ` console.log("new_js_path", new_js_path);\n\n`;
|
// script += ` console.log("Fetching updated module ...", modulePath);\n\n`;
|
||||||
// script += ` if (window.${ClientRootComponentWindowName}) {\n`;
|
// script += ` const newModule = await import(modulePath);\n\n`;
|
||||||
// script += ` const new_component = await import(new_js_path);\n`;
|
// script += ` console.log("newModule", newModule);\n\n`;
|
||||||
// script += ` window.${ClientRootComponentWindowName}.render(new_component);\n`;
|
// script += ` if (window.__BUNEXT_RERENDER__ && newModule.default) {\n`;
|
||||||
// script += ` }\n`;
|
// script += ` window.__BUNEXT_RERENDER__(newModule.default);\n`;
|
||||||
// script += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`;
|
// script += ` console.log(\`HMR: Component updated in-place\`);\n`;
|
||||||
// script += ` root.render(module.default);\n`;
|
// script += ` } else {\n`;
|
||||||
// script += ` })\n`;
|
// script += ` console.warn(\`HMR: No re-render helper found, falling back to reload\`);\n`;
|
||||||
// script += ` console.log("root", root);\n`;
|
// // script += ` window.location.reload();\n`;
|
||||||
// script += ` root.unmount();\n`;
|
// script += ` }\n\n`;
|
||||||
// script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
|
script += ` if (data.target_map.css_path) {\n`;
|
||||||
// script += ` root = hydrateRoot(container!, component);\n`;
|
script += ` const oldLink = document.querySelector('link[rel="stylesheet"]');\n`;
|
||||||
// script += ` window.history.pushState({ page: 1 }, "New Page Title", \`\${window.location.pathname}?v=\${Date.now()}\`);\n`;
|
script += ` const newLink = document.createElement("link");\n`;
|
||||||
// script += ` root.render(component);\n`;
|
script += ` newLink.rel = "stylesheet";\n`;
|
||||||
script += ` window.location.reload();\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`;
|
script += `});\n`;
|
||||||
return script;
|
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;
|
||||||
|
// }
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import isDevelopment from "../../../utils/is-development";
|
import isDevelopment from "../../../utils/is-development";
|
||||||
import getCache from "../../cache/get-cache";
|
import getCache from "../../cache/get-cache";
|
||||||
import writeCache from "../../cache/write-cache";
|
import generateWebPageResponseFromComponentReturn from "./generate-web-page-response-from-component-return";
|
||||||
import genWebHTML from "./generate-web-html";
|
|
||||||
import grabPageComponent from "./grab-page-component";
|
import grabPageComponent from "./grab-page-component";
|
||||||
import grabPageErrorComponent from "./grab-page-error-component";
|
import grabPageErrorComponent from "./grab-page-error-component";
|
||||||
export default async function handleWebPages({ req, }) {
|
export default async function handleWebPages({ req, }) {
|
||||||
@ -20,58 +19,18 @@ export default async function handleWebPages({ req, }) {
|
|||||||
return new Response(existing_cache, res_opts);
|
return new Response(existing_cache, res_opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const componentRes = await grabPageComponent({ req });
|
const componentRes = await grabPageComponent({
|
||||||
return await generateRes(componentRes);
|
req,
|
||||||
}
|
});
|
||||||
catch (error) {
|
return await generateWebPageResponseFromComponentReturn({
|
||||||
const componentRes = await grabPageErrorComponent({ error });
|
...componentRes,
|
||||||
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 res = new Response(html, res_opts);
|
catch (error) {
|
||||||
if (routeParams?.resTransform) {
|
console.error(`Error Handling Web Page: ${error.message}`);
|
||||||
return await routeParams.resTransform(res);
|
const componentRes = await grabPageErrorComponent({
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
return await generateWebPageResponseFromComponentReturn(componentRes);
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
dist/functions/server/web-pages/tailwind-esbuild-plugin.js
vendored
Normal file
20
dist/functions/server/web-pages/tailwind-esbuild-plugin.js
vendored
Normal file
@ -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
dist/functions/server/web-pages/write-hmr-tsx-module.js
vendored
Normal file
106
dist/functions/server/web-pages/write-hmr-tsx-module.js
vendored
Normal 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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
1
dist/index.js
vendored
1
dist/index.js
vendored
@ -11,6 +11,7 @@ global.ORA_SPINNER.clear();
|
|||||||
global.HMR_CONTROLLERS = [];
|
global.HMR_CONTROLLERS = [];
|
||||||
global.IS_FIRST_BUNDLE_READY = false;
|
global.IS_FIRST_BUNDLE_READY = false;
|
||||||
global.BUNDLER_REBUILDS = 0;
|
global.BUNDLER_REBUILDS = 0;
|
||||||
|
global.PAGE_FILES = [];
|
||||||
await init();
|
await init();
|
||||||
const { PAGES_DIR } = grabDirNames();
|
const { PAGES_DIR } = grabDirNames();
|
||||||
const router = new Bun.FileSystemRouter({
|
const router = new Bun.FileSystemRouter({
|
||||||
|
|||||||
12
dist/utils/log.js
vendored
12
dist/utils/log.js
vendored
@ -1,7 +1,7 @@
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import AppNames from "./grab-app-names";
|
import AppNames from "./grab-app-names";
|
||||||
const prefix = {
|
const prefix = {
|
||||||
info: chalk.cyan.bold("ℹ"),
|
info: chalk.bgCyan.bold(" ℹnfo "),
|
||||||
success: chalk.green.bold("✓"),
|
success: chalk.green.bold("✓"),
|
||||||
error: chalk.red.bold("✗"),
|
error: chalk.red.bold("✗"),
|
||||||
warn: chalk.yellow.bold("⚠"),
|
warn: chalk.yellow.bold("⚠"),
|
||||||
@ -9,12 +9,16 @@ const prefix = {
|
|||||||
watch: chalk.blue.bold("◉"),
|
watch: chalk.blue.bold("◉"),
|
||||||
};
|
};
|
||||||
export const log = {
|
export const log = {
|
||||||
info: (msg) => console.log(`${prefix.info} ${chalk.white(msg)}`),
|
info: (msg, log) => {
|
||||||
success: (msg) => console.log(`${prefix.success} ${chalk.green(msg)}`),
|
console.log(`${prefix.info} ${chalk.white(msg)}`, log || "");
|
||||||
|
},
|
||||||
|
success: (msg, log) => {
|
||||||
|
console.log(`${prefix.success} ${chalk.green(msg)}`, log || "");
|
||||||
|
},
|
||||||
error: (msg) => console.error(`${prefix.error} ${chalk.red(String(msg))}`),
|
error: (msg) => console.error(`${prefix.error} ${chalk.red(String(msg))}`),
|
||||||
warn: (msg) => console.warn(`${prefix.warn} ${chalk.yellow(msg)}`),
|
warn: (msg) => console.warn(`${prefix.warn} ${chalk.yellow(msg)}`),
|
||||||
build: (msg) => console.log(`${prefix.build} ${chalk.magenta(msg)}`),
|
build: (msg) => console.log(`${prefix.build} ${chalk.magenta(msg)}`),
|
||||||
watch: (msg) => console.log(`${prefix.watch} ${chalk.blue(msg)}`),
|
watch: (msg) => console.log(`${prefix.watch} ${chalk.blue(msg)}`),
|
||||||
server: (url) => console.log(`${prefix.success} ${chalk.white("Server running on")} ${chalk.cyan.underline(url)}`),
|
server: (url) => console.log(`${prefix.success} ${chalk.white("Server running on")} ${chalk.cyan.underline(url)}`),
|
||||||
banner: () => console.log(`\n ${chalk.cyan.bold(AppNames.name)} ${chalk.gray(`v${AppNames.version}`)}\n`),
|
banner: () => console.log(`\n ${chalk.cyan.bold(AppNames.name)} ${chalk.gray(`v${global.CURRENT_VERSION || AppNames["version"]}`)}\n`),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "@moduletrace/bunext",
|
"name": "@moduletrace/bunext",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"bin": {
|
"bin": {
|
||||||
"bunext": "dist/index.js"
|
"bunext": "dist/index.js"
|
||||||
},
|
},
|
||||||
@ -13,6 +13,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
|
"publish": "tsc --noEmit && tsc && git add . && git commit -m 'Update HMR. Make it true HMR. Add URL to page server props' && git push",
|
||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -11,6 +11,8 @@ export default function () {
|
|||||||
log.banner();
|
log.banner();
|
||||||
log.info("Starting production server ...");
|
log.info("Starting production server ...");
|
||||||
|
|
||||||
|
process.env.NODE_ENV = "production";
|
||||||
|
|
||||||
await init();
|
await init();
|
||||||
|
|
||||||
const config = await grabConfig();
|
const config = await grabConfig();
|
||||||
|
|||||||
@ -2,4 +2,6 @@ export const AppData = {
|
|||||||
DefaultCacheExpiryTimeSeconds: 60 * 60,
|
DefaultCacheExpiryTimeSeconds: 60 * 60,
|
||||||
DefaultCronInterval: 30000,
|
DefaultCronInterval: 30000,
|
||||||
BunextStaticFilesCacheExpiry: 60 * 60 * 24 * 7,
|
BunextStaticFilesCacheExpiry: 60 * 60 * 24 * 7,
|
||||||
|
ClientHMRPath: "__bunext_client_hmr__",
|
||||||
|
BunextClientHydrationScriptID: "bunext-client-hydration-script",
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -1,37 +1,16 @@
|
|||||||
import { existsSync, writeFileSync } from "fs";
|
import { writeFileSync } from "fs";
|
||||||
import path from "path";
|
|
||||||
import * as esbuild from "esbuild";
|
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 grabAllPages from "../../utils/grab-all-pages";
|
||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
import AppNames from "../../utils/grab-app-names";
|
|
||||||
import isDevelopment from "../../utils/is-development";
|
import isDevelopment from "../../utils/is-development";
|
||||||
import type { BundlerCTXMap } from "../../types";
|
import type { BundlerCTXMap } from "../../types";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import grabConstants from "../../utils/grab-constants";
|
|
||||||
import { log } from "../../utils/log";
|
import { log } from "../../utils/log";
|
||||||
|
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, PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } =
|
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
|
||||||
grabDirNames();
|
|
||||||
|
|
||||||
const tailwindPlugin: esbuild.Plugin = {
|
|
||||||
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",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
watch?: boolean;
|
watch?: boolean;
|
||||||
@ -41,37 +20,16 @@ type Params = {
|
|||||||
|
|
||||||
export default async function allPagesBundler(params?: Params) {
|
export default async function allPagesBundler(params?: Params) {
|
||||||
const pages = grabAllPages({ exclude_api: true });
|
const pages = grabAllPages({ exclude_api: true });
|
||||||
const { ClientRootElementIDName, ClientRootComponentWindowName } =
|
|
||||||
grabConstants();
|
|
||||||
|
|
||||||
const virtualEntries: Record<string, string> = {};
|
const virtualEntries: Record<string, string> = {};
|
||||||
const dev = isDevelopment();
|
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) {
|
for (const page of pages) {
|
||||||
const key = page.local_path;
|
const key = page.local_path;
|
||||||
|
|
||||||
let txt = ``;
|
const txt = grabClientHydrationScript({
|
||||||
txt += `import { hydrateRoot } from "react-dom/client";\n`;
|
page_local_path: page.local_path,
|
||||||
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`;
|
|
||||||
|
|
||||||
virtualEntries[key] = txt;
|
virtualEntries[key] = txt;
|
||||||
}
|
}
|
||||||
@ -104,48 +62,15 @@ export default async function allPagesBundler(params?: Params) {
|
|||||||
build.onEnd((result) => {
|
build.onEnd((result) => {
|
||||||
if (result.errors.length > 0) return;
|
if (result.errors.length > 0) return;
|
||||||
|
|
||||||
const artifacts: (BundlerCTXMap | undefined)[] = Object.entries(
|
const artifacts = grabArtifactsFromBundledResults({
|
||||||
result.metafile!.outputs,
|
pages,
|
||||||
)
|
result,
|
||||||
.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) {
|
if (artifacts?.[0] && artifacts.length > 0) {
|
||||||
return undefined;
|
global.BUNDLER_CTX_MAP = artifacts;
|
||||||
}
|
global.PAGE_FILES = pages;
|
||||||
|
params?.post_build_fn?.({ artifacts });
|
||||||
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),
|
|
||||||
) as BundlerCTXMap[];
|
|
||||||
global.BUNDLER_CTX_MAP = final_artifacts;
|
|
||||||
params?.post_build_fn?.({ artifacts: final_artifacts });
|
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
||||||
@ -154,7 +79,7 @@ export default async function allPagesBundler(params?: Params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const elapsed = (performance.now() - buildStart).toFixed(0);
|
const elapsed = (performance.now() - buildStart).toFixed(0);
|
||||||
log.success(`Built in ${elapsed}ms`);
|
log.success(`[Built] in ${elapsed}ms`);
|
||||||
|
|
||||||
if (params?.exit_after_first_build) {
|
if (params?.exit_after_first_build) {
|
||||||
process.exit();
|
process.exit();
|
||||||
@ -180,9 +105,10 @@ export default async function allPagesBundler(params?: Params) {
|
|||||||
},
|
},
|
||||||
entryNames: "[dir]/[name]/[hash]",
|
entryNames: "[dir]/[name]/[hash]",
|
||||||
metafile: true,
|
metafile: true,
|
||||||
plugins: [tailwindPlugin, virtualPlugin, artifactTracker],
|
plugins: [tailwindEsbuildPlugin, virtualPlugin, artifactTracker],
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
splitting: true,
|
splitting: true,
|
||||||
|
logLevel: "silent",
|
||||||
});
|
});
|
||||||
|
|
||||||
await ctx.rebuild();
|
await ctx.rebuild();
|
||||||
|
|||||||
56
src/functions/bundler/grab-artifacts-from-bundled-result.ts
Normal file
56
src/functions/bundler/grab-artifacts-from-bundled-result.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import path from "path";
|
||||||
|
import * as esbuild from "esbuild";
|
||||||
|
import type { BundlerCTXMap, PageFiles } from "../../types";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
result: esbuild.BuildResult<esbuild.BuildOptions>;
|
||||||
|
pages: PageFiles[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function grabArtifactsFromBundledResults({
|
||||||
|
result,
|
||||||
|
pages,
|
||||||
|
}: Params) {
|
||||||
|
if (result.errors.length > 0) return;
|
||||||
|
|
||||||
|
const artifacts: (BundlerCTXMap | undefined)[] = 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),
|
||||||
|
) as BundlerCTXMap[];
|
||||||
|
|
||||||
|
return final_artifacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
84
src/functions/bundler/grab-client-hydration-script.ts
Normal file
84
src/functions/bundler/grab-client-hydration-script.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
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();
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
page_local_path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function grabClientHydrationScript({ page_local_path }: Params) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
||||||
import grabDirNames from "../utils/grab-dir-names";
|
import grabDirNames from "../utils/grab-dir-names";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
export default async function () {
|
export default async function () {
|
||||||
const dirNames = grabDirNames();
|
const dirNames = grabDirNames();
|
||||||
@ -8,6 +9,14 @@ export default async function () {
|
|||||||
execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`);
|
execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`);
|
||||||
execSync(`rm -rf ${dirNames.BUNX_CWD_MODULE_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) as (keyof ReturnType<
|
const keys = Object.keys(dirNames) as (keyof ReturnType<
|
||||||
typeof grabDirNames
|
typeof grabDirNames
|
||||||
>)[];
|
>)[];
|
||||||
|
|||||||
33
src/functions/server/handle-files.ts
Normal file
33
src/functions/server/handle-files.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { Server } from "bun";
|
||||||
|
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();
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
req: Request;
|
||||||
|
server: Server;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function ({ req, server }: Params): Promise<Response> {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/functions/server/handle-hmr-update.ts
Normal file
84
src/functions/server/handle-hmr-update.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import type { Server } from "bun";
|
||||||
|
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();
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
req: Request;
|
||||||
|
server: Server;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function ({ req, server }: Params): Promise<Response> {
|
||||||
|
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: any) {
|
||||||
|
const error_msg = error.message;
|
||||||
|
|
||||||
|
console.error(error_msg);
|
||||||
|
|
||||||
|
return new Response(error_msg || "HMR Error", {
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/functions/server/handle-hmr.ts
Normal file
50
src/functions/server/handle-hmr.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import type { Server } from "bun";
|
||||||
|
import type { BunextServerRouteConfig, BunxRouteParams } from "../../types";
|
||||||
|
import grabRouteParams from "../../utils/grab-route-params";
|
||||||
|
import grabConstants from "../../utils/grab-constants";
|
||||||
|
import grabRouter from "../../utils/grab-router";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
req: Request;
|
||||||
|
server: Server;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function ({ req, server }: Params): Promise<Response> {
|
||||||
|
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: ReadableStreamDefaultController<string>;
|
||||||
|
const stream = new ReadableStream<string>({
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
40
src/functions/server/handle-public.ts
Normal file
40
src/functions/server/handle-public.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import type { Server } from "bun";
|
||||||
|
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();
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
req: Request;
|
||||||
|
server: Server;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function ({ req, server }: Params): Promise<Response> {
|
||||||
|
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: ResponseInit = {};
|
||||||
|
|
||||||
|
return new Response(file, res_opts);
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(`Public File Not Found`, {
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,14 @@
|
|||||||
import path from "path";
|
|
||||||
import type { ServeOptions } from "bun";
|
import type { ServeOptions } from "bun";
|
||||||
import grabAppPort from "../../utils/grab-app-port";
|
import grabAppPort from "../../utils/grab-app-port";
|
||||||
import grabDirNames from "../../utils/grab-dir-names";
|
|
||||||
import handleWebPages from "./web-pages/handle-web-pages";
|
import handleWebPages from "./web-pages/handle-web-pages";
|
||||||
import handleRoutes from "./handle-routes";
|
import handleRoutes from "./handle-routes";
|
||||||
import isDevelopment from "../../utils/is-development";
|
import isDevelopment from "../../utils/is-development";
|
||||||
import grabConstants from "../../utils/grab-constants";
|
import grabConstants from "../../utils/grab-constants";
|
||||||
import { AppData } from "../../data/app-data";
|
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";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
dev?: boolean;
|
dev?: boolean;
|
||||||
@ -15,7 +16,6 @@ type Params = {
|
|||||||
|
|
||||||
export default async function (params?: Params): Promise<ServeOptions> {
|
export default async function (params?: Params): Promise<ServeOptions> {
|
||||||
const port = grabAppPort();
|
const port = grabAppPort();
|
||||||
const { PUBLIC_DIR } = grabDirNames();
|
|
||||||
|
|
||||||
const is_dev = isDevelopment();
|
const is_dev = isDevelopment();
|
||||||
|
|
||||||
@ -26,6 +26,8 @@ export default async function (params?: Params): Promise<ServeOptions> {
|
|||||||
|
|
||||||
const { config } = grabConstants();
|
const { config } = grabConstants();
|
||||||
|
|
||||||
|
let response: Response | undefined = undefined;
|
||||||
|
|
||||||
if (config?.middleware) {
|
if (config?.middleware) {
|
||||||
const middleware_res = await config.middleware({
|
const middleware_res = await config.middleware({
|
||||||
req,
|
req,
|
||||||
@ -38,109 +40,32 @@ export default async function (params?: Params): Promise<ServeOptions> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.pathname === "/__hmr" && is_dev) {
|
if (url.pathname == `/${AppData["ClientHMRPath"]}`) {
|
||||||
const referer_url = new URL(
|
response = await handleHmrUpdate({ req, server });
|
||||||
req.headers.get("referer") || "",
|
} else if (url.pathname === "/__hmr" && is_dev) {
|
||||||
|
response = await handleHmr({ req, server });
|
||||||
|
} else if (url.pathname.startsWith("/api/")) {
|
||||||
|
response = await handleRoutes({ req, server });
|
||||||
|
} else if (url.pathname.startsWith("/public/")) {
|
||||||
|
response = await handlePublic({ req, server });
|
||||||
|
} 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",
|
||||||
);
|
);
|
||||||
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: ReadableStreamDefaultController<string>;
|
|
||||||
const stream = new ReadableStream<string>({
|
|
||||||
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.startsWith("/api/")) {
|
return response;
|
||||||
return await handleRoutes({ 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: ResponseInit = {};
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await handleWebPages({ req });
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new Response(`Server Error: ${error.message}`, {
|
return new Response(`Server Error: ${error.message}`, {
|
||||||
status: 500,
|
status: 500,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { log } from "../../utils/log";
|
|||||||
const { SRC_DIR } = grabDirNames();
|
const { SRC_DIR } = grabDirNames();
|
||||||
|
|
||||||
export default function watcher() {
|
export default function watcher() {
|
||||||
watch(
|
const pages_src_watcher = watch(
|
||||||
SRC_DIR,
|
SRC_DIR,
|
||||||
{
|
{
|
||||||
recursive: true,
|
recursive: true,
|
||||||
@ -17,6 +17,7 @@ export default function watcher() {
|
|||||||
if (!filename) return;
|
if (!filename) return;
|
||||||
|
|
||||||
if (event !== "rename") return;
|
if (event !== "rename") return;
|
||||||
|
if (!filename.match(/^pages\//)) return;
|
||||||
|
|
||||||
if (global.RECOMPILING) return;
|
if (global.RECOMPILING) return;
|
||||||
|
|
||||||
@ -34,6 +35,13 @@ export default function watcher() {
|
|||||||
} finally {
|
} finally {
|
||||||
global.RECOMPILING = false;
|
global.RECOMPILING = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (global.PAGES_SRC_WATCHER) {
|
||||||
|
global.PAGES_SRC_WATCHER.close();
|
||||||
|
watcher();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
global.PAGES_SRC_WATCHER = pages_src_watcher;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import type { LivePageDistGenParams } from "../../../types";
|
|||||||
import isDevelopment from "../../../utils/is-development";
|
import isDevelopment from "../../../utils/is-development";
|
||||||
import grabWebPageHydrationScript from "./grab-web-page-hydration-script";
|
import grabWebPageHydrationScript from "./grab-web-page-hydration-script";
|
||||||
import grabWebMetaHTML from "./grab-web-meta-html";
|
import grabWebMetaHTML from "./grab-web-meta-html";
|
||||||
|
import { log } from "../../../utils/log";
|
||||||
|
import { AppData } from "../../../data/app-data";
|
||||||
|
|
||||||
export default async function genWebHTML({
|
export default async function genWebHTML({
|
||||||
component,
|
component,
|
||||||
@ -14,6 +16,7 @@ export default async function genWebHTML({
|
|||||||
module,
|
module,
|
||||||
meta,
|
meta,
|
||||||
routeParams,
|
routeParams,
|
||||||
|
debug,
|
||||||
}: LivePageDistGenParams) {
|
}: LivePageDistGenParams) {
|
||||||
const { ClientRootElementIDName, ClientWindowPagePropsName } =
|
const { ClientRootElementIDName, ClientWindowPagePropsName } =
|
||||||
grabContants();
|
grabContants();
|
||||||
@ -22,7 +25,16 @@ export default async function genWebHTML({
|
|||||||
path.join(process.cwd(), "node_modules", "react-dom", "server")
|
path.join(process.cwd(), "node_modules", "react-dom", "server")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
log.info("component", component);
|
||||||
|
}
|
||||||
|
|
||||||
const componentHTML = renderToString(component);
|
const componentHTML = renderToString(component);
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
log.info("componentHTML", componentHTML);
|
||||||
|
}
|
||||||
|
|
||||||
const headHTML = Head
|
const headHTML = Head
|
||||||
? renderToString(<Head serverRes={pageProps} ctx={routeParams} />)
|
? renderToString(<Head serverRes={pageProps} ctx={routeParams} />)
|
||||||
: "";
|
: "";
|
||||||
@ -46,7 +58,7 @@ export default async function genWebHTML({
|
|||||||
}</script>\n`;
|
}</script>\n`;
|
||||||
|
|
||||||
if (bundledMap?.path) {
|
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()) {
|
if (isDevelopment()) {
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
import type { GrabPageComponentRes } from "../../../types";
|
||||||
|
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,
|
||||||
|
}: GrabPageComponentRes) {
|
||||||
|
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: ResponseInit = {
|
||||||
|
...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,37 +5,22 @@ import tailwindcss from "@tailwindcss/postcss";
|
|||||||
import { readFile } from "fs/promises";
|
import { readFile } from "fs/promises";
|
||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
import grabDirNames from "../../../utils/grab-dir-names";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
file_path: string;
|
file_path: string;
|
||||||
};
|
out_file?: string;
|
||||||
|
|
||||||
const tailwindPlugin: esbuild.Plugin = {
|
|
||||||
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<T extends any = any>({
|
export default async function grabFilePathModule<T extends any = any>({
|
||||||
file_path,
|
file_path,
|
||||||
|
out_file,
|
||||||
}: Params): Promise<T> {
|
}: Params): Promise<T> {
|
||||||
const dev = isDevelopment();
|
const dev = isDevelopment();
|
||||||
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
||||||
const target_cache_file_path = path.join(
|
const target_cache_file_path =
|
||||||
BUNX_CWD_MODULE_CACHE_DIR,
|
out_file ||
|
||||||
`${path.basename(file_path)}.js`,
|
path.join(BUNX_CWD_MODULE_CACHE_DIR, `${path.basename(file_path)}.js`);
|
||||||
);
|
|
||||||
|
|
||||||
await esbuild.build({
|
await esbuild.build({
|
||||||
entryPoints: [file_path],
|
entryPoints: [file_path],
|
||||||
@ -51,7 +36,7 @@ export default async function grabFilePathModule<T extends any = any>({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
metafile: true,
|
metafile: true,
|
||||||
plugins: [tailwindPlugin],
|
plugins: [tailwindEsbuildPlugin],
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
outfile: target_cache_file_path,
|
outfile: target_cache_file_path,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -30,9 +30,9 @@ export default async function grabPageBundledReactComponent({
|
|||||||
tsx += `const props = JSON.parse("${server_res_json}")\n\n`;
|
tsx += `const props = JSON.parse("${server_res_json}")\n\n`;
|
||||||
tsx += ` return (\n`;
|
tsx += ` return (\n`;
|
||||||
if (root_file) {
|
if (root_file) {
|
||||||
tsx += ` <Root {...props}><Page {...props} /></Root>\n`;
|
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
|
||||||
} else {
|
} else {
|
||||||
tsx += ` <Page {...props} />\n`;
|
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
|
||||||
}
|
}
|
||||||
tsx += ` )\n`;
|
tsx += ` )\n`;
|
||||||
tsx += `}\n`;
|
tsx += `}\n`;
|
||||||
@ -44,6 +44,7 @@ export default async function grabPageBundledReactComponent({
|
|||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
server_res,
|
server_res,
|
||||||
|
tsx,
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
|
||||||
import grabRouteParams from "../../../utils/grab-route-params";
|
import grabRouteParams from "../../../utils/grab-route-params";
|
||||||
import type {
|
import type {
|
||||||
BunextPageModule,
|
BunextPageModule,
|
||||||
@ -7,29 +6,28 @@ import type {
|
|||||||
BunxRouteParams,
|
BunxRouteParams,
|
||||||
GrabPageComponentRes,
|
GrabPageComponentRes,
|
||||||
} from "../../../types";
|
} from "../../../types";
|
||||||
import path from "path";
|
|
||||||
import AppNames from "../../../utils/grab-app-names";
|
|
||||||
import { existsSync } from "fs";
|
|
||||||
import grabPageErrorComponent from "./grab-page-error-component";
|
import grabPageErrorComponent from "./grab-page-error-component";
|
||||||
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
|
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { log } from "../../../utils/log";
|
||||||
|
import grabRootFile from "./grab-root-file";
|
||||||
|
|
||||||
class NotFoundError extends Error {}
|
class NotFoundError extends Error {}
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
req?: Request;
|
req?: Request;
|
||||||
file_path?: string;
|
file_path?: string;
|
||||||
|
debug?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function grabPageComponent({
|
export default async function grabPageComponent({
|
||||||
req,
|
req,
|
||||||
file_path: passed_file_path,
|
file_path: passed_file_path,
|
||||||
|
debug,
|
||||||
}: Params): Promise<GrabPageComponentRes> {
|
}: Params): Promise<GrabPageComponentRes> {
|
||||||
const url = req?.url ? new URL(req.url) : undefined;
|
const url = req?.url ? new URL(req.url) : undefined;
|
||||||
const router = global.ROUTER;
|
const router = global.ROUTER;
|
||||||
|
|
||||||
const { PAGES_DIR } = grabDirNames();
|
|
||||||
|
|
||||||
let routeParams: BunxRouteParams | undefined = undefined;
|
let routeParams: BunxRouteParams | undefined = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -41,6 +39,10 @@ export default async function grabPageComponent({
|
|||||||
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;
|
const match = url_path ? router.match(url_path) : undefined;
|
||||||
|
|
||||||
if (!match?.filePath && url?.pathname) {
|
if (!match?.filePath && url?.pathname) {
|
||||||
@ -49,6 +51,10 @@ export default async function grabPageComponent({
|
|||||||
|
|
||||||
const file_path = match?.filePath || passed_file_path;
|
const file_path = match?.filePath || passed_file_path;
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
log.info(`file_path:`, file_path);
|
||||||
|
}
|
||||||
|
|
||||||
if (!file_path) {
|
if (!file_path) {
|
||||||
const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`;
|
const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`;
|
||||||
// console.error(errMsg);
|
// console.error(errMsg);
|
||||||
@ -65,25 +71,18 @@ export default async function grabPageComponent({
|
|||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`;
|
if (debug) {
|
||||||
const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`;
|
log.info(`bundledMap:`, bundledMap);
|
||||||
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)
|
const { root_file } = grabRootFile();
|
||||||
? 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();
|
|
||||||
|
|
||||||
const module: BunextPageModule = await import(file_path);
|
const module: BunextPageModule = await import(file_path);
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
log.info(`module:`, module);
|
||||||
|
}
|
||||||
|
|
||||||
const serverRes: BunextPageModuleServerReturn = await (async () => {
|
const serverRes: BunextPageModuleServerReturn = await (async () => {
|
||||||
const default_props: BunextPageModuleServerReturn = {
|
const default_props: BunextPageModuleServerReturn = {
|
||||||
url: {
|
url: {
|
||||||
@ -123,6 +122,10 @@ export default async function grabPageComponent({
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
log.info(`serverRes:`, serverRes);
|
||||||
|
}
|
||||||
|
|
||||||
const meta = module.meta
|
const meta = module.meta
|
||||||
? typeof module.meta == "function" && routeParams
|
? typeof module.meta == "function" && routeParams
|
||||||
? await module.meta({
|
? await module.meta({
|
||||||
@ -134,6 +137,10 @@ export default async function grabPageComponent({
|
|||||||
: undefined
|
: undefined
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
log.info(`meta:`, meta);
|
||||||
|
}
|
||||||
|
|
||||||
const Head = module.Head as FC<any>;
|
const Head = module.Head as FC<any>;
|
||||||
|
|
||||||
const { component } =
|
const { component } =
|
||||||
@ -147,6 +154,10 @@ export default async function grabPageComponent({
|
|||||||
throw new Error(`Couldn't grab page component`);
|
throw new Error(`Couldn't grab page component`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
log.info(`component:`, component);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
serverRes,
|
serverRes,
|
||||||
@ -157,6 +168,8 @@ export default async function grabPageComponent({
|
|||||||
head: Head,
|
head: Head,
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error(`Error Grabbing Page Component: ${error.message}`);
|
||||||
|
|
||||||
return await grabPageErrorComponent({
|
return await grabPageErrorComponent({
|
||||||
error,
|
error,
|
||||||
routeParams,
|
routeParams,
|
||||||
|
|||||||
25
src/functions/server/web-pages/grab-root-file.tsx
Normal file
25
src/functions/server/web-pages/grab-root-file.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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 };
|
||||||
|
}
|
||||||
@ -6,29 +6,13 @@ import { readFile } from "fs/promises";
|
|||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
import grabDirNames from "../../../utils/grab-dir-names";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
|
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
tsx: string;
|
tsx: string;
|
||||||
file_path: string;
|
file_path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tailwindPlugin: esbuild.Plugin = {
|
|
||||||
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 grabTsxStringModule<T extends any = any>({
|
export default async function grabTsxStringModule<T extends any = any>({
|
||||||
tsx,
|
tsx,
|
||||||
file_path,
|
file_path,
|
||||||
@ -63,7 +47,7 @@ export default async function grabTsxStringModule<T extends any = any>({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
metafile: true,
|
metafile: true,
|
||||||
plugins: [tailwindPlugin],
|
plugins: [tailwindEsbuildPlugin],
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
write: true,
|
write: true,
|
||||||
outfile: out_file_path,
|
outfile: out_file_path,
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
import type { BundlerCTXMap } from "../../../types";
|
||||||
import type { BundlerCTXMap, PageDistGenParams } from "../../../types";
|
import { AppData } from "../../../data/app-data";
|
||||||
|
|
||||||
const { BUNX_HYDRATION_SRC_DIR } = grabDirNames();
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
bundledMap?: BundlerCTXMap;
|
bundledMap?: BundlerCTXMap;
|
||||||
@ -10,62 +8,127 @@ type Params = {
|
|||||||
export default async function ({ bundledMap }: Params) {
|
export default async function ({ bundledMap }: Params) {
|
||||||
let script = "";
|
let script = "";
|
||||||
|
|
||||||
// script += `import React from "react";\n`;
|
script += `console.log(\`Development Environment\`);\n\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 += `const hmr = new EventSource("/__hmr");\n`;
|
||||||
script += `hmr.addEventListener("update", async (event) => {\n`;
|
script += `hmr.addEventListener("update", async (event) => {\n`;
|
||||||
// script += ` console.log(\`HMR even received:\`, event);\n`;
|
|
||||||
script += ` if (event.data) {\n`;
|
script += ` if (event.data) {\n`;
|
||||||
script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`;
|
script += ` console.log(\`HMR Changes Detected. Updating ...\`);\n`;
|
||||||
// script += ` console.log("event", event);\n`;
|
script += ` try {\n`;
|
||||||
// script += ` console.log("window.${ClientRootComponentWindowName}", window.${ClientRootComponentWindowName});\n\n`;
|
script += ` const data = JSON.parse(event.data);\n`;
|
||||||
// script += ` const event_data = JSON.parse(event.data);\n\n`;
|
// script += ` console.log("data", data);\n`;
|
||||||
// script += ` const new_js_path = \`/\${event_data.target_map.path}\`;\n\n`;
|
// script += ` const modulePath = \`/\${data.target_map.path}\`;\n\n`;
|
||||||
|
|
||||||
// script += ` console.log("event_data", event_data);\n\n`;
|
// script += ` const modulePath = \`/${AppData["ClientHMRPath"]}?href=\${window.location.href}&t=\${Date.now()}\`;\n\n`;
|
||||||
// script += ` console.log("new_js_path", new_js_path);\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 (window.${ClientRootComponentWindowName}) {\n`;
|
script += ` if (data.target_map.css_path) {\n`;
|
||||||
// script += ` const new_component = await import(new_js_path);\n`;
|
script += ` const oldLink = document.querySelector('link[rel="stylesheet"]');\n`;
|
||||||
// script += ` window.${ClientRootComponentWindowName}.render(new_component);\n`;
|
script += ` const newLink = document.createElement("link");\n`;
|
||||||
// script += ` }\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 += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`;
|
script += ` const newScriptPath = \`/\${data.target_map.path}?t=\${Date.now()}\`;\n\n`;
|
||||||
// script += ` root.render(module.default);\n`;
|
script += ` const oldScript = document.getElementById("${AppData["BunextClientHydrationScriptID"]}");\n`;
|
||||||
// script += ` })\n`;
|
script += ` if (oldScript) {\n`;
|
||||||
// script += ` console.log("root", root);\n`;
|
script += ` oldScript.remove();\n`;
|
||||||
// script += ` root.unmount();\n`;
|
script += ` }\n\n`;
|
||||||
// script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
|
script += ` const newScript = document.createElement("script");\n`;
|
||||||
// script += ` root = hydrateRoot(container!, component);\n`;
|
script += ` newScript.id = "${AppData["BunextClientHydrationScriptID"]}";\n`;
|
||||||
// script += ` window.history.pushState({ page: 1 }, "New Page Title", \`\${window.location.pathname}?v=\${Date.now()}\`);\n`;
|
script += ` newScript.type = "module";\n`;
|
||||||
// script += ` root.render(component);\n`;
|
script += ` newScript.src = newScriptPath;\n`;
|
||||||
script += ` window.location.reload();\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`;
|
script += `});\n`;
|
||||||
|
|
||||||
return script;
|
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;
|
||||||
|
// }
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import type { GrabPageComponentRes } from "../../../types";
|
|
||||||
import isDevelopment from "../../../utils/is-development";
|
import isDevelopment from "../../../utils/is-development";
|
||||||
import getCache from "../../cache/get-cache";
|
import getCache from "../../cache/get-cache";
|
||||||
import writeCache from "../../cache/write-cache";
|
import generateWebPageResponseFromComponentReturn from "./generate-web-page-response-from-component-return";
|
||||||
import genWebHTML from "./generate-web-html";
|
|
||||||
import grabPageComponent from "./grab-page-component";
|
import grabPageComponent from "./grab-page-component";
|
||||||
import grabPageErrorComponent from "./grab-page-error-component";
|
import grabPageErrorComponent from "./grab-page-error-component";
|
||||||
|
|
||||||
@ -32,78 +30,20 @@ export default async function handleWebPages({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentRes = await grabPageComponent({ req });
|
const componentRes = await grabPageComponent({
|
||||||
return await generateRes(componentRes);
|
req,
|
||||||
} catch (error: any) {
|
|
||||||
const componentRes = await grabPageErrorComponent({ error });
|
|
||||||
return await generateRes(componentRes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateRes({
|
|
||||||
component,
|
|
||||||
module,
|
|
||||||
bundledMap,
|
|
||||||
head,
|
|
||||||
meta,
|
|
||||||
routeParams,
|
|
||||||
serverRes,
|
|
||||||
}: GrabPageComponentRes) {
|
|
||||||
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: ResponseInit = {
|
|
||||||
...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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return await generateWebPageResponseFromComponentReturn({
|
||||||
|
...componentRes,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`Error Handling Web Page: ${error.message}`);
|
||||||
|
|
||||||
|
const componentRes = await grabPageErrorComponent({
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await generateWebPageResponseFromComponentReturn(componentRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = new Response(html, res_opts);
|
|
||||||
|
|
||||||
if (routeParams?.resTransform) {
|
|
||||||
return await routeParams.resTransform(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/functions/server/web-pages/tailwind-esbuild-plugin.tsx
Normal file
23
src/functions/server/web-pages/tailwind-esbuild-plugin.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import * as esbuild from "esbuild";
|
||||||
|
import postcss from "postcss";
|
||||||
|
import tailwindcss from "@tailwindcss/postcss";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
|
||||||
|
const tailwindEsbuildPlugin: esbuild.Plugin = {
|
||||||
|
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;
|
||||||
126
src/functions/server/web-pages/write-hmr-tsx-module.tsx
Normal file
126
src/functions/server/web-pages/write-hmr-tsx-module.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@ -8,12 +8,14 @@ import type {
|
|||||||
BundlerCTXMap,
|
BundlerCTXMap,
|
||||||
BunextConfig,
|
BunextConfig,
|
||||||
GlobalHMRControllerObject,
|
GlobalHMRControllerObject,
|
||||||
|
PageFiles,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import type { FileSystemRouter, Server } from "bun";
|
import type { FileSystemRouter, Server } from "bun";
|
||||||
import init from "./functions/init";
|
import init from "./functions/init";
|
||||||
import grabDirNames from "./utils/grab-dir-names";
|
import grabDirNames from "./utils/grab-dir-names";
|
||||||
import build from "./commands/build";
|
import build from "./commands/build";
|
||||||
import type { BuildContext } from "esbuild";
|
import type { BuildContext } from "esbuild";
|
||||||
|
import type { FSWatcher } from "fs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Declare Global Variables
|
* # Declare Global Variables
|
||||||
@ -31,6 +33,9 @@ declare global {
|
|||||||
var BUNDLER_CTX_MAP: BundlerCTXMap[] | undefined;
|
var BUNDLER_CTX_MAP: BundlerCTXMap[] | undefined;
|
||||||
var IS_FIRST_BUNDLE_READY: boolean;
|
var IS_FIRST_BUNDLE_READY: boolean;
|
||||||
var BUNDLER_REBUILDS: 0;
|
var BUNDLER_REBUILDS: 0;
|
||||||
|
var PAGES_SRC_WATCHER: FSWatcher | undefined;
|
||||||
|
var CURRENT_VERSION: string | undefined;
|
||||||
|
var PAGE_FILES: PageFiles[];
|
||||||
}
|
}
|
||||||
|
|
||||||
global.ORA_SPINNER = ora();
|
global.ORA_SPINNER = ora();
|
||||||
@ -38,6 +43,7 @@ global.ORA_SPINNER.clear();
|
|||||||
global.HMR_CONTROLLERS = [];
|
global.HMR_CONTROLLERS = [];
|
||||||
global.IS_FIRST_BUNDLE_READY = false;
|
global.IS_FIRST_BUNDLE_READY = false;
|
||||||
global.BUNDLER_REBUILDS = 0;
|
global.BUNDLER_REBUILDS = 0;
|
||||||
|
global.PAGE_FILES = [];
|
||||||
|
|
||||||
await init();
|
await init();
|
||||||
|
|
||||||
|
|||||||
@ -146,6 +146,7 @@ export type LivePageDistGenParams = {
|
|||||||
bundledMap?: BundlerCTXMap;
|
bundledMap?: BundlerCTXMap;
|
||||||
meta?: BunextPageModuleMeta;
|
meta?: BunextPageModuleMeta;
|
||||||
routeParams?: BunxRouteParams;
|
routeParams?: BunxRouteParams;
|
||||||
|
debug?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BunextPageHeadFCProps = {
|
export type BunextPageHeadFCProps = {
|
||||||
@ -244,11 +245,13 @@ export type GrabPageComponentRes = {
|
|||||||
module: BunextPageModule;
|
module: BunextPageModule;
|
||||||
meta?: BunextPageModuleMeta;
|
meta?: BunextPageModuleMeta;
|
||||||
head?: FC<BunextPageHeadFCProps>;
|
head?: FC<BunextPageHeadFCProps>;
|
||||||
|
debug?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GrabPageReactBundledComponentRes = {
|
export type GrabPageReactBundledComponentRes = {
|
||||||
component: JSX.Element;
|
component: JSX.Element;
|
||||||
server_res?: BunextPageModuleServerReturn;
|
server_res?: BunextPageModuleServerReturn;
|
||||||
|
tsx?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PageFiles = {
|
export type PageFiles = {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import chalk from "chalk";
|
|||||||
import AppNames from "./grab-app-names";
|
import AppNames from "./grab-app-names";
|
||||||
|
|
||||||
const prefix = {
|
const prefix = {
|
||||||
info: chalk.cyan.bold("ℹ"),
|
info: chalk.bgCyan.bold(" ℹnfo "),
|
||||||
success: chalk.green.bold("✓"),
|
success: chalk.green.bold("✓"),
|
||||||
error: chalk.red.bold("✗"),
|
error: chalk.red.bold("✗"),
|
||||||
warn: chalk.yellow.bold("⚠"),
|
warn: chalk.yellow.bold("⚠"),
|
||||||
@ -11,24 +11,24 @@ const prefix = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const log = {
|
export const log = {
|
||||||
info: (msg: string) =>
|
info: (msg: string, log?: any) => {
|
||||||
console.log(`${prefix.info} ${chalk.white(msg)}`),
|
console.log(`${prefix.info} ${chalk.white(msg)}`, log || "");
|
||||||
success: (msg: string) =>
|
},
|
||||||
console.log(`${prefix.success} ${chalk.green(msg)}`),
|
success: (msg: string, log?: any) => {
|
||||||
|
console.log(`${prefix.success} ${chalk.green(msg)}`, log || "");
|
||||||
|
},
|
||||||
error: (msg: string | Error) =>
|
error: (msg: string | Error) =>
|
||||||
console.error(`${prefix.error} ${chalk.red(String(msg))}`),
|
console.error(`${prefix.error} ${chalk.red(String(msg))}`),
|
||||||
warn: (msg: string) =>
|
warn: (msg: string) => console.warn(`${prefix.warn} ${chalk.yellow(msg)}`),
|
||||||
console.warn(`${prefix.warn} ${chalk.yellow(msg)}`),
|
|
||||||
build: (msg: string) =>
|
build: (msg: string) =>
|
||||||
console.log(`${prefix.build} ${chalk.magenta(msg)}`),
|
console.log(`${prefix.build} ${chalk.magenta(msg)}`),
|
||||||
watch: (msg: string) =>
|
watch: (msg: string) => console.log(`${prefix.watch} ${chalk.blue(msg)}`),
|
||||||
console.log(`${prefix.watch} ${chalk.blue(msg)}`),
|
|
||||||
server: (url: string) =>
|
server: (url: string) =>
|
||||||
console.log(
|
console.log(
|
||||||
`${prefix.success} ${chalk.white("Server running on")} ${chalk.cyan.underline(url)}`,
|
`${prefix.success} ${chalk.white("Server running on")} ${chalk.cyan.underline(url)}`,
|
||||||
),
|
),
|
||||||
banner: () =>
|
banner: () =>
|
||||||
console.log(
|
console.log(
|
||||||
`\n ${chalk.cyan.bold(AppNames.name)} ${chalk.gray(`v${AppNames.version}`)}\n`,
|
`\n ${chalk.cyan.bold(AppNames.name)} ${chalk.gray(`v${global.CURRENT_VERSION || AppNames["version"]}`)}\n`,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user