Compare commits

..

No commits in common. "321c8ebb89052dfa9e7ea3e1b6b6e00edb9cfe0c" and "99b319f5af18428880a8e8ee04d89c5229ffc29c" have entirely different histories.

38 changed files with 354 additions and 836 deletions

View File

@ -1,6 +1,6 @@
# Bunext
A server-rendering framework for React, built entirely on [Bun](https://bun.sh). Bunext handles file-system routing, SSR, HMR, and client hydration — using ESBuild to bundle client assets and `Bun.serve` as the HTTP server.
A server-rendering framework for React, built entirely on [Bun](https://bun.sh). Bunext handles file-system routing, SSR, HMR, and client hydration — using `Bun.build` to bundle client assets and `Bun.serve` as the HTTP server.
## Philosophy
@ -8,7 +8,7 @@ Bunext is focused on **server-side rendering and processing**. Every page is ren
The goal is a framework that is:
- Fast — Bun's runtime speed and ESBuild's bundling make the full dev loop snappy
- Fast — Bun's runtime speed and Bun.build's bundling make the full dev loop snappy
- Transparent — the entire request pipeline is readable and debugable
- Standard — server functions and API handlers use native Web APIs (`Request`, `Response`, `URL`) with no custom wrappers
@ -924,7 +924,7 @@ Running `bunext dev`:
1. Loads `bunext.config.ts` and sets `development: true`.
2. Initializes directories (`.bunext/`, `public/pages/`).
3. Creates a `Bun.FileSystemRouter` pointed at `src/pages/`.
4. Creates an ESBuild context and performs the initial build. File-change rebuilds are triggered manually by the FS watcher.
4. Starts `Bun.build` in **watch mode** — it will automatically rebuild when file content changes.
5. Starts a file-system watcher on `src/` — when a file is created or deleted (a "rename" event), it triggers a full bundler rebuild to update the entry points.
6. Waits for the first successful bundle.
7. Starts `Bun.serve()`.
@ -934,7 +934,7 @@ Running `bunext dev`:
Running `bunext build`:
1. Sets `NODE_ENV=production`.
2. Runs ESBuild once with minification enabled.
2. Runs `Bun.build` once with minification enabled.
3. Writes all bundled artifacts to `.bunext/public/pages/` and the artifact map to `.bunext/public/pages/map.json`.
4. Exits.
@ -947,13 +947,11 @@ Running `bunext start`:
### Bundler
The bundler uses **ESBuild** with a virtual namespace plugin that generates in-memory hydration entry points for each page — no temporary files are written to disk. Each virtual entry imports the page component and calls `hydrateRoot()` against the server-rendered DOM node. If `src/pages/__root.tsx` exists, the page is wrapped in the root layout. Tailwind CSS is processed via a dedicated ESBuild plugin.
The bundler uses `Bun.build` with the `bun-plugin-tailwind` plugin. For each page, a client hydration entry point is generated and written as a real temporary file under `.bunext/hydration-src/`. Each entry imports the page component and calls `hydrateRoot()` against the server-rendered DOM node. If `src/pages/__root.tsx` exists, the page is wrapped in the root layout.
In development, an `esbuild.context()` is created once and rebuilt incrementally whenever the FS watcher detects a file change. In production, a single `esbuild.build()` call runs with minification enabled.
React is loaded externally — `react`, `react-dom`, `react-dom/client`, and `react/jsx-runtime` are all marked as external in the `Bun.build` config. The correct React version is resolved from the framework's own `node_modules` at startup and injected into every HTML page via a `<script type="importmap">` pointing at `esm.sh`. This guarantees a single shared React instance across all page bundles and HMR updates regardless of project size.
React is loaded externally — `react`, `react-dom`, `react-dom/client`, and `react/jsx-runtime` are all marked as external in the ESBuild config. The correct React version is resolved from the framework's own `node_modules` at startup and injected into every HTML page via a `<script type="importmap">` pointing at `esm.sh`. This guarantees a single shared React instance across all page bundles and HMR updates regardless of project size.
After each build, ESBuild's metafile is used to map each output file back to its source page, producing a `BundlerCTXMap[]`. This map is stored in `global.BUNDLER_CTX_MAP` and written to `.bunext/public/pages/map.json`.
After each build, output metadata from `Bun.build`'s metafile is used to map each output file back to its source page, producing a `BundlerCTXMap[]`. This map is stored in `global.BUNDLER_CTX_MAP` and written to `.bunext/public/pages/map.json`.
Output files are named `[hash].[ext]` so filenames change when content changes, enabling cache-busting.

View File

@ -64,6 +64,7 @@ export default async function allPagesBundler(params) {
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
process.exit(1);
}
});
// build.onEnd((result) => {

View File

@ -1,7 +0,0 @@
type Params = {
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
};
export default function allPagesESBuildContextBundlerFiles(params?: Params): Promise<void>;
export {};

View File

