Change server paradigm
This commit is contained in:
parent
6f1db7c01f
commit
7dd8d87be8
@ -1,58 +1,60 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { renderToString } from "react-dom/server";
|
||||
import grabWebMetaHTML from "../../../functions/server/web-pages/grab-web-meta-html";
|
||||
|
||||
function render(meta: Parameters<typeof grabWebMetaHTML>[0]["meta"]) {
|
||||
return renderToString(grabWebMetaHTML({ meta }));
|
||||
}
|
||||
|
||||
describe("grabWebMetaHTML", () => {
|
||||
it("returns empty string for empty meta object", () => {
|
||||
expect(grabWebMetaHTML({ meta: {} })).toBe("");
|
||||
expect(render({})).toBe("");
|
||||
});
|
||||
|
||||
it("generates a title tag", () => {
|
||||
const html = grabWebMetaHTML({ meta: { title: "My Page" } });
|
||||
expect(html).toContain("<title>My Page</title>");
|
||||
expect(render({ title: "My Page" })).toContain("<title>My Page</title>");
|
||||
});
|
||||
|
||||
it("generates a description meta tag", () => {
|
||||
const html = grabWebMetaHTML({ meta: { description: "A description" } });
|
||||
expect(html).toContain('<meta name="description" content="A description"');
|
||||
expect(render({ description: "A description" })).toContain(
|
||||
'content="A description"',
|
||||
);
|
||||
});
|
||||
|
||||
it("joins array keywords with comma", () => {
|
||||
const html = grabWebMetaHTML({
|
||||
meta: { keywords: ["react", "bun", "ssr"] },
|
||||
});
|
||||
expect(html).toContain('content="react, bun, ssr"');
|
||||
expect(render({ keywords: ["react", "bun", "ssr"] })).toContain(
|
||||
'content="react, bun, ssr"',
|
||||
);
|
||||
});
|
||||
|
||||
it("uses string keywords directly", () => {
|
||||
const html = grabWebMetaHTML({ meta: { keywords: "react, bun" } });
|
||||
expect(html).toContain('content="react, bun"');
|
||||
expect(render({ keywords: "react, bun" })).toContain(
|
||||
'content="react, bun"',
|
||||
);
|
||||
});
|
||||
|
||||
it("generates author meta tag", () => {
|
||||
const html = grabWebMetaHTML({ meta: { author: "Alice" } });
|
||||
expect(html).toContain('<meta name="author" content="Alice"');
|
||||
expect(render({ author: "Alice" })).toContain('content="Alice"');
|
||||
});
|
||||
|
||||
it("generates robots meta tag", () => {
|
||||
const html = grabWebMetaHTML({ meta: { robots: "noindex" } });
|
||||
expect(html).toContain('<meta name="robots" content="noindex"');
|
||||
expect(render({ robots: "noindex" })).toContain('content="noindex"');
|
||||
});
|
||||
|
||||
it("generates canonical link tag", () => {
|
||||
const html = grabWebMetaHTML({
|
||||
meta: { canonical: "https://example.com/page" },
|
||||
});
|
||||
expect(html).toContain('<link rel="canonical" href="https://example.com/page"');
|
||||
expect(render({ canonical: "https://example.com/page" })).toContain(
|
||||
'href="https://example.com/page"',
|
||||
);
|
||||
});
|
||||
|
||||
it("generates theme-color meta tag", () => {
|
||||
const html = grabWebMetaHTML({ meta: { themeColor: "#ff0000" } });
|
||||
expect(html).toContain('<meta name="theme-color" content="#ff0000"');
|
||||
expect(render({ themeColor: "#ff0000" })).toContain(
|
||||
'content="#ff0000"',
|
||||
);
|
||||
});
|
||||
|
||||
it("generates OG tags", () => {
|
||||
const html = grabWebMetaHTML({
|
||||
meta: {
|
||||
const html = render({
|
||||
og: {
|
||||
title: "OG Title",
|
||||
description: "OG Desc",
|
||||
@ -62,20 +64,19 @@ describe("grabWebMetaHTML", () => {
|
||||
siteName: "Example",
|
||||
locale: "en_US",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(html).toContain('<meta property="og:title" content="OG Title"');
|
||||
expect(html).toContain('<meta property="og:description" content="OG Desc"');
|
||||
expect(html).toContain('<meta property="og:image" content="https://example.com/img.png"');
|
||||
expect(html).toContain('<meta property="og:url" content="https://example.com"');
|
||||
expect(html).toContain('<meta property="og:type" content="website"');
|
||||
expect(html).toContain('<meta property="og:site_name" content="Example"');
|
||||
expect(html).toContain('<meta property="og:locale" content="en_US"');
|
||||
expect(html).toContain('property="og:title"');
|
||||
expect(html).toContain('content="OG Title"');
|
||||
expect(html).toContain('property="og:description"');
|
||||
expect(html).toContain('property="og:image"');
|
||||
expect(html).toContain('property="og:url"');
|
||||
expect(html).toContain('property="og:type"');
|
||||
expect(html).toContain('property="og:site_name"');
|
||||
expect(html).toContain('property="og:locale"');
|
||||
});
|
||||
|
||||
it("generates Twitter card tags", () => {
|
||||
const html = grabWebMetaHTML({
|
||||
meta: {
|
||||
const html = render({
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Tweet Title",
|
||||
@ -84,25 +85,25 @@ describe("grabWebMetaHTML", () => {
|
||||
site: "@example",
|
||||
creator: "@alice",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(html).toContain('<meta name="twitter:card" content="summary_large_image"');
|
||||
expect(html).toContain('<meta name="twitter:title" content="Tweet Title"');
|
||||
expect(html).toContain('<meta name="twitter:description" content="Tweet Desc"');
|
||||
expect(html).toContain('<meta name="twitter:image" content="https://example.com/tw.png"');
|
||||
expect(html).toContain('<meta name="twitter:site" content="@example"');
|
||||
expect(html).toContain('<meta name="twitter:creator" content="@alice"');
|
||||
expect(html).toContain('name="twitter:card"');
|
||||
expect(html).toContain('content="summary_large_image"');
|
||||
expect(html).toContain('name="twitter:title"');
|
||||
expect(html).toContain('name="twitter:description"');
|
||||
expect(html).toContain('name="twitter:image"');
|
||||
expect(html).toContain('name="twitter:site"');
|
||||
expect(html).toContain('name="twitter:creator"');
|
||||
});
|
||||
|
||||
it("skips undefined OG fields", () => {
|
||||
const html = grabWebMetaHTML({ meta: { og: { title: "Only Title" } } });
|
||||
const html = render({ og: { title: "Only Title" } });
|
||||
expect(html).toContain("og:title");
|
||||
expect(html).not.toContain("og:description");
|
||||
expect(html).not.toContain("og:image");
|
||||
});
|
||||
|
||||
it("does not emit tags for missing fields", () => {
|
||||
const html = grabWebMetaHTML({ meta: { title: "Hello" } });
|
||||
const html = render({ title: "Hello" });
|
||||
expect(html).not.toContain("description");
|
||||
expect(html).not.toContain("og:");
|
||||
expect(html).not.toContain("twitter:");
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Command } from "commander";
|
||||
import { log } from "../../utils/log";
|
||||
import init from "../../functions/init";
|
||||
import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||
// import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||
import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import { rmSync } from "fs";
|
||||
import allPagesBundler from "../../functions/bundler/all-pages-bundler";
|
||||
|
||||
const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames();
|
||||
|
||||
@ -20,7 +21,9 @@ export default function () {
|
||||
rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
|
||||
} catch (error) {}
|
||||
|
||||
await rewritePagesModule();
|
||||
global.SKIPPED_BROWSER_MODULES = new Set<string>();
|
||||
|
||||
// await rewritePagesModule();
|
||||
await init();
|
||||
|
||||
log.banner();
|
||||
|
||||
@ -2,7 +2,7 @@ import { Command } from "commander";
|
||||
import startServer from "../../functions/server/start-server";
|
||||
import { log } from "../../utils/log";
|
||||
import bunextInit from "../../functions/bunext-init";
|
||||
import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||
// import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import { rmSync } from "fs";
|
||||
|
||||
@ -21,7 +21,7 @@ export default function () {
|
||||
rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
|
||||
} catch (error) {}
|
||||
|
||||
await rewritePagesModule();
|
||||
// await rewritePagesModule();
|
||||
await bunextInit();
|
||||
|
||||
await startServer();
|
||||
|
||||
@ -8,6 +8,7 @@ import path from "path";
|
||||
import grabClientHydrationScript from "./grab-client-hydration-script";
|
||||
import { mkdirSync, rmSync } from "fs";
|
||||
import recordArtifacts from "./record-artifacts";
|
||||
import BunSkipNonBrowserPlugin from "./plugins/bun-skip-browser-plugin";
|
||||
|
||||
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR, BUNX_TMP_DIR } =
|
||||
grabDirNames();
|
||||
|
||||
@ -9,6 +9,7 @@ import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-resul
|
||||
import { writeFileSync } from "fs";
|
||||
import type { BundlerCTXMap } from "../../types";
|
||||
import recordArtifacts from "./record-artifacts";
|
||||
import stripServerSideLogic from "./strip-server-side-logic";
|
||||
|
||||
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
|
||||
|
||||
@ -39,7 +40,7 @@ export default async function allPagesBundler(params?: Params) {
|
||||
const dev = isDevelopment();
|
||||
|
||||
for (const page of target_pages) {
|
||||
const key = page.transformed_path;
|
||||
const key = page.local_path;
|
||||
|
||||
const txt = await grabClientHydrationScript({
|
||||
page_local_path: page.local_path,
|
||||
@ -51,6 +52,13 @@ export default async function allPagesBundler(params?: Params) {
|
||||
|
||||
if (!txt) continue;
|
||||
|
||||
// const final_tsx = stripServerSideLogic({
|
||||
// txt_code: txt,
|
||||
// file_path: key,
|
||||
// });
|
||||
|
||||
// console.log("final_tsx", final_tsx);
|
||||
|
||||
virtualEntries[key] = txt;
|
||||
}
|
||||
|
||||
@ -94,6 +102,31 @@ export default async function allPagesBundler(params?: Params) {
|
||||
|
||||
const entryPoints = Object.keys(virtualEntries).map((k) => `virtual:${k}`);
|
||||
|
||||
// let alias: any = {};
|
||||
// const excludes = [
|
||||
// "bun:sqlite",
|
||||
// "path",
|
||||
// "url",
|
||||
// "events",
|
||||
// "util",
|
||||
// "crypto",
|
||||
// "net",
|
||||
// "tls",
|
||||
// "fs",
|
||||
// "node:path",
|
||||
// "node:url",
|
||||
// "node:process",
|
||||
// "node:fs",
|
||||
// "node:timers/promises",
|
||||
// ];
|
||||
|
||||
// for (let i = 0; i < excludes.length; i++) {
|
||||
// const exclude = excludes[i];
|
||||
// alias[exclude] = "./empty.js";
|
||||
// }
|
||||
|
||||
// console.log("alias", alias);
|
||||
|
||||
const result = await esbuild.build({
|
||||
entryPoints,
|
||||
outdir: HYDRATION_DST_DIR,
|
||||
@ -119,6 +152,7 @@ export default async function allPagesBundler(params?: Params) {
|
||||
"react-dom/client",
|
||||
"react/jsx-runtime",
|
||||
],
|
||||
// alias,
|
||||
});
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
|
||||
@ -23,24 +23,24 @@ export default async function grabClientHydrationScript({
|
||||
|
||||
const { root_file_path } = grabRootFilePath();
|
||||
|
||||
const target_path = pagePathTransform({ page_path: page_local_path });
|
||||
const target_root_path = root_file_path
|
||||
? pagePathTransform({ page_path: root_file_path })
|
||||
: undefined;
|
||||
// const target_path = pagePathTransform({ page_path: page_local_path });
|
||||
// const target_root_path = root_file_path
|
||||
// ? pagePathTransform({ page_path: root_file_path })
|
||||
// : undefined;
|
||||
|
||||
let txt = ``;
|
||||
|
||||
txt += `import { hydrateRoot } from "react-dom/client";\n`;
|
||||
if (target_root_path) {
|
||||
txt += `import Root from "${target_root_path}";\n`;
|
||||
if (root_file_path) {
|
||||
txt += `import Root from "${root_file_path}";\n`;
|
||||
}
|
||||
txt += `import Page from "${target_path}";\n\n`;
|
||||
txt += `import Page from "${page_local_path}";\n\n`;
|
||||
txt += `const pageProps = window.${ClientWindowPagePropsName} || {};\n`;
|
||||
|
||||
if (target_root_path) {
|
||||
txt += `const component = <Root suppressHydrationWarning={true} {...pageProps}><Page {...pageProps} /></Root>\n`;
|
||||
if (root_file_path) {
|
||||
txt += `const component = <Root {...pageProps}><Page {...pageProps} /></Root>\n`;
|
||||
} else {
|
||||
txt += `const component = <Page suppressHydrationWarning={true} {...pageProps} />\n`;
|
||||
txt += `const component = <Page {...pageProps} />\n`;
|
||||
}
|
||||
|
||||
txt += `if (window.${ClientRootComponentWindowName}?.render) {\n`;
|
||||
|
||||
@ -1,33 +1,84 @@
|
||||
import { log } from "../../../utils/log";
|
||||
|
||||
const BunSkipNonBrowserPlugin: Bun.BunPlugin = {
|
||||
name: "skip-non-browser",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^(bun:|node:)/ }, (args) => {
|
||||
return { path: args.path, external: true };
|
||||
const skipFilter =
|
||||
/^(bun:|node:|fs$|path$|os$|crypto$|net$|events$|util$|tls$|url$|process$)/;
|
||||
|
||||
// const skipped_modules = new Set<string>();
|
||||
|
||||
build.onResolve({ filter: skipFilter }, (args) => {
|
||||
global.SKIPPED_BROWSER_MODULES.add(args.path);
|
||||
return {
|
||||
path: args.path,
|
||||
namespace: "skipped",
|
||||
// external: true,
|
||||
};
|
||||
});
|
||||
|
||||
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
||||
// If it's a built-in like 'fs' or 'path', skip it immediately
|
||||
const excludes = [
|
||||
"fs",
|
||||
"path",
|
||||
"os",
|
||||
"crypto",
|
||||
"net",
|
||||
"events",
|
||||
"util",
|
||||
];
|
||||
// build.onEnd(() => {
|
||||
// log.warn(`global.SKIPPED_BROWSER_MODULES`, [
|
||||
// ...global.SKIPPED_BROWSER_MODULES,
|
||||
// ]);
|
||||
// });
|
||||
|
||||
if (excludes.includes(args.path) || args.path.startsWith("node:")) {
|
||||
return { path: args.path, external: true };
|
||||
}
|
||||
// build.onResolve({ filter: /^[^./]/ }, (args) => {
|
||||
// // If it's a built-in like 'fs' or 'path', skip it immediately
|
||||
// const excludes = [
|
||||
// "fs",
|
||||
// "path",
|
||||
// "os",
|
||||
// "crypto",
|
||||
// "net",
|
||||
// "events",
|
||||
// "util",
|
||||
// "tls",
|
||||
// ];
|
||||
|
||||
try {
|
||||
Bun.resolveSync(args.path, args.importer || process.cwd());
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.warn(`[Skip] Mark as external: ${args.path}`);
|
||||
return { path: args.path, external: true };
|
||||
}
|
||||
// if (excludes.includes(args.path) || args.path.startsWith("node:")) {
|
||||
// return {
|
||||
// path: args.path,
|
||||
// // namespace: "skipped",
|
||||
// external: true,
|
||||
// };
|
||||
// }
|
||||
|
||||
// try {
|
||||
// Bun.resolveSync(args.path, args.importer || process.cwd());
|
||||
// return null;
|
||||
// } catch (e) {
|
||||
// console.warn(`[Skip] Mark as external: ${args.path}`);
|
||||
// return {
|
||||
// path: args.path,
|
||||
// // namespace: "skipped",
|
||||
// external: true,
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: "skipped" }, (args) => {
|
||||
return {
|
||||
contents: `
|
||||
const proxy = new Proxy(() => proxy, {
|
||||
get: () => proxy,
|
||||
construct: () => proxy,
|
||||
});
|
||||
|
||||
export const Database = proxy;
|
||||
export const join = proxy;
|
||||
export const fileURLToPath = proxy;
|
||||
export const arch = proxy;
|
||||
export const platform = proxy;
|
||||
export const statSync = proxy;
|
||||
|
||||
export const $H = proxy;
|
||||
export const _ = proxy;
|
||||
|
||||
export default proxy;
|
||||
`,
|
||||
loader: "js",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@ -35,6 +35,7 @@ declare global {
|
||||
var CURRENT_VERSION: string | undefined;
|
||||
var PAGE_FILES: PageFiles[];
|
||||
var ROOT_FILE_UPDATED: boolean;
|
||||
var SKIPPED_BROWSER_MODULES: Set<string>;
|
||||
// var BUNDLER_CTX: BuildContext | undefined;
|
||||
}
|
||||
|
||||
@ -47,6 +48,7 @@ export default async function bunextInit() {
|
||||
global.BUNDLER_CTX_MAP = {};
|
||||
global.BUNDLER_REBUILDS = 0;
|
||||
global.PAGE_FILES = [];
|
||||
global.SKIPPED_BROWSER_MODULES = new Set<string>();
|
||||
|
||||
await init();
|
||||
log.banner();
|
||||
|
||||
@ -3,7 +3,7 @@ import path from "path";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import rebuildBundler from "./rebuild-bundler";
|
||||
import { log } from "../../utils/log";
|
||||
import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||
// import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||
|
||||
const { ROOT_DIR } = grabDirNames();
|
||||
|
||||
@ -86,7 +86,7 @@ async function fullRebuild(params?: { msg?: string }) {
|
||||
(hmr) => hmr.target_map?.local_path,
|
||||
).filter((f) => typeof f == "string");
|
||||
|
||||
await rewritePagesModule();
|
||||
// await rewritePagesModule();
|
||||
|
||||
if (msg) {
|
||||
log.watch(msg);
|
||||
|
||||
@ -79,6 +79,13 @@ export default async function genWebHTML({
|
||||
},
|
||||
});
|
||||
|
||||
// let skipped_modules_import_map: { [k: string]: string } = {};
|
||||
|
||||
// [...global.SKIPPED_BROWSER_MODULES].forEach((sk) => {
|
||||
// skipped_modules_import_map[sk] =
|
||||
// "data:text/javascript,export default {}";
|
||||
// });
|
||||
|
||||
let final_component = (
|
||||
<html {...html_props}>
|
||||
<head>
|
||||
@ -101,6 +108,16 @@ export default async function genWebHTML({
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* {global.SKIPPED_BROWSER_MODULES ? (
|
||||
<script
|
||||
type="importmap"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: importMap,
|
||||
}}
|
||||
fetchPriority="high"
|
||||
/>
|
||||
) : null} */}
|
||||
|
||||
{bundledMap?.path ? (
|
||||
<>
|
||||
<script
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import _ from "lodash";
|
||||
import type { GrabPageComponentRes } from "../../../types";
|
||||
import isDevelopment from "../../../utils/is-development";
|
||||
import { log } from "../../../utils/log";
|
||||
@ -53,9 +54,10 @@ export default async function generateWebPageResponseFromComponentReturn({
|
||||
};
|
||||
}
|
||||
|
||||
const cache_page =
|
||||
module.config?.cachePage || serverRes?.cachePage || false;
|
||||
const expiry_seconds = module.config?.cacheExpiry || serverRes?.cacheExpiry;
|
||||
const config = _.merge(root_module?.config, module.config);
|
||||
|
||||
const cache_page = config?.cachePage || serverRes?.cachePage || false;
|
||||
const expiry_seconds = config?.cacheExpiry || serverRes?.cacheExpiry;
|
||||
|
||||
if (cache_page && routeParams?.url) {
|
||||
const key = routeParams.url.pathname + (routeParams.url.search || "");
|
||||
|
||||
@ -2,6 +2,7 @@ import grabRouteParams from "../../../utils/grab-route-params";
|
||||
import type {
|
||||
BunextPageModule,
|
||||
BunextPageModuleServerReturn,
|
||||
BunextPageServerModule,
|
||||
BunextRootModule,
|
||||
BunxRouteParams,
|
||||
GrabPageComponentRes,
|
||||
@ -12,6 +13,7 @@ import _ from "lodash";
|
||||
import { log } from "../../../utils/log";
|
||||
import grabRootFilePath from "./grab-root-file-path";
|
||||
import grabPageServerRes from "./grab-page-server-res";
|
||||
import grabPageServerPath from "./grab-page-server-path";
|
||||
|
||||
class NotFoundError extends Error {}
|
||||
|
||||
@ -77,20 +79,23 @@ export default async function grabPageComponent({
|
||||
}
|
||||
|
||||
const { root_file_path } = grabRootFilePath();
|
||||
|
||||
const module: BunextPageModule = await import(`${file_path}?t=${now}`);
|
||||
const root_module: BunextRootModule | undefined = root_file_path
|
||||
? await import(`${root_file_path}?t=${now}`)
|
||||
: undefined;
|
||||
const { server_file_path: root_server_file_path } = root_file_path
|
||||
? grabPageServerPath({ file_path: root_file_path })
|
||||
: {};
|
||||
const root_server_module: BunextPageServerModule = root_server_file_path
|
||||
? await import(`${root_server_file_path}?t=${now}`)
|
||||
: undefined;
|
||||
|
||||
if (debug) {
|
||||
log.info(`module:`, module);
|
||||
}
|
||||
const root_server_fn =
|
||||
root_server_module?.default || root_server_module?.server;
|
||||
|
||||
const rootServerRes: BunextPageModuleServerReturn | undefined =
|
||||
root_module?.server
|
||||
root_server_fn
|
||||
? await grabPageServerRes({
|
||||
module: root_module,
|
||||
server_function: root_server_fn,
|
||||
url,
|
||||
query: match?.query,
|
||||
routeParams,
|
||||
@ -101,39 +106,38 @@ export default async function grabPageComponent({
|
||||
log.info(`rootServerRes:`, rootServerRes);
|
||||
}
|
||||
|
||||
const serverRes: BunextPageModuleServerReturn = await grabPageServerRes(
|
||||
{
|
||||
module,
|
||||
const module: BunextPageModule = await import(`${file_path}?t=${now}`);
|
||||
const { server_file_path } = grabPageServerPath({ file_path });
|
||||
const server_module: BunextPageServerModule = server_file_path
|
||||
? await import(`${server_file_path}?t=${now}`)
|
||||
: undefined;
|
||||
|
||||
if (debug) {
|
||||
log.info(`module:`, module);
|
||||
}
|
||||
|
||||
const server_fn = server_module?.default || server_module?.server;
|
||||
|
||||
const serverRes: BunextPageModuleServerReturn | undefined = server_fn
|
||||
? await grabPageServerRes({
|
||||
server_function: server_fn,
|
||||
url,
|
||||
query: match?.query,
|
||||
routeParams,
|
||||
},
|
||||
);
|
||||
})
|
||||
: undefined;
|
||||
|
||||
if (debug) {
|
||||
log.info(`serverRes:`, serverRes);
|
||||
}
|
||||
|
||||
const meta = module.meta
|
||||
? typeof module.meta == "function" && routeParams
|
||||
? await module.meta({
|
||||
ctx: routeParams,
|
||||
serverRes,
|
||||
})
|
||||
: typeof module.meta == "object"
|
||||
? module.meta
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
if (debug) {
|
||||
log.info(`meta:`, meta);
|
||||
}
|
||||
const mergedServerRes = _.merge(rootServerRes || {}, serverRes || {});
|
||||
|
||||
const { component } =
|
||||
(await grabPageBundledReactComponent({
|
||||
file_path,
|
||||
root_file_path,
|
||||
server_res: serverRes,
|
||||
server_res: mergedServerRes,
|
||||
})) || {};
|
||||
|
||||
if (!component) {
|
||||
@ -146,7 +150,7 @@ export default async function grabPageComponent({
|
||||
|
||||
return {
|
||||
component,
|
||||
serverRes: _.merge(rootServerRes || {}, serverRes),
|
||||
serverRes: mergedServerRes,
|
||||
routeParams,
|
||||
module,
|
||||
bundledMap,
|
||||
|
||||
@ -13,10 +13,10 @@ export default function grabPageReactComponentString({
|
||||
server_res,
|
||||
}: Params): string | undefined {
|
||||
try {
|
||||
const target_path = pagePathTransform({ page_path: file_path });
|
||||
const target_root_path = root_file_path
|
||||
? pagePathTransform({ page_path: root_file_path })
|
||||
: undefined;
|
||||
// const target_path = pagePathTransform({ page_path: file_path });
|
||||
// const target_root_path = root_file_path
|
||||
// ? pagePathTransform({ page_path: root_file_path })
|
||||
// : undefined;
|
||||
|
||||
let tsx = ``;
|
||||
|
||||
@ -24,18 +24,23 @@ export default function grabPageReactComponentString({
|
||||
EJSON.stringify(server_res || {}) ?? "{}",
|
||||
);
|
||||
|
||||
if (target_root_path) {
|
||||
tsx += `import Root from "${target_root_path}"\n`;
|
||||
// Import Root from its original source path so that all sub-components
|
||||
// that import __root (e.g. AppContext) resolve to the same module instance.
|
||||
// Using the rewritten .bunext/pages/__root would create a separate
|
||||
// createContext() call, breaking context for any sub-component that
|
||||
// imports AppContext via a relative path to the source __root.
|
||||
if (root_file_path) {
|
||||
tsx += `import Root from "${root_file_path}"\n`;
|
||||
}
|
||||
|
||||
tsx += `import Page from "${target_path}"\n`;
|
||||
tsx += `import Page from "${file_path}"\n`;
|
||||
tsx += `export default function Main() {\n\n`;
|
||||
tsx += `const props = JSON.parse(${server_res_json})\n\n`;
|
||||
tsx += ` return (\n`;
|
||||
if (target_root_path) {
|
||||
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
|
||||
if (root_file_path) {
|
||||
tsx += ` <Root {...props}><Page {...props} /></Root>\n`;
|
||||
} else {
|
||||
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
|
||||
tsx += ` <Page {...props} />\n`;
|
||||
}
|
||||
tsx += ` )\n`;
|
||||
tsx += `}\n`;
|
||||
|
||||
18
src/functions/server/web-pages/grab-page-server-path.tsx
Normal file
18
src/functions/server/web-pages/grab-page-server-path.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { existsSync } from "fs";
|
||||
|
||||
type Params = {
|
||||
file_path: string;
|
||||
};
|
||||
|
||||
export default function grabPageServerPath({ file_path }: Params) {
|
||||
const page_server_ts_file = file_path.replace(/\.tsx?$/, ".server.ts");
|
||||
const page_server_tsx_file = file_path.replace(/\.tsx?$/, ".server.tsx");
|
||||
|
||||
const server_file_path = existsSync(page_server_ts_file)
|
||||
? page_server_ts_file
|
||||
: existsSync(page_server_tsx_file)
|
||||
? page_server_tsx_file
|
||||
: undefined;
|
||||
|
||||
return { server_file_path };
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
BunextPageModule,
|
||||
BunextPageModuleServerReturn,
|
||||
BunextPageServerFn,
|
||||
BunxRouteParams,
|
||||
GrabPageComponentRes,
|
||||
} from "../../../types";
|
||||
@ -8,7 +9,7 @@ import _ from "lodash";
|
||||
|
||||
type Params = {
|
||||
url?: URL;
|
||||
module: BunextPageModule;
|
||||
server_function: BunextPageServerFn;
|
||||
query?: Record<string, string>;
|
||||
routeParams?: BunxRouteParams;
|
||||
};
|
||||
@ -17,7 +18,7 @@ export default async function grabPageServerRes({
|
||||
url,
|
||||
query,
|
||||
routeParams,
|
||||
module,
|
||||
server_function,
|
||||
}: Params): Promise<BunextPageModuleServerReturn> {
|
||||
const default_props: BunextPageModuleServerReturn = {
|
||||
url: url
|
||||
@ -43,7 +44,7 @@ export default async function grabPageServerRes({
|
||||
|
||||
try {
|
||||
if (routeParams) {
|
||||
const serverData = await module["server"]?.({
|
||||
const serverData = await server_function({
|
||||
...routeParams,
|
||||
query: { ...routeParams.query, ...query },
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import isDevelopment from "../../../utils/is-development";
|
||||
import tailwindcss from "bun-plugin-tailwind";
|
||||
import grabDirNames from "../../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
import BunSkipNonBrowserPlugin from "../../bundler/plugins/bun-skip-browser-plugin";
|
||||
|
||||
type Params = {
|
||||
tsx: string;
|
||||
@ -31,7 +32,7 @@ export default async function grabTsxStringModule<T extends any = any>({
|
||||
|
||||
await Bun.write(src_file_path, tsx);
|
||||
|
||||
await Bun.build({
|
||||
const build = await Bun.build({
|
||||
entrypoints: [src_file_path],
|
||||
format: "esm",
|
||||
target: "bun",
|
||||
@ -43,7 +44,7 @@ export default async function grabTsxStringModule<T extends any = any>({
|
||||
),
|
||||
},
|
||||
metafile: true,
|
||||
plugins: [tailwindcss],
|
||||
plugins: [tailwindcss, BunSkipNonBrowserPlugin],
|
||||
jsx: {
|
||||
runtime: "automatic",
|
||||
development: dev,
|
||||
|
||||
@ -167,13 +167,17 @@ export type BunextPageHeadFCProps = {
|
||||
|
||||
export type BunextPageModule = {
|
||||
default: FC<any>;
|
||||
server?: BunextPageServerFn;
|
||||
meta?: BunextPageModuleMeta | BunextPageModuleMetaFn;
|
||||
Head?: FC<BunextPageHeadFCProps>;
|
||||
config?: BunextRouteConfig;
|
||||
html_props?: BunextHTMLProps;
|
||||
};
|
||||
|
||||
export type BunextPageServerModule = {
|
||||
default?: BunextPageServerFn;
|
||||
server?: BunextPageServerFn;
|
||||
};
|
||||
|
||||
export type BunextHTMLProps = DetailedHTMLProps<
|
||||
HtmlHTMLAttributes<HTMLHtmlElement>,
|
||||
HTMLHtmlElement
|
||||
|
||||
@ -33,9 +33,10 @@ function grabPageDirRecursively({ page_dir }: { page_dir: string }) {
|
||||
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
const page = pages[i];
|
||||
const page_name = page.split("/").pop();
|
||||
const full_page_path = path.join(page_dir, page);
|
||||
|
||||
if (!existsSync(full_page_path)) {
|
||||
if (!existsSync(full_page_path) || !page_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -47,6 +48,10 @@ function grabPageDirRecursively({ page_dir }: { page_dir: string }) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (page_name.split(".").length > 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const page_stat = statSync(full_page_path);
|
||||
|
||||
if (page_stat.isDirectory()) {
|
||||
|
||||
@ -19,7 +19,8 @@ export const log = {
|
||||
},
|
||||
error: (msg: string | Error, log?: any) =>
|
||||
console.error(`${prefix.error} ${chalk.red(String(msg))}`, log || ""),
|
||||
warn: (msg: string) => console.warn(`${prefix.warn} ${chalk.yellow(msg)}`),
|
||||
warn: (msg: string, log?: any) =>
|
||||
console.warn(`${prefix.warn} ${chalk.yellow(msg)}`, log || ""),
|
||||
build: (msg: string) =>
|
||||
console.log(`${prefix.build} ${chalk.magenta(msg)}`),
|
||||
watch: (msg: string) => console.log(`${prefix.watch} ${chalk.blue(msg)}`),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user