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