From eb0721f94be051e91b04cf5f99e625398989c5b6 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Thu, 9 Apr 2026 07:47:38 +0100 Subject: [PATCH] Updates --- dist/commands/dev/index.js | 5 +- .../bundler/all-pages-bun-bundler.js | 1 + dist/functions/bundler/all-pages-bundler.js | 1 + dist/functions/bundler/init-pages.d.ts | 7 + dist/functions/bundler/init-pages.js | 48 +++++ .../plugins/esbuild-ctx-artifact-tracker.js | 2 + dist/functions/bunext-init.d.ts | 5 + dist/functions/bunext-init.js | 15 +- dist/functions/server/server-post-build-fn.js | 15 +- dist/functions/server/watcher-esbuild-ctx.js | 6 + dist/functions/server/watcher.js | 1 + .../server/web-pages/generate-web-html.d.ts | 2 +- .../server/web-pages/generate-web-html.js | 9 +- .../grab-page-bundled-react-component.d.ts | 5 +- .../grab-page-bundled-react-component.js | 18 +- .../server/web-pages/grab-page-component.d.ts | 3 +- .../server/web-pages/grab-page-component.js | 4 +- .../web-pages/grab-page-error-component.js | 6 +- .../server/web-pages/grab-page-modules.d.ts | 7 +- .../server/web-pages/grab-page-modules.js | 20 +- .../grab-page-react-component-string.d.ts | 3 +- .../grab-page-react-component-string.js | 12 +- .../grab-tsx-string-module-deprecated.d.ts | 5 + .../grab-tsx-string-module-deprecated.js | 73 +++++++ .../web-pages/grab-tsx-string-module.d.ts | 7 +- .../web-pages/grab-tsx-string-module.js | 143 +++++++++++-- dist/types/index.d.ts | 19 +- package.json | 2 +- src/commands/dev/index.ts | 9 +- .../bundler/all-pages-bun-bundler.ts | 1 + src/functions/bundler/all-pages-bundler.ts | 1 + src/functions/bundler/init-pages.ts | 70 +++++++ .../plugins/esbuild-ctx-artifact-tracker.ts | 2 + src/functions/bunext-init.ts | 16 +- src/functions/init.ts | 1 + src/functions/server/server-post-build-fn.ts | 17 +- src/functions/server/watcher-esbuild-ctx.ts | 9 + src/functions/server/watcher.ts | 1 + .../server/web-pages/generate-web-html.tsx | 10 +- .../grab-page-bundled-react-component.tsx | 27 ++- .../server/web-pages/grab-page-component.tsx | 4 + .../web-pages/grab-page-error-component.tsx | 8 +- .../server/web-pages/grab-page-modules.tsx | 20 +- .../grab-page-react-component-string.tsx | 14 +- .../grab-tsx-string-module-deprecated.tsx | 96 +++++++++ .../web-pages/grab-tsx-string-module.tsx | 192 ++++++++++++++++-- src/types/index.ts | 20 +- 47 files changed, 826 insertions(+), 136 deletions(-) create mode 100644 dist/functions/bundler/init-pages.d.ts create mode 100644 dist/functions/bundler/init-pages.js create mode 100644 dist/functions/server/web-pages/grab-tsx-string-module-deprecated.d.ts create mode 100644 dist/functions/server/web-pages/grab-tsx-string-module-deprecated.js create mode 100644 src/functions/bundler/init-pages.ts create mode 100644 src/functions/server/web-pages/grab-tsx-string-module-deprecated.tsx diff --git a/dist/commands/dev/index.js b/dist/commands/dev/index.js index acdf15f..de878da 100644 --- a/dist/commands/dev/index.js +++ b/dist/commands/dev/index.js @@ -4,10 +4,7 @@ import { log } from "../../utils/log"; import bunextInit from "../../functions/bunext-init"; import grabDirNames from "../../utils/grab-dir-names"; import { rmSync } from "fs"; -import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; -import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; -import serverPostBuildFn from "../../functions/server/server-post-build-fn"; -const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames(); +const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR, BUNX_CWD_MODULE_CACHE_DIR, } = grabDirNames(); export default function () { return new Command("dev") .description("Run development server") diff --git a/dist/functions/bundler/all-pages-bun-bundler.js b/dist/functions/bundler/all-pages-bun-bundler.js index e5c9dd8..6e0eae4 100644 --- a/dist/functions/bundler/all-pages-bun-bundler.js +++ b/dist/functions/bundler/all-pages-bun-bundler.js @@ -102,5 +102,6 @@ export default async function allPagesBunBundler(params) { const elapsed = (performance.now() - buildStart).toFixed(0); log.success(`[Built] in ${elapsed}ms`); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; return artifacts; } diff --git a/dist/functions/bundler/all-pages-bundler.js b/dist/functions/bundler/all-pages-bundler.js index 4817e35..3884cbd 100644 --- a/dist/functions/bundler/all-pages-bundler.js +++ b/dist/functions/bundler/all-pages-bundler.js @@ -137,5 +137,6 @@ export default async function allPagesBundler(params) { const elapsed = (performance.now() - buildStart).toFixed(0); log.success(`[Built] in ${elapsed}ms`); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; build_starts = 0; } diff --git a/dist/functions/bundler/init-pages.d.ts b/dist/functions/bundler/init-pages.d.ts new file mode 100644 index 0000000..cd8502b --- /dev/null +++ b/dist/functions/bundler/init-pages.d.ts @@ -0,0 +1,7 @@ +type Params = { + log_time?: boolean; + debug?: boolean; + target_page_file?: string; +}; +export default function initPages(params?: Params): Promise; +export {}; diff --git a/dist/functions/bundler/init-pages.js b/dist/functions/bundler/init-pages.js new file mode 100644 index 0000000..d2a0c0c --- /dev/null +++ b/dist/functions/bundler/init-pages.js @@ -0,0 +1,48 @@ +import grabDirNames from "../../utils/grab-dir-names"; +import isDevelopment from "../../utils/is-development"; +import grabAllPages from "../../utils/grab-all-pages"; +import { log } from "../../utils/log"; +import grabPageBundledReactComponent from "../server/web-pages/grab-page-bundled-react-component"; +import grabTsxStringModule from "../server/web-pages/grab-tsx-string-module"; +const {} = grabDirNames(); +export default async function initPages(params) { + const buildStart = performance.now(); + const dev = isDevelopment(); + const pages = grabAllPages({ + exclude_api: true, + }); + if (params?.log_time) { + log.build(`Compiling SSR for ${pages.length} pages ...`); + } + const tsx_map = []; + try { + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + if (params?.target_page_file && + page.local_path !== params.target_page_file) { + continue; + } + const { tsx } = (await grabPageBundledReactComponent({ + file_path: page.local_path, + return_tsx_only: true, + })) || {}; + if (!tsx) { + continue; + } + tsx_map.push({ + tsx, + page_file_path: page.local_path, + }); + // const component = await grabPageComponent({ + // file_path: page.local_path, + // skip_server_res: true, + // }); + } + await grabTsxStringModule({ tsx_map }); + } + catch (error) { } + const elapsed = (performance.now() - buildStart).toFixed(0); + if (params?.log_time) { + log.success(`[SSR Compiled] in ${elapsed}ms`); + } +} diff --git a/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js b/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js index ca9b161..1a5c7c2 100644 --- a/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js +++ b/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js @@ -14,6 +14,7 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, const error_msg = `Build Failed. Please check all your components and imports.`; log.error(error_msg); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; } }); build.onEnd((result) => { @@ -49,6 +50,7 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, const elapsed = (performance.now() - buildStart).toFixed(0); log.success(`[Built] in ${elapsed}ms`); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; build_starts = 0; }); }, diff --git a/dist/functions/bunext-init.d.ts b/dist/functions/bunext-init.d.ts index 09e21d4..87dedd6 100644 --- a/dist/functions/bunext-init.d.ts +++ b/dist/functions/bunext-init.d.ts @@ -10,6 +10,7 @@ declare global { var CONFIG: BunextConfig; var SERVER: Server | undefined; var RECOMPILING: boolean; + var IS_SERVER_COMPONENT: boolean; var WATCHER_TIMEOUT: any; var ROUTER: FileSystemRouter; var HMR_CONTROLLERS: GlobalHMRControllerObject[]; @@ -29,5 +30,9 @@ declare global { imports: Record; }; var REACT_DOM_SERVER: any; + var REACT_DOM_MODULE_CACHE: Map; } export default function bunextInit(): Promise; diff --git a/dist/functions/bunext-init.js b/dist/functions/bunext-init.js index 504cf0c..282ddef 100644 --- a/dist/functions/bunext-init.js +++ b/dist/functions/bunext-init.js @@ -8,6 +8,7 @@ import watcherEsbuildCTX from "./server/watcher-esbuild-ctx"; import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler"; import serverPostBuildFn from "./server/server-post-build-fn"; import reactModulesBundler from "./bundler/react-modules-bundler"; +import initPages from "./bundler/init-pages"; const dirNames = grabDirNames(); const { PAGES_DIR } = dirNames; export default async function bunextInit() { @@ -18,10 +19,10 @@ export default async function bunextInit() { global.SKIPPED_BROWSER_MODULES = new Set(); global.DIR_NAMES = dirNames; global.REACT_IMPORTS_MAP = { imports: {} }; - await init(); - // await bunReactModulesBundler(); - await reactModulesBundler(); + global.REACT_DOM_MODULE_CACHE = new Map(); log.banner(); + await init(); + await reactModulesBundler(); const router = new Bun.FileSystemRouter({ style: "nextjs", dir: PAGES_DIR, @@ -29,13 +30,21 @@ export default async function bunextInit() { global.ROUTER = router; const is_dev = isDevelopment(); if (is_dev) { + log.build(`Building Modules ...`); await allPagesESBuildContextBundler({ post_build_fn: serverPostBuildFn, }); + initPages({ + log_time: true, + }); watcherEsbuildCTX(); } else { + log.build(`Building Modules ...`); await allPagesESBuildContextBundler(); + initPages({ + log_time: true, + }); cron(); } } diff --git a/dist/functions/server/server-post-build-fn.js b/dist/functions/server/server-post-build-fn.js index ea7e3c4..29bfb24 100644 --- a/dist/functions/server/server-post-build-fn.js +++ b/dist/functions/server/server-post-build-fn.js @@ -1,5 +1,6 @@ import _ from "lodash"; import grabPageComponent from "./web-pages/grab-page-component"; +import initPages from "../bundler/init-pages"; export default async function serverPostBuildFn() { // if (!global.IS_FIRST_BUNDLE_READY) { // global.IS_FIRST_BUNDLE_READY = true; @@ -14,10 +15,12 @@ export default async function serverPostBuildFn() { } const target_artifact = global.BUNDLER_CTX_MAP[controller.target_map.local_path]; const mock_req = new Request(controller.page_url); - const { serverRes } = await grabPageComponent({ - req: mock_req, - return_server_res_only: true, - }); + const { serverRes } = global.IS_SERVER_COMPONENT + ? await grabPageComponent({ + req: mock_req, + return_server_res_only: true, + }) + : {}; const final_artifact = { ..._.omit(controller, ["controller"]), target_map: target_artifact, @@ -42,5 +45,9 @@ export default async function serverPostBuildFn() { catch { global.HMR_CONTROLLERS.splice(i, 1); } + global.REACT_DOM_MODULE_CACHE.delete(target_artifact.local_path); + initPages({ + target_page_file: target_artifact.local_path, + }); } } diff --git a/dist/functions/server/watcher-esbuild-ctx.js b/dist/functions/server/watcher-esbuild-ctx.js index 8035ec1..3b8cd63 100644 --- a/dist/functions/server/watcher-esbuild-ctx.js +++ b/dist/functions/server/watcher-esbuild-ctx.js @@ -4,6 +4,7 @@ import grabDirNames from "../../utils/grab-dir-names"; import { log } from "../../utils/log"; import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler"; import serverPostBuildFn from "./server-post-build-fn"; +import initPages from "../bundler/init-pages"; const { ROOT_DIR } = grabDirNames(); export default async function watcherEsbuildCTX() { const pages_src_watcher = watch(ROOT_DIR, { @@ -43,6 +44,9 @@ export default async function watcherEsbuildCTX() { if (global.RECOMPILING) return; global.RECOMPILING = true; + if (filename.match(/.*\.server\.tsx?/)) { + global.IS_SERVER_COMPONENT = true; + } await global.BUNDLER_CTX?.rebuild(); if (filename.match(/(404|500)\.tsx?/)) { for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) { @@ -102,10 +106,12 @@ async function fullRebuild(params) { global.PAGES_SRC_WATCHER.close(); watcherEsbuildCTX(); } + initPages(); } function reloadWatcher() { if (global.PAGES_SRC_WATCHER) { global.PAGES_SRC_WATCHER.close(); watcherEsbuildCTX(); } + initPages(); } diff --git a/dist/functions/server/watcher.js b/dist/functions/server/watcher.js index 0af8ea3..44d1469 100644 --- a/dist/functions/server/watcher.js +++ b/dist/functions/server/watcher.js @@ -72,6 +72,7 @@ async function fullRebuild(params) { } finally { global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; } if (global.PAGES_SRC_WATCHER) { global.PAGES_SRC_WATCHER.close(); diff --git a/dist/functions/server/web-pages/generate-web-html.d.ts b/dist/functions/server/web-pages/generate-web-html.d.ts index 193b716..3eb5cc4 100644 --- a/dist/functions/server/web-pages/generate-web-html.d.ts +++ b/dist/functions/server/web-pages/generate-web-html.d.ts @@ -1,2 +1,2 @@ import type { LivePageDistGenParams } from "../../../types"; -export default function genWebHTML({ component, pageProps, bundledMap, module, routeParams, debug, root_module, }: LivePageDistGenParams): Promise; +export default function genWebHTML({ component: Main, pageProps, bundledMap, module, routeParams, debug, root_module, }: LivePageDistGenParams): Promise; diff --git a/dist/functions/server/web-pages/generate-web-html.js b/dist/functions/server/web-pages/generate-web-html.js index 7a96660..6919211 100644 --- a/dist/functions/server/web-pages/generate-web-html.js +++ b/dist/functions/server/web-pages/generate-web-html.js @@ -9,12 +9,15 @@ import { AppData } from "../../../data/app-data"; import _ from "lodash"; import grabDirNames from "../../../utils/grab-dir-names"; const { ROOT_DIR } = grabDirNames(); -export default async function genWebHTML({ component, pageProps, bundledMap, module, routeParams, debug, root_module, }) { +export default async function genWebHTML({ component: Main, pageProps, bundledMap, module, routeParams, debug, root_module, }) { const { ClientRootElementIDName, ClientWindowPagePropsName } = grabContants(); const { renderToReadableStream } = await import(`${ROOT_DIR}/node_modules/react-dom/server.js`); const is_dev = isDevelopment(); if (debug) { - log.info("component", component); + log.info("component", Main); + } + if (!Main) { + throw new Error(`Main Component not found!`); } const serializedProps = (EJSON.stringify(pageProps || {}) || "{}").replace(/<\//g, "<\\/"); const page_hydration_script = await grabWebPageHydrationScript(); @@ -46,7 +49,7 @@ export default async function genWebHTML({ component, pageProps, bundledMap, mod __html: JSON.stringify(global.REACT_IMPORTS_MAP), }, defer: true, "data-bunext-head": true }), _jsx("script", { src: `/${bundledMap.path}`, type: "module", id: AppData["BunextClientHydrationScriptID"], defer: true, "data-bunext-head": true })] })) : null, is_dev ? (_jsx("script", { defer: true, dangerouslySetInnerHTML: { __html: page_hydration_script, - }, "data-bunext-head": true })) : null] }), _jsx("body", { children: _jsx("div", { id: ClientRootElementIDName, suppressHydrationWarning: !dev, children: component }) })] })); + }, "data-bunext-head": true })) : null] }), _jsx("body", { children: _jsx("div", { id: ClientRootElementIDName, suppressHydrationWarning: !dev, children: _jsx(Main, { ...pageProps }) }) })] })); let html = `\n`; // const stream = await renderToReadableStream(final_component, { // onError(error: any) { diff --git a/dist/functions/server/web-pages/grab-page-bundled-react-component.d.ts b/dist/functions/server/web-pages/grab-page-bundled-react-component.d.ts index 9b12d2c..078f4ff 100644 --- a/dist/functions/server/web-pages/grab-page-bundled-react-component.d.ts +++ b/dist/functions/server/web-pages/grab-page-bundled-react-component.d.ts @@ -1,8 +1,7 @@ import type { GrabPageReactBundledComponentRes } from "../../../types"; type Params = { file_path: string; - root_file_path?: string; - server_res?: any; + return_tsx_only?: boolean; }; -export default function grabPageBundledReactComponent({ file_path, root_file_path, server_res, }: Params): Promise; +export default function grabPageBundledReactComponent({ file_path, return_tsx_only, }: Params): Promise; export {}; diff --git a/dist/functions/server/web-pages/grab-page-bundled-react-component.js b/dist/functions/server/web-pages/grab-page-bundled-react-component.js index 75ea4cb..a1eff06 100644 --- a/dist/functions/server/web-pages/grab-page-bundled-react-component.js +++ b/dist/functions/server/web-pages/grab-page-bundled-react-component.js @@ -1,23 +1,27 @@ -import { jsx as _jsx } from "react/jsx-runtime"; import grabPageReactComponentString from "./grab-page-react-component-string"; import grabTsxStringModule from "./grab-tsx-string-module"; import { log } from "../../../utils/log"; -export default async function grabPageBundledReactComponent({ file_path, root_file_path, server_res, }) { +import grabRootFilePath from "./grab-root-file-path"; +export default async function grabPageBundledReactComponent({ file_path, return_tsx_only, }) { try { + const { root_file_path } = grabRootFilePath(); let tsx = grabPageReactComponentString({ file_path, root_file_path, - server_res, }); if (!tsx) { return undefined; } - const mod = await grabTsxStringModule({ tsx }); + if (return_tsx_only) { + return { tsx }; + } + const mod = await grabTsxStringModule({ + tsx, + page_file_path: file_path, + }); const Main = mod.default; - const component = _jsx(Main, {}); return { - component, - server_res, + component: Main, tsx, }; } diff --git a/dist/functions/server/web-pages/grab-page-component.d.ts b/dist/functions/server/web-pages/grab-page-component.d.ts index bcc938a..fc64d6b 100644 --- a/dist/functions/server/web-pages/grab-page-component.d.ts +++ b/dist/functions/server/web-pages/grab-page-component.d.ts @@ -4,6 +4,7 @@ type Params = { file_path?: string; debug?: boolean; return_server_res_only?: boolean; + skip_server_res?: boolean; }; -export default function grabPageComponent({ req, file_path: passed_file_path, debug, return_server_res_only, }: Params): Promise; +export default function grabPageComponent({ req, file_path: passed_file_path, debug, return_server_res_only, skip_server_res, }: Params): Promise; export {}; diff --git a/dist/functions/server/web-pages/grab-page-component.js b/dist/functions/server/web-pages/grab-page-component.js index c25b88d..6eb7fb4 100644 --- a/dist/functions/server/web-pages/grab-page-component.js +++ b/dist/functions/server/web-pages/grab-page-component.js @@ -11,7 +11,7 @@ class NotFoundError extends Error { this.name = "NotFoundError"; } } -export default async function grabPageComponent({ req, file_path: passed_file_path, debug, return_server_res_only, }) { +export default async function grabPageComponent({ req, file_path: passed_file_path, debug, return_server_res_only, skip_server_res, }) { const url = req?.url ? new URL(req.url) : undefined; const router = global.ROUTER; let routeParams = undefined; @@ -63,6 +63,7 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa query: match?.query, routeParams, url, + skip_server_res, }); return { component, @@ -79,6 +80,7 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa error?.status === 404; if (!is404) { log.error(`Error Grabbing Page Component: ${error.message}`); + log.error(`Page: ${passed_file_path || url?.pathname}`); } return await grabPageErrorComponent({ error, diff --git a/dist/functions/server/web-pages/grab-page-error-component.js b/dist/functions/server/web-pages/grab-page-error-component.js index 0d9147c..3f6f7ca 100644 --- a/dist/functions/server/web-pages/grab-page-error-component.js +++ b/dist/functions/server/web-pages/grab-page-error-component.js @@ -19,7 +19,9 @@ export default async function grabPageErrorComponent({ error, routeParams, is404 if (!match?.filePath) { const default_module = await import(presetComponent); const Component = default_module.default; - const default_jsx = (_jsx(Component, { children: _jsx("span", { children: error.message }) })); + const default_jsx = () => { + return _jsx(Component, { children: _jsx("span", { children: error.message }) }); + }; return { component: default_jsx, module: default_module, @@ -54,7 +56,7 @@ export default async function grabPageErrorComponent({ error, routeParams, is404 flexDirection: "column", }, children: [_jsx("h1", { children: is404 ? "404 Not Found" : "500 Internal Server Error" }), _jsx("span", { children: error.message })] })); return { - component: _jsx(DefaultNotFound, {}), + component: DefaultNotFound, routeParams, module: { default: DefaultNotFound }, serverRes: default_server_res, diff --git a/dist/functions/server/web-pages/grab-page-modules.d.ts b/dist/functions/server/web-pages/grab-page-modules.d.ts index 393f99b..8d0427b 100644 --- a/dist/functions/server/web-pages/grab-page-modules.d.ts +++ b/dist/functions/server/web-pages/grab-page-modules.d.ts @@ -5,10 +5,11 @@ type Params = { url?: URL; query?: any; routeParams?: BunxRouteParams; + skip_server_res?: boolean; }; -export default function grabPageModules({ file_path, debug, url, query, routeParams, }: Params): Promise<{ - component: import("react").JSX.Element; - serverRes: import("../../../types").BunextPageModuleServerReturn; +export default function grabPageModules({ file_path, debug, url, query, routeParams, skip_server_res, }: Params): Promise<{ + component: import("react").FC; + serverRes: import("../../../types").BunextPageModuleServerReturn | undefined; module: BunextPageModule; root_module: BunextPageModule | undefined; }>; diff --git a/dist/functions/server/web-pages/grab-page-modules.js b/dist/functions/server/web-pages/grab-page-modules.js index b1a8335..e76d08d 100644 --- a/dist/functions/server/web-pages/grab-page-modules.js +++ b/dist/functions/server/web-pages/grab-page-modules.js @@ -3,7 +3,7 @@ import _ from "lodash"; import { log } from "../../../utils/log"; import grabRootFilePath from "./grab-root-file-path"; import grabPageCombinedServerRes from "./grab-page-combined-server-res"; -export default async function grabPageModules({ file_path, debug, url, query, routeParams, }) { +export default async function grabPageModules({ file_path, debug, url, query, routeParams, skip_server_res, }) { const now = Date.now(); const { root_file_path } = grabRootFilePath(); const root_module = root_file_path @@ -13,17 +13,17 @@ export default async function grabPageModules({ file_path, debug, url, query, ro if (debug) { log.info(`module:`, module); } - const { serverRes } = await grabPageCombinedServerRes({ - file_path, - debug, - query, - routeParams, - url, - }); + const { serverRes } = skip_server_res + ? {} + : await grabPageCombinedServerRes({ + file_path, + debug, + query, + routeParams, + url, + }); const { component } = (await grabPageBundledReactComponent({ file_path, - root_file_path, - server_res: serverRes, })) || {}; if (!component) { throw new Error(`Couldn't grab page component`); diff --git a/dist/functions/server/web-pages/grab-page-react-component-string.d.ts b/dist/functions/server/web-pages/grab-page-react-component-string.d.ts index c39cff3..4e8f43b 100644 --- a/dist/functions/server/web-pages/grab-page-react-component-string.d.ts +++ b/dist/functions/server/web-pages/grab-page-react-component-string.d.ts @@ -1,7 +1,6 @@ type Params = { file_path: string; root_file_path?: string; - server_res?: any; }; -export default function grabPageReactComponentString({ file_path, root_file_path, server_res, }: Params): string | undefined; +export default function grabPageReactComponentString({ file_path, root_file_path, }: Params): string | undefined; export {}; diff --git a/dist/functions/server/web-pages/grab-page-react-component-string.js b/dist/functions/server/web-pages/grab-page-react-component-string.js index 2f22740..9f5c09a 100644 --- a/dist/functions/server/web-pages/grab-page-react-component-string.js +++ b/dist/functions/server/web-pages/grab-page-react-component-string.js @@ -1,15 +1,19 @@ import EJSON from "../../../utils/ejson"; import { log } from "../../../utils/log"; -export default function grabPageReactComponentString({ file_path, root_file_path, server_res, }) { +export default function grabPageReactComponentString({ file_path, root_file_path, +// server_res, + }) { try { let tsx = ``; - const server_res_json = JSON.stringify(EJSON.stringify(server_res || {}) ?? "{}"); + // const server_res_json = JSON.stringify( + // EJSON.stringify(server_res || {}) ?? "{}", + // ); if (root_file_path) { tsx += `import Root from "${root_file_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 += `export default function Main({...props}) {\n\n`; + // tsx += `const props = JSON.parse(${server_res_json})\n\n`; tsx += ` return (\n`; if (root_file_path) { tsx += ` \n`; diff --git a/dist/functions/server/web-pages/grab-tsx-string-module-deprecated.d.ts b/dist/functions/server/web-pages/grab-tsx-string-module-deprecated.d.ts new file mode 100644 index 0000000..8741320 --- /dev/null +++ b/dist/functions/server/web-pages/grab-tsx-string-module-deprecated.d.ts @@ -0,0 +1,5 @@ +type Params = { + tsx: string; +}; +export default function grabTsxStringModule({ tsx, }: Params): Promise; +export {}; diff --git a/dist/functions/server/web-pages/grab-tsx-string-module-deprecated.js b/dist/functions/server/web-pages/grab-tsx-string-module-deprecated.js new file mode 100644 index 0000000..b6cda41 --- /dev/null +++ b/dist/functions/server/web-pages/grab-tsx-string-module-deprecated.js @@ -0,0 +1,73 @@ +import isDevelopment from "../../../utils/is-development"; +import * as esbuild from "esbuild"; +export default async function grabTsxStringModule({ tsx, }) { + const dev = isDevelopment(); + const now = Date.now(); + const final_tsx = dev ? tsx + `\n// v_${now}` : tsx; + const result = await esbuild.transform(final_tsx, { + loader: "tsx", + format: "esm", + jsx: "automatic", + minify: !dev, + }); + const blob = new Blob([result.code], { type: "text/javascript" }); + const url = URL.createObjectURL(blob); + const mod = await import(url); + URL.revokeObjectURL(url); + return mod; +} +// export default async function grabTsxStringModule({ +// tsx, +// }: Params): Promise { +// const dev = isDevelopment(); +// const now = Date.now(); +// const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); +// const target_cache_file_path = path.join( +// BUNX_CWD_MODULE_CACHE_DIR, +// `server-render-${now}.js`, +// ); +// await esbuild.build({ +// stdin: { +// contents: dev ? tsx + `\n// v_${now}` : tsx, +// resolveDir: process.cwd(), +// loader: "tsx", +// }, +// bundle: true, +// format: "esm", +// target: "es2020", +// platform: "node", +// external: [ +// "react", +// "react-dom", +// "react/jsx-runtime", +// "react/jsx-dev-runtime", +// ], +// minify: !dev, +// define: { +// "process.env.NODE_ENV": JSON.stringify( +// dev ? "development" : "production", +// ), +// }, +// jsx: "automatic", +// outfile: target_cache_file_path, +// plugins: [tailwindEsbuildPlugin], +// }); +// Loader.registry.delete(target_cache_file_path); +// const mod = await import(`${target_cache_file_path}?t=${now}`); +// return mod as T; +// } +// if (!dev) { +// const now = Date.now(); +// const final_tsx = dev ? tsx + `\n// v_${now}` : tsx; +// const result = await esbuild.transform(final_tsx, { +// loader: "tsx", +// format: "esm", +// jsx: "automatic", +// minify: !dev, +// }); +// const blob = new Blob([result.code], { type: "text/javascript" }); +// const url = URL.createObjectURL(blob); +// const mod = await import(url); +// URL.revokeObjectURL(url); +// return mod as T; +// } diff --git a/dist/functions/server/web-pages/grab-tsx-string-module.d.ts b/dist/functions/server/web-pages/grab-tsx-string-module.d.ts index e0e1656..5ea91b1 100644 --- a/dist/functions/server/web-pages/grab-tsx-string-module.d.ts +++ b/dist/functions/server/web-pages/grab-tsx-string-module.d.ts @@ -1,5 +1,4 @@ -type Params = { - tsx: string; -}; -export default function grabTsxStringModule({ tsx, }: Params): Promise; +import type { GrabTSXModuleBatchParams, GrabTSXModuleSingleParams } from "../../../types"; +type Params = GrabTSXModuleSingleParams | GrabTSXModuleBatchParams; +export default function grabTsxStringModule(params: Params): Promise; export {}; diff --git a/dist/functions/server/web-pages/grab-tsx-string-module.js b/dist/functions/server/web-pages/grab-tsx-string-module.js index 8da9bea..9069a54 100644 --- a/dist/functions/server/web-pages/grab-tsx-string-module.js +++ b/dist/functions/server/web-pages/grab-tsx-string-module.js @@ -3,17 +3,60 @@ import * as esbuild from "esbuild"; import grabDirNames from "../../../utils/grab-dir-names"; import path from "path"; import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin"; -export default async function grabTsxStringModule({ tsx, }) { +import { existsSync, unlinkSync } from "fs"; +import { log } from "../../../utils/log"; +const { PAGES_DIR, BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); +function toModPath(page_file_path) { + return path.join(BUNX_CWD_MODULE_CACHE_DIR, page_file_path.replace(PAGES_DIR, "").replace(/\.(t|j)sx?$/, ".js")); +} +function isBatch(params) { + return "tsx_map" in params; +} +async function buildEntries({ entries, clean_cache }) { const dev = isDevelopment(); - const now = Date.now(); - const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); - const target_cache_file_path = path.join(BUNX_CWD_MODULE_CACHE_DIR, `server-render-${now}.js`); - await esbuild.build({ - stdin: { - contents: dev ? tsx + `\n// v_${now}` : tsx, - resolveDir: process.cwd(), - loader: "tsx", + const toBuild = []; + for (const entry of entries) { + const mod_file_path = toModPath(entry.page_file_path); + if (!global.REACT_DOM_MODULE_CACHE.has(entry.page_file_path) && + !(await Bun.file(mod_file_path).exists())) { + toBuild.push({ + tsx: entry.tsx, + mod_file_path, + }); + } + else { + try { + if (clean_cache && existsSync(mod_file_path)) { + unlinkSync(mod_file_path); + } + } + catch (error) { } + } + } + if (toBuild.length === 0) + return; + const virtualEntries = {}; + for (const { tsx, mod_file_path } of toBuild) { + virtualEntries[mod_file_path] = tsx; + } + const virtualPlugin = { + name: "virtual-tsx-entries", + setup(build) { + const entryPaths = new Set(Object.keys(virtualEntries)); + build.onResolve({ filter: /.*/ }, (args) => { + if (entryPaths.has(args.path)) { + return { path: args.path, namespace: "virtual" }; + } + }); + build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => ({ + contents: virtualEntries[args.path], + resolveDir: process.cwd(), + loader: "tsx", + })); }, + }; + await esbuild.build({ + entryPoints: Object.keys(virtualEntries), bundle: true, format: "esm", target: "es2020", @@ -29,10 +72,84 @@ export default async function grabTsxStringModule({ tsx, }) { "process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"), }, jsx: "automatic", - outfile: target_cache_file_path, - plugins: [tailwindEsbuildPlugin], + outdir: BUNX_CWD_MODULE_CACHE_DIR, + plugins: [virtualPlugin, tailwindEsbuildPlugin], + }); +} +async function loadEntry(page_file_path) { + const now = Date.now(); + const mod_file_path = toModPath(page_file_path); + const mod_css_path = mod_file_path.replace(/\.js$/, ".css"); + if (global.REACT_DOM_MODULE_CACHE.has(page_file_path)) { + return global.REACT_DOM_MODULE_CACHE.get(page_file_path)?.main; + } + const mod = await import(`${mod_file_path}?t=${now}`); + global.REACT_DOM_MODULE_CACHE.set(page_file_path, { + main: mod, + css: mod_css_path, }); - Loader.registry.delete(target_cache_file_path); - const mod = await import(`${target_cache_file_path}?t=${now}`); return mod; } +export default async function grabTsxStringModule(params) { + if (isBatch(params)) { + await buildEntries({ entries: params.tsx_map }); + return Promise.all(params.tsx_map.map((entry) => loadEntry(entry.page_file_path))); + } + await buildEntries({ entries: [params], clean_cache: true }); + return loadEntry(params.page_file_path); +} +// export default async function grabTsxStringModule({ +// tsx, +// }: Params): Promise { +// const dev = isDevelopment(); +// const now = Date.now(); +// const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); +// const target_cache_file_path = path.join( +// BUNX_CWD_MODULE_CACHE_DIR, +// `server-render-${now}.js`, +// ); +// await esbuild.build({ +// stdin: { +// contents: dev ? tsx + `\n// v_${now}` : tsx, +// resolveDir: process.cwd(), +// loader: "tsx", +// }, +// bundle: true, +// format: "esm", +// target: "es2020", +// platform: "node", +// external: [ +// "react", +// "react-dom", +// "react/jsx-runtime", +// "react/jsx-dev-runtime", +// ], +// minify: !dev, +// define: { +// "process.env.NODE_ENV": JSON.stringify( +// dev ? "development" : "production", +// ), +// }, +// jsx: "automatic", +// outfile: target_cache_file_path, +// plugins: [tailwindEsbuildPlugin], +// }); +// Loader.registry.delete(target_cache_file_path); +// const mod = await import(`${target_cache_file_path}?t=${now}`); +// return mod as T; +// } +// if (!dev) { +// const now = Date.now(); +// const final_tsx = dev ? tsx + `\n// v_${now}` : tsx; +// const result = await esbuild.transform(final_tsx, { +// loader: "tsx", +// format: "esm", +// jsx: "automatic", +// minify: !dev, +// }); +// const blob = new Blob([result.code], { type: "text/javascript" }); +// const url = URL.createObjectURL(blob); +// const mod = await import(url); +// URL.revokeObjectURL(url); +// return mod as T; +// } diff --git a/dist/types/index.d.ts b/dist/types/index.d.ts index f63c287..33f3e44 100644 --- a/dist/types/index.d.ts +++ b/dist/types/index.d.ts @@ -1,5 +1,5 @@ import type { MatchedRoute, Server, WebSocketHandler } from "bun"; -import type { DetailedHTMLProps, FC, HtmlHTMLAttributes, JSX, PropsWithChildren, ReactNode } from "react"; +import type { DetailedHTMLProps, FC, HtmlHTMLAttributes, PropsWithChildren } from "react"; export type ServerProps = { params: Record; searchParams: Record; @@ -128,7 +128,7 @@ export type PageDistGenParams = { page_file: string; }; export type LivePageDistGenParams = { - component: ReactNode; + component?: FC; pageProps?: any; module?: BunextPageModule; root_module?: BunextRootModule; @@ -247,7 +247,7 @@ export type BunextPageModuleMetadata = { description?: string; }; export type GrabPageComponentRes = { - component?: JSX.Element; + component?: FC; serverRes?: BunextPageModuleServerReturn; routeParams?: BunxRouteParams; bundledMap?: BundlerCTXMap; @@ -257,7 +257,7 @@ export type GrabPageComponentRes = { }; export type BunextRootModule = BunextPageModule; export type GrabPageReactBundledComponentRes = { - component: JSX.Element; + component?: FC; server_res?: BunextPageModuleServerReturn; tsx?: string; }; @@ -288,3 +288,14 @@ export type BunextCacheFileMeta = { expiry_seconds?: number; }; export type BunextRootComponentProps = PropsWithChildren & BunextPageProps; +export type GrabTSXModuleSingleParams = { + tsx: string; + page_file_path: string; +}; +export type GrabTSXModuleBatchParams = { + tsx_map: GrabTSXModuleBatchMap[]; +}; +export type GrabTSXModuleBatchMap = { + tsx: string; + page_file_path: string; +}; diff --git a/package.json b/package.json index 109ce1c..4708d39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/bunext", - "version": "1.0.58", + "version": "1.0.60", "main": "dist/index.js", "module": "index.ts", "dependencies": { diff --git a/src/commands/dev/index.ts b/src/commands/dev/index.ts index bbd0438..9f24dc6 100644 --- a/src/commands/dev/index.ts +++ b/src/commands/dev/index.ts @@ -4,11 +4,12 @@ import { log } from "../../utils/log"; import bunextInit from "../../functions/bunext-init"; import grabDirNames from "../../utils/grab-dir-names"; import { rmSync } from "fs"; -import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; -import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; -import serverPostBuildFn from "../../functions/server/server-post-build-fn"; -const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames(); +const { + HYDRATION_DST_DIR, + BUNX_CWD_PAGES_REWRITE_DIR, + BUNX_CWD_MODULE_CACHE_DIR, +} = grabDirNames(); export default function () { return new Command("dev") diff --git a/src/functions/bundler/all-pages-bun-bundler.ts b/src/functions/bundler/all-pages-bun-bundler.ts index 39493e1..0fba1c3 100644 --- a/src/functions/bundler/all-pages-bun-bundler.ts +++ b/src/functions/bundler/all-pages-bun-bundler.ts @@ -129,6 +129,7 @@ export default async function allPagesBunBundler(params?: Params) { log.success(`[Built] in ${elapsed}ms`); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; return artifacts; } diff --git a/src/functions/bundler/all-pages-bundler.ts b/src/functions/bundler/all-pages-bundler.ts index 5217d44..518c6f8 100644 --- a/src/functions/bundler/all-pages-bundler.ts +++ b/src/functions/bundler/all-pages-bundler.ts @@ -177,6 +177,7 @@ export default async function allPagesBundler(params?: Params) { log.success(`[Built] in ${elapsed}ms`); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; build_starts = 0; } diff --git a/src/functions/bundler/init-pages.ts b/src/functions/bundler/init-pages.ts new file mode 100644 index 0000000..54477d8 --- /dev/null +++ b/src/functions/bundler/init-pages.ts @@ -0,0 +1,70 @@ +import grabDirNames from "../../utils/grab-dir-names"; +import isDevelopment from "../../utils/is-development"; +import grabAllPages from "../../utils/grab-all-pages"; +import { log } from "../../utils/log"; +import type { GrabTSXModuleBatchMap } from "../../types"; +import grabPageBundledReactComponent from "../server/web-pages/grab-page-bundled-react-component"; +import grabTsxStringModule from "../server/web-pages/grab-tsx-string-module"; + +const {} = grabDirNames(); + +type Params = { + log_time?: boolean; + debug?: boolean; + target_page_file?: string; +}; + +export default async function initPages(params?: Params) { + const buildStart = performance.now(); + + const dev = isDevelopment(); + const pages = grabAllPages({ + exclude_api: true, + }); + + if (params?.log_time) { + log.build(`Compiling SSR for ${pages.length} pages ...`); + } + + const tsx_map: GrabTSXModuleBatchMap[] = []; + + try { + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + if ( + params?.target_page_file && + page.local_path !== params.target_page_file + ) { + continue; + } + + const { tsx } = + (await grabPageBundledReactComponent({ + file_path: page.local_path, + return_tsx_only: true, + })) || {}; + + if (!tsx) { + continue; + } + + tsx_map.push({ + tsx, + page_file_path: page.local_path, + }); + + // const component = await grabPageComponent({ + // file_path: page.local_path, + // skip_server_res: true, + // }); + } + + await grabTsxStringModule({ tsx_map }); + } catch (error) {} + + const elapsed = (performance.now() - buildStart).toFixed(0); + + if (params?.log_time) { + log.success(`[SSR Compiled] in ${elapsed}ms`); + } +} diff --git a/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts b/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts index 0de7909..ff8964a 100644 --- a/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts +++ b/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts @@ -32,6 +32,7 @@ export default function esbuildCTXArtifactTracker({ const error_msg = `Build Failed. Please check all your components and imports.`; log.error(error_msg); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; } }); @@ -75,6 +76,7 @@ export default function esbuildCTXArtifactTracker({ log.success(`[Built] in ${elapsed}ms`); global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; build_starts = 0; }); diff --git a/src/functions/bunext-init.ts b/src/functions/bunext-init.ts index b96945c..3635d37 100644 --- a/src/functions/bunext-init.ts +++ b/src/functions/bunext-init.ts @@ -16,6 +16,7 @@ import watcherEsbuildCTX from "./server/watcher-esbuild-ctx"; import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler"; import serverPostBuildFn from "./server/server-post-build-fn"; import reactModulesBundler from "./bundler/react-modules-bundler"; +import initPages from "./bundler/init-pages"; /** * # Declare Global Variables @@ -24,6 +25,7 @@ declare global { var CONFIG: BunextConfig; var SERVER: Server | undefined; var RECOMPILING: boolean; + var IS_SERVER_COMPONENT: boolean; var WATCHER_TIMEOUT: any; var ROUTER: FileSystemRouter; var HMR_CONTROLLERS: GlobalHMRControllerObject[]; @@ -39,6 +41,7 @@ declare global { var DIR_NAMES: ReturnType; var REACT_IMPORTS_MAP: { imports: Record }; var REACT_DOM_SERVER: any; + var REACT_DOM_MODULE_CACHE: Map; } const dirNames = grabDirNames(); @@ -52,11 +55,12 @@ export default async function bunextInit() { global.SKIPPED_BROWSER_MODULES = new Set(); global.DIR_NAMES = dirNames; global.REACT_IMPORTS_MAP = { imports: {} }; + global.REACT_DOM_MODULE_CACHE = new Map(); + + log.banner(); await init(); - // await bunReactModulesBundler(); await reactModulesBundler(); - log.banner(); const router = new Bun.FileSystemRouter({ style: "nextjs", @@ -68,12 +72,20 @@ export default async function bunextInit() { const is_dev = isDevelopment(); if (is_dev) { + log.build(`Building Modules ...`); await allPagesESBuildContextBundler({ post_build_fn: serverPostBuildFn, }); + initPages({ + log_time: true, + }); watcherEsbuildCTX(); } else { + log.build(`Building Modules ...`); await allPagesESBuildContextBundler(); + initPages({ + log_time: true, + }); cron(); } } diff --git a/src/functions/init.ts b/src/functions/init.ts index c04c708..18808d1 100644 --- a/src/functions/init.ts +++ b/src/functions/init.ts @@ -13,6 +13,7 @@ export default async function () { recursive: true, force: true, }); + rmSync(dirNames.BUNX_CWD_MODULE_CACHE_DIR, { recursive: true, force: true, diff --git a/src/functions/server/server-post-build-fn.ts b/src/functions/server/server-post-build-fn.ts index b6996c7..64ca904 100644 --- a/src/functions/server/server-post-build-fn.ts +++ b/src/functions/server/server-post-build-fn.ts @@ -1,6 +1,7 @@ import _ from "lodash"; import type { GlobalHMRControllerObject } from "../../types"; import grabPageComponent from "./web-pages/grab-page-component"; +import initPages from "../bundler/init-pages"; export default async function serverPostBuildFn() { // if (!global.IS_FIRST_BUNDLE_READY) { @@ -23,10 +24,12 @@ export default async function serverPostBuildFn() { const mock_req = new Request(controller.page_url); - const { serverRes } = await grabPageComponent({ - req: mock_req, - return_server_res_only: true, - }); + const { serverRes } = global.IS_SERVER_COMPONENT + ? await grabPageComponent({ + req: mock_req, + return_server_res_only: true, + }) + : {}; const final_artifact: Omit = { ..._.omit(controller, ["controller"]), @@ -58,5 +61,11 @@ export default async function serverPostBuildFn() { } catch { global.HMR_CONTROLLERS.splice(i, 1); } + + global.REACT_DOM_MODULE_CACHE.delete(target_artifact.local_path); + + initPages({ + target_page_file: target_artifact.local_path, + }); } } diff --git a/src/functions/server/watcher-esbuild-ctx.ts b/src/functions/server/watcher-esbuild-ctx.ts index 81c8877..002a5bd 100644 --- a/src/functions/server/watcher-esbuild-ctx.ts +++ b/src/functions/server/watcher-esbuild-ctx.ts @@ -4,6 +4,7 @@ import grabDirNames from "../../utils/grab-dir-names"; import { log } from "../../utils/log"; import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler"; import serverPostBuildFn from "./server-post-build-fn"; +import initPages from "../bundler/init-pages"; const { ROOT_DIR } = grabDirNames(); @@ -55,6 +56,10 @@ export default async function watcherEsbuildCTX() { if (global.RECOMPILING) return; global.RECOMPILING = true; + if (filename.match(/.*\.server\.tsx?/)) { + global.IS_SERVER_COMPONENT = true; + } + await global.BUNDLER_CTX?.rebuild(); if (filename.match(/(404|500)\.tsx?/)) { @@ -133,6 +138,8 @@ async function fullRebuild(params?: { msg?: string }) { global.PAGES_SRC_WATCHER.close(); watcherEsbuildCTX(); } + + initPages(); } function reloadWatcher() { @@ -140,4 +147,6 @@ function reloadWatcher() { global.PAGES_SRC_WATCHER.close(); watcherEsbuildCTX(); } + + initPages(); } diff --git a/src/functions/server/watcher.ts b/src/functions/server/watcher.ts index f2bc8c1..e194e52 100644 --- a/src/functions/server/watcher.ts +++ b/src/functions/server/watcher.ts @@ -94,6 +94,7 @@ async function fullRebuild(params?: { msg?: string }) { log.error(error); } finally { global.RECOMPILING = false; + global.IS_SERVER_COMPONENT = false; } if (global.PAGES_SRC_WATCHER) { diff --git a/src/functions/server/web-pages/generate-web-html.tsx b/src/functions/server/web-pages/generate-web-html.tsx index 3dd4511..e77939a 100644 --- a/src/functions/server/web-pages/generate-web-html.tsx +++ b/src/functions/server/web-pages/generate-web-html.tsx @@ -12,7 +12,7 @@ import grabDirNames from "../../../utils/grab-dir-names"; const { ROOT_DIR } = grabDirNames(); export default async function genWebHTML({ - component, + component: Main, pageProps, bundledMap, module, @@ -30,7 +30,11 @@ export default async function genWebHTML({ const is_dev = isDevelopment(); if (debug) { - log.info("component", component); + log.info("component", Main); + } + + if (!Main) { + throw new Error(`Main Component not found!`); } const serializedProps = (EJSON.stringify(pageProps || {}) || "{}").replace( @@ -135,7 +139,7 @@ export default async function genWebHTML({ id={ClientRootElementIDName} suppressHydrationWarning={!dev} > - {component} +
diff --git a/src/functions/server/web-pages/grab-page-bundled-react-component.tsx b/src/functions/server/web-pages/grab-page-bundled-react-component.tsx index 325330a..2e951ca 100644 --- a/src/functions/server/web-pages/grab-page-bundled-react-component.tsx +++ b/src/functions/server/web-pages/grab-page-bundled-react-component.tsx @@ -2,36 +2,43 @@ import type { GrabPageReactBundledComponentRes } from "../../../types"; import grabPageReactComponentString from "./grab-page-react-component-string"; import grabTsxStringModule from "./grab-tsx-string-module"; import { log } from "../../../utils/log"; +import type { FC } from "react"; +import grabRootFilePath from "./grab-root-file-path"; type Params = { file_path: string; - root_file_path?: string; - server_res?: any; + return_tsx_only?: boolean; }; export default async function grabPageBundledReactComponent({ file_path, - root_file_path, - server_res, + return_tsx_only, }: Params): Promise { try { + const { root_file_path } = grabRootFilePath(); + let tsx = grabPageReactComponentString({ file_path, root_file_path, - server_res, }); if (!tsx) { return undefined; } - const mod = await grabTsxStringModule({ tsx }); - const Main = mod.default; - const component =
; + if (return_tsx_only) { + return { tsx }; + } + + const mod: any = await grabTsxStringModule({ + tsx, + page_file_path: file_path, + }); + + const Main = mod.default as FC; return { - component, - server_res, + component: Main, tsx, }; } catch (error: any) { diff --git a/src/functions/server/web-pages/grab-page-component.tsx b/src/functions/server/web-pages/grab-page-component.tsx index c1ab0e4..ef52948 100644 --- a/src/functions/server/web-pages/grab-page-component.tsx +++ b/src/functions/server/web-pages/grab-page-component.tsx @@ -20,6 +20,7 @@ type Params = { file_path?: string; debug?: boolean; return_server_res_only?: boolean; + skip_server_res?: boolean; }; export default async function grabPageComponent({ @@ -27,6 +28,7 @@ export default async function grabPageComponent({ file_path: passed_file_path, debug, return_server_res_only, + skip_server_res, }: Params): Promise { const url = req?.url ? new URL(req.url) : undefined; const router = global.ROUTER; @@ -96,6 +98,7 @@ export default async function grabPageComponent({ query: match?.query, routeParams, url, + skip_server_res, }); return { @@ -114,6 +117,7 @@ export default async function grabPageComponent({ if (!is404) { log.error(`Error Grabbing Page Component: ${error.message}`); + log.error(`Page: ${passed_file_path || url?.pathname}`); } return await grabPageErrorComponent({ diff --git a/src/functions/server/web-pages/grab-page-error-component.tsx b/src/functions/server/web-pages/grab-page-error-component.tsx index 60160e6..50559bd 100644 --- a/src/functions/server/web-pages/grab-page-error-component.tsx +++ b/src/functions/server/web-pages/grab-page-error-component.tsx @@ -45,9 +45,9 @@ export default async function grabPageErrorComponent({ presetComponent ); const Component = default_module.default as FC; - const default_jsx = ( - {{error.message}} - ); + const default_jsx: FC = () => { + return {{error.message}}; + }; return { component: default_jsx, @@ -95,7 +95,7 @@ export default async function grabPageErrorComponent({ ); return { - component: , + component: DefaultNotFound, routeParams, module: { default: DefaultNotFound }, serverRes: default_server_res, diff --git a/src/functions/server/web-pages/grab-page-modules.tsx b/src/functions/server/web-pages/grab-page-modules.tsx index 5cce599..1f536e8 100644 --- a/src/functions/server/web-pages/grab-page-modules.tsx +++ b/src/functions/server/web-pages/grab-page-modules.tsx @@ -15,6 +15,7 @@ type Params = { url?: URL; query?: any; routeParams?: BunxRouteParams; + skip_server_res?: boolean; }; export default async function grabPageModules({ @@ -23,6 +24,7 @@ export default async function grabPageModules({ url, query, routeParams, + skip_server_res, }: Params) { const now = Date.now(); @@ -37,19 +39,19 @@ export default async function grabPageModules({ log.info(`module:`, module); } - const { serverRes } = await grabPageCombinedServerRes({ - file_path, - debug, - query, - routeParams, - url, - }); + const { serverRes } = skip_server_res + ? {} + : await grabPageCombinedServerRes({ + file_path, + debug, + query, + routeParams, + url, + }); const { component } = (await grabPageBundledReactComponent({ file_path, - root_file_path, - server_res: serverRes, })) || {}; if (!component) { diff --git a/src/functions/server/web-pages/grab-page-react-component-string.tsx b/src/functions/server/web-pages/grab-page-react-component-string.tsx index 5a50691..0e407bd 100644 --- a/src/functions/server/web-pages/grab-page-react-component-string.tsx +++ b/src/functions/server/web-pages/grab-page-react-component-string.tsx @@ -4,28 +4,28 @@ import { log } from "../../../utils/log"; type Params = { file_path: string; root_file_path?: string; - server_res?: any; + // server_res?: any; }; export default function grabPageReactComponentString({ file_path, root_file_path, - server_res, + // server_res, }: Params): string | undefined { try { let tsx = ``; - const server_res_json = JSON.stringify( - EJSON.stringify(server_res || {}) ?? "{}", - ); + // const server_res_json = JSON.stringify( + // EJSON.stringify(server_res || {}) ?? "{}", + // ); if (root_file_path) { tsx += `import Root from "${root_file_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 += `export default function Main({...props}) {\n\n`; + // tsx += `const props = JSON.parse(${server_res_json})\n\n`; tsx += ` return (\n`; if (root_file_path) { tsx += ` \n`; diff --git a/src/functions/server/web-pages/grab-tsx-string-module-deprecated.tsx b/src/functions/server/web-pages/grab-tsx-string-module-deprecated.tsx new file mode 100644 index 0000000..9f8a865 --- /dev/null +++ b/src/functions/server/web-pages/grab-tsx-string-module-deprecated.tsx @@ -0,0 +1,96 @@ +import isDevelopment from "../../../utils/is-development"; +import * as esbuild from "esbuild"; + +type Params = { + tsx: string; +}; + +export default async function grabTsxStringModule({ + tsx, +}: Params): Promise { + const dev = isDevelopment(); + const now = Date.now(); + + const final_tsx = dev ? tsx + `\n// v_${now}` : tsx; + + const result = await esbuild.transform(final_tsx, { + loader: "tsx", + format: "esm", + jsx: "automatic", + minify: !dev, + }); + + const blob = new Blob([result.code], { type: "text/javascript" }); + const url = URL.createObjectURL(blob); + const mod = await import(url); + + URL.revokeObjectURL(url); + + return mod as T; +} + +// export default async function grabTsxStringModule({ +// tsx, +// }: Params): Promise { +// const dev = isDevelopment(); + +// const now = Date.now(); +// const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); +// const target_cache_file_path = path.join( +// BUNX_CWD_MODULE_CACHE_DIR, +// `server-render-${now}.js`, +// ); + +// await esbuild.build({ +// stdin: { +// contents: dev ? tsx + `\n// v_${now}` : tsx, +// resolveDir: process.cwd(), +// loader: "tsx", +// }, +// bundle: true, +// format: "esm", +// target: "es2020", +// platform: "node", +// external: [ +// "react", +// "react-dom", +// "react/jsx-runtime", +// "react/jsx-dev-runtime", +// ], +// minify: !dev, +// define: { +// "process.env.NODE_ENV": JSON.stringify( +// dev ? "development" : "production", +// ), +// }, +// jsx: "automatic", +// outfile: target_cache_file_path, +// plugins: [tailwindEsbuildPlugin], +// }); + +// Loader.registry.delete(target_cache_file_path); +// const mod = await import(`${target_cache_file_path}?t=${now}`); + +// return mod as T; +// } + +// if (!dev) { +// const now = Date.now(); + +// const final_tsx = dev ? tsx + `\n// v_${now}` : tsx; + +// const result = await esbuild.transform(final_tsx, { +// loader: "tsx", +// format: "esm", +// jsx: "automatic", +// minify: !dev, +// }); + +// const blob = new Blob([result.code], { type: "text/javascript" }); +// const url = URL.createObjectURL(blob); +// const mod = await import(url); + +// URL.revokeObjectURL(url); + +// return mod as T; +// } diff --git a/src/functions/server/web-pages/grab-tsx-string-module.tsx b/src/functions/server/web-pages/grab-tsx-string-module.tsx index 81bbc2c..c6b50fd 100644 --- a/src/functions/server/web-pages/grab-tsx-string-module.tsx +++ b/src/functions/server/web-pages/grab-tsx-string-module.tsx @@ -3,28 +3,86 @@ import * as esbuild from "esbuild"; import grabDirNames from "../../../utils/grab-dir-names"; import path from "path"; import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin"; +import type { + GrabTSXModuleBatchParams, + GrabTSXModuleSingleParams, +} from "../../../types"; +import { existsSync, unlinkSync } from "fs"; +import { log } from "../../../utils/log"; -type Params = { - tsx: string; +const { PAGES_DIR, BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); + +type Params = GrabTSXModuleSingleParams | GrabTSXModuleBatchParams; + +function toModPath(page_file_path: string) { + return path.join( + BUNX_CWD_MODULE_CACHE_DIR, + page_file_path.replace(PAGES_DIR, "").replace(/\.(t|j)sx?$/, ".js"), + ); +} + +function isBatch(params: Params): params is GrabTSXModuleBatchParams { + return "tsx_map" in params; +} + +type BuildEntriesParams = { + entries: { tsx: string; page_file_path: string }[]; + clean_cache?: boolean; }; -export default async function grabTsxStringModule({ - tsx, -}: Params): Promise { +async function buildEntries({ entries, clean_cache }: BuildEntriesParams) { const dev = isDevelopment(); - const now = Date.now(); - const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); - const target_cache_file_path = path.join( - BUNX_CWD_MODULE_CACHE_DIR, - `server-render-${now}.js`, - ); + + const toBuild: { tsx: string; mod_file_path: string }[] = []; + + for (const entry of entries) { + const mod_file_path = toModPath(entry.page_file_path); + + if ( + !global.REACT_DOM_MODULE_CACHE.has(entry.page_file_path) && + !(await Bun.file(mod_file_path).exists()) + ) { + toBuild.push({ + tsx: entry.tsx, + mod_file_path, + }); + } else { + try { + if (clean_cache && existsSync(mod_file_path)) { + unlinkSync(mod_file_path); + } + } catch (error) {} + } + } + + if (toBuild.length === 0) return; + + const virtualEntries: Record = {}; + for (const { tsx, mod_file_path } of toBuild) { + virtualEntries[mod_file_path] = tsx; + } + + const virtualPlugin: esbuild.Plugin = { + name: "virtual-tsx-entries", + setup(build) { + const entryPaths = new Set(Object.keys(virtualEntries)); + + build.onResolve({ filter: /.*/ }, (args) => { + if (entryPaths.has(args.path)) { + return { path: args.path, namespace: "virtual" }; + } + }); + + build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => ({ + contents: virtualEntries[args.path], + resolveDir: process.cwd(), + loader: "tsx", + })); + }, + }; await esbuild.build({ - stdin: { - contents: dev ? tsx + `\n// v_${now}` : tsx, - resolveDir: process.cwd(), - loader: "tsx", - }, + entryPoints: Object.keys(virtualEntries), bundle: true, format: "esm", target: "es2020", @@ -42,12 +100,106 @@ export default async function grabTsxStringModule({ ), }, jsx: "automatic", - outfile: target_cache_file_path, - plugins: [tailwindEsbuildPlugin], + outdir: BUNX_CWD_MODULE_CACHE_DIR, + plugins: [virtualPlugin, tailwindEsbuildPlugin], }); +} - Loader.registry.delete(target_cache_file_path); - const mod = await import(`${target_cache_file_path}?t=${now}`); +async function loadEntry(page_file_path: string): Promise { + const now = Date.now(); + const mod_file_path = toModPath(page_file_path); + const mod_css_path = mod_file_path.replace(/\.js$/, ".css"); + + if (global.REACT_DOM_MODULE_CACHE.has(page_file_path)) { + return global.REACT_DOM_MODULE_CACHE.get(page_file_path)?.main as T; + } + + const mod = await import(`${mod_file_path}?t=${now}`); + + global.REACT_DOM_MODULE_CACHE.set(page_file_path, { + main: mod, + css: mod_css_path, + }); return mod as T; } + +export default async function grabTsxStringModule( + params: Params, +): Promise { + if (isBatch(params)) { + await buildEntries({ entries: params.tsx_map }); + return Promise.all( + params.tsx_map.map((entry) => loadEntry(entry.page_file_path)), + ); + } + + await buildEntries({ entries: [params], clean_cache: true }); + return loadEntry(params.page_file_path); +} + +// export default async function grabTsxStringModule({ +// tsx, +// }: Params): Promise { +// const dev = isDevelopment(); + +// const now = Date.now(); +// const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); +// const target_cache_file_path = path.join( +// BUNX_CWD_MODULE_CACHE_DIR, +// `server-render-${now}.js`, +// ); + +// await esbuild.build({ +// stdin: { +// contents: dev ? tsx + `\n// v_${now}` : tsx, +// resolveDir: process.cwd(), +// loader: "tsx", +// }, +// bundle: true, +// format: "esm", +// target: "es2020", +// platform: "node", +// external: [ +// "react", +// "react-dom", +// "react/jsx-runtime", +// "react/jsx-dev-runtime", +// ], +// minify: !dev, +// define: { +// "process.env.NODE_ENV": JSON.stringify( +// dev ? "development" : "production", +// ), +// }, +// jsx: "automatic", +// outfile: target_cache_file_path, +// plugins: [tailwindEsbuildPlugin], +// }); + +// Loader.registry.delete(target_cache_file_path); +// const mod = await import(`${target_cache_file_path}?t=${now}`); + +// return mod as T; +// } + +// if (!dev) { +// const now = Date.now(); + +// const final_tsx = dev ? tsx + `\n// v_${now}` : tsx; + +// const result = await esbuild.transform(final_tsx, { +// loader: "tsx", +// format: "esm", +// jsx: "automatic", +// minify: !dev, +// }); + +// const blob = new Blob([result.code], { type: "text/javascript" }); +// const url = URL.createObjectURL(blob); +// const mod = await import(url); + +// URL.revokeObjectURL(url); + +// return mod as T; +// } diff --git a/src/types/index.ts b/src/types/index.ts index 7cbe26b..2da4b9d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -151,7 +151,7 @@ export type PageDistGenParams = { }; export type LivePageDistGenParams = { - component: ReactNode; + component?: FC; pageProps?: any; module?: BunextPageModule; root_module?: BunextRootModule; @@ -282,7 +282,7 @@ export type BunextPageModuleMetadata = { }; export type GrabPageComponentRes = { - component?: JSX.Element; + component?: FC; serverRes?: BunextPageModuleServerReturn; routeParams?: BunxRouteParams; bundledMap?: BundlerCTXMap; @@ -294,7 +294,7 @@ export type GrabPageComponentRes = { export type BunextRootModule = BunextPageModule; export type GrabPageReactBundledComponentRes = { - component: JSX.Element; + component?: FC; server_res?: BunextPageModuleServerReturn; tsx?: string; }; @@ -330,3 +330,17 @@ export type BunextCacheFileMeta = { }; export type BunextRootComponentProps = PropsWithChildren & BunextPageProps; + +export type GrabTSXModuleSingleParams = { + tsx: string; + page_file_path: string; +}; + +export type GrabTSXModuleBatchParams = { + tsx_map: GrabTSXModuleBatchMap[]; +}; + +export type GrabTSXModuleBatchMap = { + tsx: string; + page_file_path: string; +};