@ -1,58 +0,0 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabClientHydrationScript from "./grab-client-hydration-script";
import path from "path";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
export default async function allPagesESBuildContextBundlerFiles(params) {
const pages = grabAllPages({ exclude_api: true });
global.PAGE_FILES = pages;
const dev = isDevelopment();
const entryToPage = new Map();
for (const page of pages) {
const tsx = await grabClientHydrationScript({
page_local_path: page.local_path,
});
if (!tsx)
continue;
const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`);
await Bun.write(entryFile, tsx, { createPath: true });
entryToPage.set(entryFile, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()];
const ctx = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
splitting: true,
logLevel: "silent",
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
],
});
await ctx.rebuild();
global.BUNDLER_CTX = ctx;
}

View File

@ -2,30 +2,81 @@ import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import { log } from "../../utils/log";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabClientHydrationScript from "./grab-client-hydration-script";
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
import { writeFileSync } from "fs";
import path from "path";
import virtualFilesPlugin from "./plugins/virtual-files-plugin";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE, BUNX_HYDRATION_SRC_DIR, } = grabDirNames();
let build_starts = 0;
const MAX_BUILD_STARTS = 10;
export default async function allPagesESBuildContextBundler(params) {
// return await allPagesESBuildContextBundlerFiles(params);
const pages = grabAllPages({ exclude_api: true });
global.PAGE_FILES = pages;
const dev = isDevelopment();
const entryToPage = new Map();
for (const page of pages) {
const tsx = await grabClientHydrationScript({
const txt = await grabClientHydrationScript({
page_local_path: page.local_path,
});
if (!tsx)
if (!txt)
continue;
const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`);
// await Bun.write(entryFile, txt, { createPath: true });
entryToPage.set(entryFile, { ...page, tsx });
await Bun.write(entryFile, txt, { createPath: true });
entryToPage.set(path.resolve(entryFile), page);
}
const entryPoints = [...entryToPage.keys()].map((e) => `hydration-virtual:${e}`);
global.BUNDLER_CTX = await esbuild.context({
let buildStart = 0;
const artifactTracker = {
name: "artifact-tracker",
setup(build) {
build.onStart(() => {
build_starts++;
buildStart = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
process.exit(1);
}
});
build.onEnd((result) => {
if (result.errors.length > 0) {
for (const error of result.errors) {
const loc = error.location;
const location = loc
? ` ${loc.file}:${loc.line}:${loc.column}`
: "";
log.error(`[Build]${location} ${error.text}`);
}
return;
}
const artifacts = grabArtifactsFromBundledResults({
result,
entryToPage,
});
if (artifacts?.[0] && artifacts.length > 0) {
for (let i = 0; i < artifacts.length; i++) {
const artifact = artifacts[i];
if (artifact?.local_path && global.BUNDLER_CTX_MAP) {
global.BUNDLER_CTX_MAP[artifact.local_path] =
artifact;
}
}
params?.post_build_fn?.({ artifacts });
// writeFileSync(
// HYDRATION_DST_DIR_MAP_JSON_FILE,
// JSON.stringify(artifacts, null, 4),
// );
}
const elapsed = (performance.now() - buildStart).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
build_starts = 0;
});
},
};
const entryPoints = [...entryToPage.keys()];
const ctx = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
@ -38,21 +89,10 @@ export default async function allPagesESBuildContextBundler(params) {
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
virtualFilesPlugin({
entryToPage,
}),
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
plugins: [tailwindEsbuildPlugin, artifactTracker],
jsx: "automatic",
splitting: true,
logLevel: "silent",
// logLevel: "silent",
// logLevel: dev ? "error" : "silent",
external: [
"react",
"react-dom",
@ -60,5 +100,9 @@ export default async function allPagesESBuildContextBundler(params) {
"react/jsx-runtime",
],
});
await global.BUNDLER_CTX.rebuild();
await ctx.rebuild();
// if (params?.watch) {
// await ctx.watch();
// }
global.BUNDLER_CTX = ctx;
}

View File

@ -2,9 +2,7 @@ import * as esbuild from "esbuild";
import type { BundlerCTXMap, PageFiles } from "../../types";
type Params = {
result: esbuild.BuildResult<esbuild.BuildOptions>;
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
entryToPage: Map<string, PageFiles>;
};
export default function grabArtifactsFromBundledResults({ result, entryToPage, }: Params): BundlerCTXMap[] | undefined;
export {};

View File

