Add page meta. Update page HTML gen function

This commit is contained in:
Benjamin Toby 2026-03-17 15:23:10 +01:00
parent ecc841f7ed
commit 451639b0c3
6 changed files with 182 additions and 28 deletions

View File

@ -1,10 +1,10 @@
import path from "path";
import grabContants from "../../../utils/grab-constants";
import grabDirNames from "../../../utils/grab-dir-names";
import EJSON from "../../../utils/ejson";
import type { LivePageDistGenParams } from "../../../types";
import isDevelopment from "../../../utils/is-development";
import grabWebPageHydrationScript from "./grab-web-page-hydration-script";
import grabWebMetaHTML from "./grab-web-meta-html";
export default async function genWebHTML({
component,
@ -12,6 +12,7 @@ export default async function genWebHTML({
bundledMap,
head,
module,
meta,
}: LivePageDistGenParams) {
const { ClientRootElementIDName, ClientWindowPagePropsName } =
await grabContants();
@ -23,24 +24,27 @@ export default async function genWebHTML({
const componentHTML = renderToString(component);
const headHTML = head ? renderToString(head) : "";
// const SCRIPT_SRC = path.join("/public/pages", bundledMap.path);
// const CSS_SRC = bundledMap.css_path
// ? path.join("/public/pages", bundledMap.css_path)
// : undefined;
// const { HYDRATION_DST_DIR } = grabDirNames();
let html = `<!DOCTYPE html>\n`;
html += `<html>\n`;
html += ` <head>\n`;
html += ` <meta charset="utf-8" />\n`;
html += ` <meta name="viewport" content="width=device-width, initial-scale=1.0">\n`;
if (bundledMap.css_path) {
if (meta) {
html += ` ${grabWebMetaHTML({ meta })}\n`;
}
if (bundledMap?.css_path) {
html += ` <link rel="stylesheet" href="/${bundledMap.css_path}" />\n`;
}
html += ` <script>window.${ClientWindowPagePropsName} = ${
EJSON.stringify(pageProps || {}) || "{}"
}</script>\n`;
html += ` <script src="/${bundledMap.path}" type="module" defer></script>\n`;
if (bundledMap?.path) {
html += ` <script src="/${bundledMap.path}" type="module" defer></script>\n`;
}
if (isDevelopment()) {
html += `<script defer>\n${await grabWebPageHydrationScript({ bundledMap })}\n</script>\n`;

View File

@ -2,7 +2,11 @@ import type { FC } from "react";
import grabDirNames from "../../../utils/grab-dir-names";
import grabRouteParams from "../../../utils/grab-route-params";
import grabRouter from "../../../utils/grab-router";
import type { BunextPageModule, GrabPageComponentRes } from "../../../types";
import type {
BundlerCTXMap,
BunextPageModule,
GrabPageComponentRes,
} from "../../../types";
import path from "path";
import AppNames from "../../../utils/grab-app-names";
import { existsSync } from "fs";
@ -92,6 +96,17 @@ export default async function grabPageComponent({
}
})();
const meta = module.meta
? typeof module.meta == "function" && routeParams
? await module.meta({
ctx: routeParams,
serverRes,
})
: typeof module.meta == "object"
? module.meta
: undefined
: undefined;
const Component = module.default as FC<any>;
const component = RootComponent ? (
@ -108,24 +123,51 @@ export default async function grabPageComponent({
routeParams,
module,
bundledMap,
meta,
};
} catch (error: any) {
// console.log(`Grab page component ERROR =>`, error.message);
try {
const match = router.match("/500");
const filePath = match?.filePath || BUNX_ROOT_500_PRESET_COMPONENT;
const match = router.match("/500");
const bundledMap = match?.filePath
? (global.BUNDLER_CTX_MAP?.find(
(m) => m.local_path === match.filePath,
) ?? ({} as BundlerCTXMap))
: ({} as BundlerCTXMap);
const filePath = match?.filePath || BUNX_ROOT_500_PRESET_COMPONENT;
const module: BunextPageModule = await import(filePath);
const Component = module.default as FC<any>;
const component = <Component />;
const module: BunextPageModule = await import(filePath);
return {
component,
routeParams,
module,
bundledMap,
};
} catch {
const DefaultServerError: FC = () => (
<div
style={{
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<span>500 Internal Server Error</span>
</div>
);
const Component = module.default as FC<any>;
const component = <Component />;
return {
component,
routeParams,
module,
bundledMap: {},
};
return {
component: <DefaultServerError />,
routeParams,
module: {
default: DefaultServerError,
},
};
}
}
}

View File

@ -0,0 +1,63 @@
import type { BunextPageModuleMeta } from "../../../types";
type Params = {
meta: BunextPageModuleMeta;
};
export default async function grabWebMetaHTML({ meta }: Params) {
let html = ``;
if (meta.title) {
html += ` <title>${meta.title}</title>\n`;
}
if (meta.description) {
html += ` <meta name="description" content="${meta.description}" />\n`;
}
if (meta.keywords) {
const keywords = Array.isArray(meta.keywords)
? meta.keywords.join(", ")
: meta.keywords;
html += ` <meta name="keywords" content="${keywords}" />\n`;
}
if (meta.author) {
html += ` <meta name="author" content="${meta.author}" />\n`;
}
if (meta.robots) {
html += ` <meta name="robots" content="${meta.robots}" />\n`;
}
if (meta.canonical) {
html += ` <link rel="canonical" href="${meta.canonical}" />\n`;
}
if (meta.themeColor) {
html += ` <meta name="theme-color" content="${meta.themeColor}" />\n`;
}
if (meta.og) {
const { og } = meta;
if (og.title) html += ` <meta property="og:title" content="${og.title}" />\n`;
if (og.description) html += ` <meta property="og:description" content="${og.description}" />\n`;
if (og.image) html += ` <meta property="og:image" content="${og.image}" />\n`;
if (og.url) html += ` <meta property="og:url" content="${og.url}" />\n`;
if (og.type) html += ` <meta property="og:type" content="${og.type}" />\n`;
if (og.siteName) html += ` <meta property="og:site_name" content="${og.siteName}" />\n`;
if (og.locale) html += ` <meta property="og:locale" content="${og.locale}" />\n`;
}
if (meta.twitter) {
const { twitter } = meta;
if (twitter.card) html += ` <meta name="twitter:card" content="${twitter.card}" />\n`;
if (twitter.title) html += ` <meta name="twitter:title" content="${twitter.title}" />\n`;
if (twitter.description) html += ` <meta name="twitter:description" content="${twitter.description}" />\n`;
if (twitter.image) html += ` <meta name="twitter:image" content="${twitter.image}" />\n`;
if (twitter.site) html += ` <meta name="twitter:site" content="${twitter.site}" />\n`;
if (twitter.creator) html += ` <meta name="twitter:creator" content="${twitter.creator}" />\n`;
}
return html;
}

View File

@ -5,7 +5,7 @@ import grabConstants from "../../../utils/grab-constants";
const { BUNX_HYDRATION_SRC_DIR } = grabDirNames();
type Params = {
bundledMap: BundlerCTXMap;
bundledMap?: BundlerCTXMap;
};
export default async function ({ bundledMap }: Params) {

View File

@ -8,14 +8,23 @@ type Params = {
export default async function ({ req }: Params): Promise<Response> {
try {
const { component, bundledMap, module, serverRes } =
await grabPageComponent({ req });
const {
component,
bundledMap,
module,
serverRes,
meta,
head,
routeParams,
} = await grabPageComponent({ req });
const html = await genWebHTML({
component,
pageProps: serverRes,
bundledMap,
module,
meta,
head,
});
const res_opts: ResponseInit = {

View File

@ -128,12 +128,46 @@ export type LivePageDistGenParams = {
head?: ReactNode;
pageProps?: any;
module?: BunextPageModule;
bundledMap: BundlerCTXMap;
bundledMap?: BundlerCTXMap;
meta?: BunextPageModuleMeta;
};
export type BunextPageModule = {
default: FC<any>;
server?: BunextPageServerFn;
meta?: BunextPageModuleMeta | BunextPageModuleMetaFn;
};
export type BunextPageModuleMetaFn = (params: {
ctx: BunxRouteParams;
serverRes?: BunextPageModuleServerReturn;
}) => Promise<BunextPageModuleMeta>;
export type BunextPageModuleMeta = {
title?: string;
description?: string;
keywords?: string | string[];
author?: string;
robots?: string;
canonical?: string;
themeColor?: string;
og?: {
title?: string;
description?: string;
image?: string;
url?: string;
type?: string;
siteName?: string;
locale?: string;
};
twitter?: {
card?: "summary" | "summary_large_image" | "app" | "player";
title?: string;
description?: string;
image?: string;
site?: string;
creator?: string;
};
};
export type BunextPageServerFn<
@ -155,8 +189,10 @@ export type GrabPageComponentRes = {
component: JSX.Element;
serverRes?: BunextPageModuleServerReturn;
routeParams?: BunxRouteParams;
bundledMap: BundlerCTXMap;
bundledMap?: BundlerCTXMap;
module: BunextPageModule;
meta?: BunextPageModuleMeta;
head?: ReactNode;
};
export type PageFiles = {