@ -1,7 +1,6 @@
import path from "path";
import * as esbuild from "esbuild";
import grabDirNames from "../../utils/grab-dir-names";
import { log } from "../../utils/log";
const { ROOT_DIR } = grabDirNames();
export default function grabArtifactsFromBundledResults({ result, entryToPage, }) {
if (result.errors.length > 0)
@ -9,29 +8,24 @@ export default function grabArtifactsFromBundledResults({ result, entryToPage, }
const artifacts = Object.entries(result.metafile.outputs)
.filter(([, meta]) => meta.entryPoint)
.map(([outputPath, meta]) => {
const entrypoint = meta.entryPoint?.match(/^hydration-virtual:/)
? meta.entryPoint?.replace(/^hydration-virtual:/, "")
: meta.entryPoint
? path.join(ROOT_DIR, meta.entryPoint)
: "";
// const entrypoint = path.join(ROOT_DIR, meta.entryPoint || "");
// console.log("entrypoint", entrypoint);
const entrypoint = path.join(ROOT_DIR, meta.entryPoint || "");
const target_page = entryToPage.get(entrypoint);
if (!target_page || !meta.entryPoint) {
return undefined;
}
const { file_name, local_path, url_path } = target_page;
const { file_name, local_path, url_path, transformed_path } = target_page;
return {
path: outputPath,
hash: path.basename(outputPath, path.extname(outputPath)),
type: outputPath.endsWith(".css")
? "text/css"
: "text/javascript",
entrypoint: meta.entryPoint,
entrypoint,
css_path: meta.cssBundle,
file_name,
local_path,
url_path,
transformed_path,
};
});
if (artifacts.length > 0) {

View File

@ -1,5 +1,5 @@
type Params = {
page_local_path: string;
};
export default function grabClientHydrationScript({ page_local_path, }: Params): Promise<string | undefined>;
export default function grabClientHydrationScript({ page_local_path, }: Params): Promise<string>;
export {};

View File

@ -13,22 +13,6 @@ export default async function grabClientHydrationScript({ page_local_path, }) {
// const target_root_path = root_file_path
// ? pagePathTransform({ page_path: root_file_path })
// : undefined;
if (!existsSync(page_local_path)) {
return undefined;
}
if (root_file_path) {
if (!existsSync(root_file_path)) {
return undefined;
}
const root_content = await Bun.file(root_file_path).text();
if (!root_content.match(/^export default/m)) {
return undefined;
}
}
const page_content = await Bun.file(page_local_path).text();
if (!page_content.match(/^export default/m)) {
return undefined;
}
let txt = ``;
txt += `import { hydrateRoot } from "react-dom/client";\n`;
if (root_file_path) {

View File

@ -1,12 +0,0 @@
import type { Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
type Params = {
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
};
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, }: Params): Plugin;
export {};

View File

@ -1,57 +0,0 @@
import { log } from "../../../utils/log";
import grabArtifactsFromBundledResults from "../grab-artifacts-from-bundled-result";
let buildStart = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 10;
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, }) {
const artifactTracker = {
name: "artifact-tracker",
setup(build) {
build.onStart(() => {
build_starts++;
buildStart = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
global.RECOMPILING = false;
}
});
build.onEnd((result) => {
if (result.errors.length > 0) {
// for (const error of result.errors) {
// const loc = error.location;
// const location = loc
// ? ` ${loc.file}:${loc.line}:${loc.column}`
// : "";
// log.error(`[Build]${location} ${error.text}`);
// }
return;
}
const artifacts = grabArtifactsFromBundledResults({
result,
entryToPage,
});
// console.log("artifacts", artifacts);
if (artifacts?.[0] && artifacts.length > 0) {
for (let i = 0; i < artifacts.length; i++) {
const artifact = artifacts[i];
if (artifact?.local_path && global.BUNDLER_CTX_MAP) {
global.BUNDLER_CTX_MAP[artifact.local_path] =
artifact;
}
}
post_build_fn?.({ artifacts });
// writeFileSync(
// HYDRATION_DST_DIR_MAP_JSON_FILE,
// JSON.stringify(artifacts, null, 4),
// );
}
const elapsed = (performance.now() - buildStart).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
build_starts = 0;
});
},
};
return artifactTracker;
}

View File

@ -1,9 +0,0 @@
import type { Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
type Params = {
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
};
export default function virtualFilesPlugin({ entryToPage }: Params): Plugin;
export {};

View File

@ -1,28 +0,0 @@
import path from "path";
import { log } from "../../../utils/log";
export default function virtualFilesPlugin({ entryToPage }) {
const virtualPlugin = {
name: "virtual-hydration",
setup(build) {
build.onResolve({ filter: /^hydration-virtual:/ }, (args) => {
const final_path = args.path.replace(/hydration-virtual:/, "");
return {
path: final_path,
namespace: "hydration-virtual",
};
});
build.onLoad({ filter: /.*/, namespace: "hydration-virtual" }, (args) => {
const target = entryToPage.get(args.path);
if (!target?.tsx)
return null;
const contents = target.tsx;
return {
contents: contents || "",
loader: "tsx",
resolveDir: path.dirname(target.local_path),
};
});
},
};
return virtualPlugin;
}

View File

@ -9,7 +9,7 @@ export default async function serverPostBuildFn() {
}
for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.HMR_CONTROLLERS[i];
if (!controller?.target_map?.local_path) {
if (!controller.target_map?.local_path) {
continue;
}
const target_artifact = global.BUNDLER_CTX_MAP[controller.target_map.local_path];

View File

@ -13,9 +13,6 @@ export default async function watcherEsbuildCTX() {
}, async (event, filename) => {
if (!filename)
return;
if (filename.match(/^\.\w+/)) {
return;
}
const full_file_path = path.join(ROOT_DIR, filename);
if (full_file_path.match(/\/styles$/)) {
global.RECOMPILING = true;
@ -41,12 +38,6 @@ export default async function watcherEsbuildCTX() {
return;
global.RECOMPILING = true;
await global.BUNDLER_CTX?.rebuild();
if (filename.match(/(404|500)\.tsx?/)) {
for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.HMR_CONTROLLERS[i];
controller?.controller?.enqueue(`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`);
}
}
}
return;
}
@ -78,14 +69,16 @@ async function fullRebuild(params) {
global.ROUTER.reload();
await global.BUNDLER_CTX?.dispose();
global.BUNDLER_CTX = undefined;
global.BUNDLER_CTX_MAP = {};
allPagesESBuildContextBundler({
await allPagesESBuildContextBundler({
post_build_fn: serverPostBuildFn,
});
}
catch (error) {
log.error(error);
}
finally {
global.RECOMPILING = false;
}
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
watcherEsbuildCTX();

View File

@ -6,12 +6,12 @@ import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import grabPageServerRes from "./grab-page-server-res";
import grabPageServerPath from "./grab-page-server-path";
import grabPageModules from "./grab-page-modules";
class NotFoundError extends Error {
}
export default async function grabPageComponent({ req, file_path: passed_file_path, debug, }) {
const url = req?.url ? new URL(req.url) : undefined;
const router = global.ROUTER;
const now = Date.now();
let routeParams = undefined;
try {
routeParams = req ? await grabRouteParams({ req }) : undefined;
@ -45,16 +45,63 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
if (debug) {
log.info(`bundledMap:`, bundledMap);
}
const { component, module, serverRes, root_module } = await grabPageModules({
file_path,
debug,
const { root_file_path } = grabRootFilePath();
const root_module = root_file_path
? await import(`${root_file_path}?t=${now}`)
: undefined;
const { server_file_path: root_server_file_path } = root_file_path
? grabPageServerPath({ file_path: root_file_path })
: {};
const root_server_module = root_server_file_path
? await import(`${root_server_file_path}?t=${now}`)
: undefined;
const root_server_fn = root_server_module?.default || root_server_module?.server;
const rootServerRes = root_server_fn
? await grabPageServerRes({
server_function: root_server_fn,
url,
query: match?.query,
routeParams,
})
: undefined;
if (debug) {
log.info(`rootServerRes:`, rootServerRes);
}
const module = await import(`${file_path}?t=${now}`);
const { server_file_path } = grabPageServerPath({ file_path });
const server_module = server_file_path
? await import(`${server_file_path}?t=${now}`)
: undefined;
if (debug) {
log.info(`module:`, module);
}
const server_fn = server_module?.default || server_module?.server;
const serverRes = server_fn
? await grabPageServerRes({
server_function: server_fn,
url,
});
query: match?.query,
routeParams,
})
: undefined;
if (debug) {
log.info(`serverRes:`, serverRes);
}
const mergedServerRes = _.merge(rootServerRes || {}, serverRes || {});
const { component } = (await grabPageBundledReactComponent({
file_path,
root_file_path,
server_res: mergedServerRes,
})) || {};
if (!component) {
throw new Error(`Couldn't grab page component`);
}
if (debug) {
log.info(`component:`, component);
}
return {
component,
serverRes,
serverRes: mergedServerRes,
routeParams,
module,
bundledMap,
@ -67,7 +114,6 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
error,
routeParams,
is404: error instanceof NotFoundError,
url,
});
}
}

View File

@ -3,7 +3,6 @@ type Params = {
error?: any;
routeParams?: BunxRouteParams;
is404?: boolean;
url?: URL;
};
export default function grabPageErrorComponent({ error, routeParams, is404, url, }: Params): Promise<GrabPageComponentRes>;
export default function grabPageErrorComponent({ error, routeParams, is404, }: Params): Promise<GrabPageComponentRes>;
export {};

View File

@ -1,47 +1,31 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import grabDirNames from "../../../utils/grab-dir-names";
import grabPageModules from "./grab-page-modules";
import _ from "lodash";
export default async function grabPageErrorComponent({ error, routeParams, is404, url, }) {
export default async function grabPageErrorComponent({ error, routeParams, is404, }) {
const router = global.ROUTER;
const { BUNX_ROOT_500_PRESET_COMPONENT, BUNX_ROOT_404_PRESET_COMPONENT } = grabDirNames();
const errorRoute = is404 ? "/404" : "/500";
const presetComponent = is404
? BUNX_ROOT_404_PRESET_COMPONENT
: BUNX_ROOT_500_PRESET_COMPONENT;
const default_server_res = {
responseOptions: {
status: is404 ? 404 : 500,
},
};
try {
const match = router.match(errorRoute);
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 }) }));
return {
component: default_jsx,
module: default_module,
routeParams,
serverRes: default_server_res,
};
}
const file_path = match.filePath;
const bundledMap = global.BUNDLER_CTX_MAP?.[file_path];
const { component, module, serverRes, root_module } = await grabPageModules({
file_path: file_path,
query: match?.query,
routeParams,
url,
});
const filePath = match?.filePath || presetComponent;
const bundledMap = match?.filePath
? global.BUNDLER_CTX_MAP?.[match.filePath]
: undefined;
const module = await import(filePath);
const Component = module.default;
const component = _jsx(Component, { children: _jsx("span", { children: error.message }) });
return {
component,
routeParams,
module,
bundledMap,
serverRes: _.merge(serverRes, default_server_res),
root_module,
serverRes: {
responseOptions: {
status: is404 ? 404 : 500,
},
},
};
}
catch {
@ -57,7 +41,12 @@ export default async function grabPageErrorComponent({ error, routeParams, is404
component: _jsx(DefaultNotFound, {}),
routeParams,
module: { default: DefaultNotFound },
serverRes: default_server_res,
bundledMap: undefined,
serverRes: {
responseOptions: {
status: is404 ? 404 : 500,
},
},
};
}
}

View File

@ -1,16 +0,0 @@
import type { BunextPageModule, BunextPageModuleServerReturn, BunxRouteParams } from "../../../types";
import type { JSX } from "react";
type Params = {
file_path: string;
debug?: boolean;
url?: URL;
query?: any;
routeParams?: BunxRouteParams;
};
export default function grabPageModules({ file_path, debug, url, query, routeParams, }: Params): Promise<{
component: JSX.Element;
serverRes: BunextPageModuleServerReturn;
module: BunextPageModule;
root_module: BunextPageModule | undefined;
}>;
export {};

View File

@ -1,69 +0,0 @@
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
import _ from "lodash";
import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import grabPageServerRes from "./grab-page-server-res";
import grabPageServerPath from "./grab-page-server-path";
export default async function grabPageModules({ file_path, debug, url, query, routeParams, }) {
const now = Date.now();
const { root_file_path } = grabRootFilePath();
const root_module = root_file_path
? await import(`${root_file_path}?t=${now}`)
: undefined;
const { server_file_path: root_server_file_path } = root_file_path
? grabPageServerPath({ file_path: root_file_path })
: {};
const root_server_module = root_server_file_path
? await import(`${root_server_file_path}?t=${now}`)
: undefined;
const root_server_fn = root_server_module?.default || root_server_module?.server;
const rootServerRes = root_server_fn
? await grabPageServerRes({
server_function: root_server_fn,
url,
query,
routeParams,
})
: undefined;
if (debug) {
log.info(`rootServerRes:`, rootServerRes);
}
const module = await import(`${file_path}?t=${now}`);
const { server_file_path } = grabPageServerPath({ file_path });
const server_module = server_file_path
? await import(`${server_file_path}?t=${now}`)
: undefined;
if (debug) {
log.info(`module:`, module);
}
const server_fn = server_module?.default || server_module?.server;
const serverRes = server_fn
? await grabPageServerRes({
server_function: server_fn,
url,
query,
routeParams,
})
: undefined;
if (debug) {
log.info(`serverRes:`, serverRes);
}
const mergedServerRes = _.merge(rootServerRes || {}, serverRes || {});
const { component } = (await grabPageBundledReactComponent({
file_path,
root_file_path,
server_res: mergedServerRes,
})) || {};
if (!component) {
throw new Error(`Couldn't grab page component`);
}
if (debug) {
log.info(`component:`, component);
}
return {
component,
serverRes: mergedServerRes,
module,
root_module,
};
}

View File

@ -250,6 +250,7 @@ export type GrabPageReactBundledComponentRes = {
};
export type PageFiles = {
local_path: string;
transformed_path: string;
url_path: string;
file_name: string;
};

View File

@ -67,9 +67,10 @@ function grabPageFileObject({ file_path, }) {
let file_name = url_path.split("/").pop();
if (!file_name)
return;
// const transformed_path = pagePathTransform({ page_path: file_path });
const transformed_path = pagePathTransform({ page_path: file_path });
return {
local_path: file_path,
transformed_path,
url_path,
file_name,
};

View File

@ -2,7 +2,7 @@
"name": "@moduletrace/bunext",
"module": "index.ts",
"type": "module",
"version": "1.0.33",
"version": "1.0.32",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {

View File

@ -90,6 +90,7 @@ export default async function allPagesBundler(params?: Params) {
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
process.exit(1);
}
});

View File

@ -1,81 +0,0 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabClientHydrationScript from "./grab-client-hydration-script";
import type { PageFiles } from "../../types";
import path from "path";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
type Params = {
post_build_fn?: (params: { artifacts: any[] }) => Promise<void> | void;
};
export default async function allPagesESBuildContextBundlerFiles(
params?: Params,
) {
const pages = grabAllPages({ exclude_api: true });
global.PAGE_FILES = pages;
const dev = isDevelopment();
const entryToPage = new Map<string, PageFiles & { tsx: string }>();
for (const page of pages) {
const tsx = await grabClientHydrationScript({
page_local_path: page.local_path,
});
if (!tsx) continue;
const entryFile = path.join(
BUNX_HYDRATION_SRC_DIR,
`${page.url_path}.tsx`,
);
await Bun.write(entryFile, tsx, { createPath: true });
entryToPage.set(entryFile, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()];
const ctx = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(
dev ? "development" : "production",
),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
splitting: true,
logLevel: "silent",
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
],
});
await ctx.rebuild();
global.BUNDLER_CTX = ctx;
}

View File

@ -2,51 +2,114 @@ import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import { log } from "../../utils/log";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabClientHydrationScript from "./grab-client-hydration-script";
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
import { writeFileSync } from "fs";
import type { PageFiles } from "../../types";
import path from "path";
import virtualFilesPlugin from "./plugins/virtual-files-plugin";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
const {
HYDRATION_DST_DIR,
HYDRATION_DST_DIR_MAP_JSON_FILE,
BUNX_HYDRATION_SRC_DIR,
} = grabDirNames();
let build_starts = 0;
const MAX_BUILD_STARTS = 10;
type Params = {
post_build_fn?: (params: { artifacts: any[] }) => Promise<void> | void;
// watch?: boolean;
};
export default async function allPagesESBuildContextBundler(params?: Params) {
// return await allPagesESBuildContextBundlerFiles(params);
const pages = grabAllPages({ exclude_api: true });
global.PAGE_FILES = pages;
const dev = isDevelopment();
const entryToPage = new Map<string, PageFiles & { tsx: string }>();
const entryToPage = new Map<string, PageFiles>();
for (const page of pages) {
const tsx = await grabClientHydrationScript({
const txt = await grabClientHydrationScript({
page_local_path: page.local_path,
});
if (!tsx) continue;
if (!txt) continue;
const entryFile = path.join(
BUNX_HYDRATION_SRC_DIR,
`${page.url_path}.tsx`,
);
// await Bun.write(entryFile, txt, { createPath: true });
entryToPage.set(entryFile, { ...page, tsx });
await Bun.write(entryFile, txt, { createPath: true });
entryToPage.set(path.resolve(entryFile), page);
}
const entryPoints = [...entryToPage.keys()].map(
(e) => `hydration-virtual:${e}`,
);
let buildStart = 0;
global.BUNDLER_CTX = await esbuild.context({
const artifactTracker: esbuild.Plugin = {
name: "artifact-tracker",
setup(build) {
build.onStart(() => {
build_starts++;
buildStart = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
process.exit(1);
}
});
build.onEnd((result) => {
if (result.errors.length > 0) {
for (const error of result.errors) {
const loc = error.location;
const location = loc
? ` ${loc.file}:${loc.line}:${loc.column}`
: "";
log.error(`[Build]${location} ${error.text}`);
}
return;
}
const artifacts = grabArtifactsFromBundledResults({
result,
entryToPage,
});
if (artifacts?.[0] && artifacts.length > 0) {
for (let i = 0; i < artifacts.length; i++) {
const artifact = artifacts[i];
if (artifact?.local_path && global.BUNDLER_CTX_MAP) {
global.BUNDLER_CTX_MAP[artifact.local_path] =
artifact;
}
}
params?.post_build_fn?.({ artifacts });
// writeFileSync(
// HYDRATION_DST_DIR_MAP_JSON_FILE,
// JSON.stringify(artifacts, null, 4),
// );
}
const elapsed = (performance.now() - buildStart).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
build_starts = 0;
});
},
};
const entryPoints = [...entryToPage.keys()];
const ctx = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
@ -61,21 +124,10 @@ export default async function allPagesESBuildContextBundler(params?: Params) {
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
virtualFilesPlugin({
entryToPage,
}),
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
plugins: [tailwindEsbuildPlugin, artifactTracker],
jsx: "automatic",
splitting: true,
logLevel: "silent",
// logLevel: "silent",
// logLevel: dev ? "error" : "silent",
external: [
"react",
"react-dom",
@ -84,5 +136,11 @@ export default async function allPagesESBuildContextBundler(params?: Params) {
],
});
await global.BUNDLER_CTX.rebuild();
await ctx.rebuild();
// if (params?.watch) {
// await ctx.watch();
// }
global.BUNDLER_CTX = ctx;
}

View File

@ -2,18 +2,12 @@ import path from "path";
import * as esbuild from "esbuild";
import type { BundlerCTXMap, PageFiles } from "../../types";
import grabDirNames from "../../utils/grab-dir-names";
import { log } from "../../utils/log";
const { ROOT_DIR } = grabDirNames();
type Params = {
result: esbuild.BuildResult<esbuild.BuildOptions>;
entryToPage: Map<
string,
PageFiles & {
tsx: string;
}
>;
entryToPage: Map<string, PageFiles>;
};
export default function grabArtifactsFromBundledResults({
@ -27,14 +21,7 @@ export default function grabArtifactsFromBundledResults({
)
.filter(([, meta]) => meta.entryPoint)
.map(([outputPath, meta]) => {
const entrypoint = meta.entryPoint?.match(/^hydration-virtual:/)
? meta.entryPoint?.replace(/^hydration-virtual:/, "")
: meta.entryPoint
? path.join(ROOT_DIR, meta.entryPoint)
: "";
// const entrypoint = path.join(ROOT_DIR, meta.entryPoint || "");
// console.log("entrypoint", entrypoint);
const entrypoint = path.join(ROOT_DIR, meta.entryPoint || "");
const target_page = entryToPage.get(entrypoint);
@ -42,7 +29,8 @@ export default function grabArtifactsFromBundledResults({
return undefined;
}
const { file_name, local_path, url_path } = target_page;
const { file_name, local_path, url_path, transformed_path } =
target_page;
return {
path: outputPath,
@ -50,11 +38,12 @@ export default function grabArtifactsFromBundledResults({
type: outputPath.endsWith(".css")
? "text/css"
: "text/javascript",
entrypoint: meta.entryPoint,
entrypoint,
css_path: meta.cssBundle,
file_name,
local_path,
url_path,
transformed_path,
};
});

View File

@ -28,28 +28,6 @@ export default async function grabClientHydrationScript({
// ? pagePathTransform({ page_path: root_file_path })
// : undefined;
if (!existsSync(page_local_path)) {
return undefined;
}
if (root_file_path) {
if (!existsSync(root_file_path)) {
return undefined;
}
const root_content = await Bun.file(root_file_path).text();
if (!root_content.match(/^export default/m)) {
return undefined;
}
}
const page_content = await Bun.file(page_local_path).text();
if (!page_content.match(/^export default/m)) {
return undefined;
}
let txt = ``;
txt += `import { hydrateRoot } from "react-dom/client";\n`;

View File

@ -1,85 +0,0 @@
import type { Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
import { log } from "../../../utils/log";
import grabArtifactsFromBundledResults from "../grab-artifacts-from-bundled-result";
let buildStart = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 10;
type Params = {
entryToPage: Map<
string,
PageFiles & {
tsx: string;
}
>;
post_build_fn?: (params: { artifacts: any[] }) => Promise<void> | void;
};
export default function esbuildCTXArtifactTracker({
entryToPage,
post_build_fn,
}: Params) {
const artifactTracker: Plugin = {
name: "artifact-tracker",
setup(build) {
build.onStart(() => {
build_starts++;
buildStart = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
global.RECOMPILING = false;
}
});
build.onEnd((result) => {
if (result.errors.length > 0) {
// for (const error of result.errors) {
// const loc = error.location;
// const location = loc
// ? ` ${loc.file}:${loc.line}:${loc.column}`
// : "";
// log.error(`[Build]${location} ${error.text}`);
// }
return;
}
const artifacts = grabArtifactsFromBundledResults({
result,
entryToPage,
});
// console.log("artifacts", artifacts);
if (artifacts?.[0] && artifacts.length > 0) {
for (let i = 0; i < artifacts.length; i++) {
const artifact = artifacts[i];
if (artifact?.local_path && global.BUNDLER_CTX_MAP) {
global.BUNDLER_CTX_MAP[artifact.local_path] =
artifact;
}
}
post_build_fn?.({ artifacts });
// writeFileSync(
// HYDRATION_DST_DIR_MAP_JSON_FILE,
// JSON.stringify(artifacts, null, 4),
// );
}
const elapsed = (performance.now() - buildStart).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
build_starts = 0;
});
},
};
return artifactTracker;
}

View File

@ -1,46 +0,0 @@
import type { Plugin } from "esbuild";
import path from "path";
import type { PageFiles } from "../../../types";
import { log } from "../../../utils/log";
type Params = {
entryToPage: Map<
string,
PageFiles & {
tsx: string;
}
>;
};
export default function virtualFilesPlugin({ entryToPage }: Params) {
const virtualPlugin: Plugin = {
name: "virtual-hydration",
setup(build) {
build.onResolve({ filter: /^hydration-virtual:/ }, (args) => {
const final_path = args.path.replace(/hydration-virtual:/, "");
return {
path: final_path,
namespace: "hydration-virtual",
};
});
build.onLoad(
{ filter: /.*/, namespace: "hydration-virtual" },
(args) => {
const target = entryToPage.get(args.path);
if (!target?.tsx) return null;
const contents = target.tsx;
return {
contents: contents || "",
loader: "tsx",
resolveDir: path.dirname(target.local_path),
};
},
);
},
};
return virtualPlugin;
}

View File

@ -14,7 +14,7 @@ export default async function serverPostBuildFn() {
for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.HMR_CONTROLLERS[i];
if (!controller?.target_map?.local_path) {
if (!controller.target_map?.local_path) {
continue;
}

View File

@ -18,10 +18,6 @@ export default async function watcherEsbuildCTX() {
async (event, filename) => {
if (!filename) return;
if (filename.match(/^\.\w+/)) {
return;
}
const full_file_path = path.join(ROOT_DIR, filename);
if (full_file_path.match(/\/styles$/)) {
@ -51,21 +47,7 @@ export default async function watcherEsbuildCTX() {
if (filename.match(target_files_match)) {
if (global.RECOMPILING) return;
global.RECOMPILING = true;
await global.BUNDLER_CTX?.rebuild();
if (filename.match(/(404|500)\.tsx?/)) {
for (
let i = global.HMR_CONTROLLERS.length - 1;
i >= 0;
i--
) {
const controller = global.HMR_CONTROLLERS[i];
controller?.controller?.enqueue(
`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`,
);
}
}
}
return;
}
@ -110,13 +92,13 @@ async function fullRebuild(params?: { msg?: string }) {
await global.BUNDLER_CTX?.dispose();
global.BUNDLER_CTX = undefined;
global.BUNDLER_CTX_MAP = {};
allPagesESBuildContextBundler({
await allPagesESBuildContextBundler({
post_build_fn: serverPostBuildFn,
});
} catch (error: any) {
log.error(error);
} finally {
global.RECOMPILING = false;
}
if (global.PAGES_SRC_WATCHER) {

View File

@ -1,4 +1,3 @@
import type { JSX } from "react";
import type { GrabPageReactBundledComponentRes } from "../../../types";
import grabPageReactComponentString from "./grab-page-react-component-string";
import grabTsxStringModule from "./grab-tsx-string-module";

View File

@ -14,7 +14,6 @@ import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import grabPageServerRes from "./grab-page-server-res";
import grabPageServerPath from "./grab-page-server-path";
import grabPageModules from "./grab-page-modules";
class NotFoundError extends Error {}
@ -31,6 +30,7 @@ export default async function grabPageComponent({
}: Params): Promise<GrabPageComponentRes> {
const url = req?.url ? new URL(req.url) : undefined;
const router = global.ROUTER;
const now = Date.now();
let routeParams: BunxRouteParams | undefined = undefined;
@ -78,18 +78,79 @@ export default async function grabPageComponent({
log.info(`bundledMap:`, bundledMap);
}
const { component, module, serverRes, root_module } =
await grabPageModules({
file_path,
debug,
const { root_file_path } = grabRootFilePath();
const root_module: BunextRootModule | undefined = root_file_path
? await import(`${root_file_path}?t=${now}`)
: undefined;
const { server_file_path: root_server_file_path } = root_file_path
? grabPageServerPath({ file_path: root_file_path })
: {};
const root_server_module: BunextPageServerModule = root_server_file_path
? await import(`${root_server_file_path}?t=${now}`)
: undefined;
const root_server_fn =
root_server_module?.default || root_server_module?.server;
const rootServerRes: BunextPageModuleServerReturn | undefined =
root_server_fn
? await grabPageServerRes({
server_function: root_server_fn,
url,
query: match?.query,
routeParams,
})
: undefined;
if (debug) {
log.info(`rootServerRes:`, rootServerRes);
}
const module: BunextPageModule = await import(`${file_path}?t=${now}`);
const { server_file_path } = grabPageServerPath({ file_path });
const server_module: BunextPageServerModule = server_file_path
? await import(`${server_file_path}?t=${now}`)
: undefined;
if (debug) {
log.info(`module:`, module);
}
const server_fn = server_module?.default || server_module?.server;
const serverRes: BunextPageModuleServerReturn | undefined = server_fn
? await grabPageServerRes({
server_function: server_fn,
url,
});
query: match?.query,
routeParams,
})
: undefined;
if (debug) {
log.info(`serverRes:`, serverRes);
}
const mergedServerRes = _.merge(rootServerRes || {}, serverRes || {});
const { component } =
(await grabPageBundledReactComponent({
file_path,
root_file_path,
server_res: mergedServerRes,
})) || {};
if (!component) {
throw new Error(`Couldn't grab page component`);
}
if (debug) {
log.info(`component:`, component);
}
return {
component,
serverRes,
serverRes: mergedServerRes,
routeParams,
module,
bundledMap,
@ -102,7 +163,6 @@ export default async function grabPageComponent({
error,
routeParams,
is404: error instanceof NotFoundError,
url,
});
}
}

View File

@ -5,21 +5,17 @@ import type {
BunxRouteParams,
GrabPageComponentRes,
} from "../../../types";
import grabPageModules from "./grab-page-modules";
import _ from "lodash";
type Params = {
error?: any;
routeParams?: BunxRouteParams;
is404?: boolean;
url?: URL;
};
export default async function grabPageErrorComponent({
error,
routeParams,
is404,
url,
}: Params): Promise<GrabPageComponentRes> {
const router = global.ROUTER;
@ -31,51 +27,28 @@ export default async function grabPageErrorComponent({
? BUNX_ROOT_404_PRESET_COMPONENT
: BUNX_ROOT_500_PRESET_COMPONENT;
const default_server_res = {
responseOptions: {
status: is404 ? 404 : 500,
},
};
try {
const match = router.match(errorRoute);
const filePath = match?.filePath || presetComponent;
if (!match?.filePath) {
const default_module: BunextPageModule = await import(
presetComponent
);
const Component = default_module.default as FC<any>;
const default_jsx = (
<Component>{<span>{error.message}</span>}</Component>
);
const bundledMap = match?.filePath
? global.BUNDLER_CTX_MAP?.[match.filePath]
: undefined;
return {
component: default_jsx,
module: default_module,
routeParams,
serverRes: default_server_res,
};
}
const file_path = match.filePath;
const bundledMap = global.BUNDLER_CTX_MAP?.[file_path];
const { component, module, serverRes, root_module } =
await grabPageModules({
file_path: file_path,
query: match?.query,
routeParams,
url,
});
const module: BunextPageModule = await import(filePath);
const Component = module.default as FC<any>;
const component = <Component>{<span>{error.message}</span>}</Component>;
return {
component,
routeParams,
module,
bundledMap,
serverRes: _.merge(serverRes, default_server_res),
root_module,
serverRes: {
responseOptions: {
status: is404 ? 404 : 500,
},
} as any,
};
} catch {
const DefaultNotFound: FC = () => (
@ -98,7 +71,12 @@ export default async function grabPageErrorComponent({
component: <DefaultNotFound />,
routeParams,
module: { default: DefaultNotFound },
serverRes: default_server_res,
bundledMap: undefined,
serverRes: {
responseOptions: {
status: is404 ? 404 : 500,
},
} as any,
};
}
}

View File

@ -1,109 +0,0 @@
import type {
BunextPageModule,
BunextPageModuleServerReturn,
BunextPageServerModule,
BunextRootModule,
BunxRouteParams,
} from "../../../types";
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
import _ from "lodash";
import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import grabPageServerRes from "./grab-page-server-res";
import grabPageServerPath from "./grab-page-server-path";
import type { JSX } from "react";
type Params = {
file_path: string;
debug?: boolean;
url?: URL;
query?: any;
routeParams?: BunxRouteParams;
};
export default async function grabPageModules({
file_path,
debug,
url,
query,
routeParams,
}: Params) {
const now = Date.now();
const { root_file_path } = grabRootFilePath();
const root_module: BunextRootModule | undefined = root_file_path
? await import(`${root_file_path}?t=${now}`)
: undefined;
const { server_file_path: root_server_file_path } = root_file_path
? grabPageServerPath({ file_path: root_file_path })
: {};
const root_server_module: BunextPageServerModule = root_server_file_path
? await import(`${root_server_file_path}?t=${now}`)
: undefined;
const root_server_fn =
root_server_module?.default || root_server_module?.server;
const rootServerRes: BunextPageModuleServerReturn | undefined =
root_server_fn
? await grabPageServerRes({
server_function: root_server_fn,
url,
query,
routeParams,
})
: undefined;
if (debug) {
log.info(`rootServerRes:`, rootServerRes);
}
const module: BunextPageModule = await import(`${file_path}?t=${now}`);
const { server_file_path } = grabPageServerPath({ file_path });
const server_module: BunextPageServerModule = server_file_path
? await import(`${server_file_path}?t=${now}`)
: undefined;
if (debug) {
log.info(`module:`, module);
}
const server_fn = server_module?.default || server_module?.server;
const serverRes: BunextPageModuleServerReturn | undefined = server_fn
? await grabPageServerRes({
server_function: server_fn,
url,
query,
routeParams,
})
: undefined;
if (debug) {
log.info(`serverRes:`, serverRes);
}
const mergedServerRes = _.merge(rootServerRes || {}, serverRes || {});
const { component } =
(await grabPageBundledReactComponent({
file_path,
root_file_path,
server_res: mergedServerRes,
})) || {};
if (!component) {
throw new Error(`Couldn't grab page component`);
}
if (debug) {
log.info(`component:`, component);
}
return {
component,
serverRes: mergedServerRes,
module,
root_module,
};
}

View File

@ -283,6 +283,7 @@ export type GrabPageReactBundledComponentRes = {
export type PageFiles = {
local_path: string;
transformed_path: string;
url_path: string;
file_name: string;
};

View File

@ -90,10 +90,11 @@ function grabPageFileObject({
let file_name = url_path.split("/").pop();
if (!file_name) return;
// const transformed_path = pagePathTransform({ page_path: file_path });
const transformed_path = pagePathTransform({ page_path: file_path });
return {
local_path: file_path,
transformed_path,
url_path,
file_name,
};