Server Refactor
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
import { describe, expect, test, beforeAll, afterAll } from "bun:test";
|
||||
import startServer from "../../../src/functions/server/start-server";
|
||||
import rewritePagesModule from "../../../src/utils/rewrite-pages-module";
|
||||
import pagePathTransform from "../../../src/utils/page-path-transform";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
@@ -10,9 +8,6 @@ const fixtureDir = path.resolve(__dirname, "../../../test/e2e-fixture");
|
||||
const fixturePagesDir = path.join(fixtureDir, "src", "pages");
|
||||
const fixtureIndexPage = path.join(fixturePagesDir, "index.tsx");
|
||||
|
||||
// The rewritten page path (inside .bunext/pages, stripped of server logic)
|
||||
const rewrittenIndexPage = pagePathTransform({ page_path: fixtureIndexPage });
|
||||
|
||||
let originalCwd = process.cwd();
|
||||
let originalPort: string | undefined;
|
||||
|
||||
@@ -37,10 +32,6 @@ describe("E2E Integration", () => {
|
||||
dir: fixturePagesDir,
|
||||
});
|
||||
|
||||
// Rewrite the fixture page (strips server logic) into .bunext/pages
|
||||
// so that grab-page-react-component-string can resolve the import
|
||||
await rewritePagesModule({ page_file_path: fixtureIndexPage });
|
||||
|
||||
// Pre-populate the bundler context map so grab-page-component can
|
||||
// look up the compiled path. The `path` value only needs to be
|
||||
// present for the guard check; SSR does not require the file to exist.
|
||||
@@ -70,12 +61,6 @@ describe("E2E Integration", () => {
|
||||
delete process.env.PORT;
|
||||
}
|
||||
|
||||
// Remove the rewritten page created during setup
|
||||
const rewrittenDir = path.dirname(rewrittenIndexPage);
|
||||
if (fs.existsSync(rewrittenDir)) {
|
||||
fs.rmSync(rewrittenDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
// Remove any generated .bunext artifacts from the fixture
|
||||
const dotBunext = path.join(fixtureDir, ".bunext");
|
||||
if (fs.existsSync(dotBunext)) {
|
||||
@@ -102,4 +87,41 @@ describe("E2E Integration", () => {
|
||||
// Default 404 component is rendered
|
||||
expect(text).toContain("404");
|
||||
});
|
||||
|
||||
test("server props injected from .server.ts companion file", async () => {
|
||||
const serverFilePath = path.join(fixturePagesDir, "index.server.ts");
|
||||
const pageFilePath = fixtureIndexPage;
|
||||
|
||||
// Write a temporary .server.ts companion that injects a prop
|
||||
await Bun.write(serverFilePath, `
|
||||
import type { BunextPageServerFn } from "../../../../../src/types";
|
||||
|
||||
const server: BunextPageServerFn<{ greeting: string }> = async () => {
|
||||
return { props: { greeting: "Hello from server" } };
|
||||
};
|
||||
|
||||
export default server;
|
||||
`);
|
||||
|
||||
// Add the fixture page to the BUNDLER_CTX_MAP
|
||||
global.BUNDLER_CTX_MAP[pageFilePath] = {
|
||||
path: ".bunext/public/pages/index.js",
|
||||
hash: "index",
|
||||
type: "text/javascript",
|
||||
entrypoint: pageFilePath,
|
||||
local_path: pageFilePath,
|
||||
url_path: "/",
|
||||
file_name: "index",
|
||||
};
|
||||
|
||||
const response = await fetch(`http://localhost:${server.port}/`);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const html = await response.text();
|
||||
// __PAGE_PROPS__ should include the prop from the server companion
|
||||
expect(html).toContain("Hello from server");
|
||||
|
||||
// Clean up
|
||||
fs.unlinkSync(serverFilePath);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import grabPageServerPath from "../../../../src/functions/server/web-pages/grab-page-server-path";
|
||||
|
||||
const tmpDir = path.join(import.meta.dir, "__tmp_server_path__");
|
||||
|
||||
beforeAll(() => {
|
||||
fs.mkdirSync(tmpDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("grabPageServerPath", () => {
|
||||
it("returns undefined when no companion file exists", () => {
|
||||
const { server_file_path } = grabPageServerPath({
|
||||
file_path: path.join(tmpDir, "index.tsx"),
|
||||
});
|
||||
expect(server_file_path).toBeUndefined();
|
||||
});
|
||||
|
||||
it("resolves .server.ts companion for a .tsx page", () => {
|
||||
const serverFile = path.join(tmpDir, "profile.server.ts");
|
||||
fs.writeFileSync(serverFile, "export default async () => ({})");
|
||||
|
||||
const { server_file_path } = grabPageServerPath({
|
||||
file_path: path.join(tmpDir, "profile.tsx"),
|
||||
});
|
||||
|
||||
expect(server_file_path).toBe(serverFile);
|
||||
});
|
||||
|
||||
it("resolves .server.tsx companion when only .server.tsx exists", () => {
|
||||
const serverFile = path.join(tmpDir, "about.server.tsx");
|
||||
fs.writeFileSync(serverFile, "export default async () => ({})");
|
||||
|
||||
const { server_file_path } = grabPageServerPath({
|
||||
file_path: path.join(tmpDir, "about.tsx"),
|
||||
});
|
||||
|
||||
expect(server_file_path).toBe(serverFile);
|
||||
});
|
||||
|
||||
it("prefers .server.ts over .server.tsx when both exist", () => {
|
||||
const tsFile = path.join(tmpDir, "blog.server.ts");
|
||||
const tsxFile = path.join(tmpDir, "blog.server.tsx");
|
||||
fs.writeFileSync(tsFile, "export default async () => ({})");
|
||||
fs.writeFileSync(tsxFile, "export default async () => ({})");
|
||||
|
||||
const { server_file_path } = grabPageServerPath({
|
||||
file_path: path.join(tmpDir, "blog.tsx"),
|
||||
});
|
||||
|
||||
expect(server_file_path).toBe(tsFile);
|
||||
});
|
||||
|
||||
it("resolves companion for a .ts page file", () => {
|
||||
const serverFile = path.join(tmpDir, "api-page.server.ts");
|
||||
fs.writeFileSync(serverFile, "export default async () => ({})");
|
||||
|
||||
const { server_file_path } = grabPageServerPath({
|
||||
file_path: path.join(tmpDir, "api-page.ts"),
|
||||
});
|
||||
|
||||
expect(server_file_path).toBe(serverFile);
|
||||
});
|
||||
});
|
||||
@@ -13,9 +13,6 @@ export default function () {
|
||||
return new Command("build")
|
||||
.description("Build Project")
|
||||
.action(async () => {
|
||||
process.env.NODE_ENV = "production";
|
||||
process.env.BUILD = "true";
|
||||
|
||||
try {
|
||||
rmSync(HYDRATION_DST_DIR, { recursive: true });
|
||||
rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Command } from "commander";
|
||||
import startServer from "../../functions/server/start-server";
|
||||
import { log } from "../../utils/log";
|
||||
import bunextInit from "../../functions/bunext-init";
|
||||
// import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import { rmSync } from "fs";
|
||||
import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler";
|
||||
|
||||
const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames();
|
||||
|
||||
@@ -21,8 +21,8 @@ export default function () {
|
||||
rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
|
||||
} catch (error) {}
|
||||
|
||||
// await rewritePagesModule();
|
||||
await bunextInit();
|
||||
await allPagesBunBundler();
|
||||
|
||||
await startServer();
|
||||
});
|
||||
|
||||
@@ -2,17 +2,18 @@ import { Command } from "commander";
|
||||
import startServer from "../../functions/server/start-server";
|
||||
import { log } from "../../utils/log";
|
||||
import bunextInit from "../../functions/bunext-init";
|
||||
import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler";
|
||||
|
||||
export default function () {
|
||||
return new Command("start")
|
||||
.description("Start production server")
|
||||
.action(async () => {
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
log.info("Starting production server ...");
|
||||
|
||||
await bunextInit();
|
||||
|
||||
await allPagesBunBundler();
|
||||
|
||||
await startServer();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,17 +56,19 @@ export default async function allPagesBunBundler(params?: Params) {
|
||||
|
||||
const buildStart = performance.now();
|
||||
|
||||
const define = {
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
dev ? "development" : "production",
|
||||
),
|
||||
};
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints: [...entryToPage.keys()],
|
||||
outdir: HYDRATION_DST_DIR,
|
||||
root: BUNX_HYDRATION_SRC_DIR,
|
||||
minify: true,
|
||||
minify: !dev,
|
||||
format: "esm",
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
dev ? "development" : "production",
|
||||
),
|
||||
},
|
||||
define,
|
||||
naming: {
|
||||
entry: "[dir]/[hash].[ext]",
|
||||
chunk: "chunks/[hash].[ext]",
|
||||
|
||||
@@ -25,8 +25,8 @@ export default async function recordArtifacts({
|
||||
global.BUNDLER_CTX_MAP = _.merge(global.BUNDLER_CTX_MAP, artifacts_map);
|
||||
}
|
||||
|
||||
await Bun.write(
|
||||
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
||||
JSON.stringify(artifacts_map, null, 4),
|
||||
);
|
||||
// await Bun.write(
|
||||
// HYDRATION_DST_DIR_MAP_JSON_FILE,
|
||||
// JSON.stringify(artifacts_map, null, 4),
|
||||
// );
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ import type {
|
||||
} from "../types";
|
||||
import type { FileSystemRouter, Server } from "bun";
|
||||
import grabDirNames from "../utils/grab-dir-names";
|
||||
import { readFileSync, type FSWatcher } from "fs";
|
||||
import { type FSWatcher } from "fs";
|
||||
import init from "./init";
|
||||
import isDevelopment from "../utils/is-development";
|
||||
import allPagesBundler from "./bundler/all-pages-bundler";
|
||||
import watcher from "./server/watcher";
|
||||
import { log } from "../utils/log";
|
||||
import cron from "./server/cron";
|
||||
import EJSON from "../utils/ejson";
|
||||
import allPagesBunBundler from "./bundler/all-pages-bun-bundler";
|
||||
|
||||
/**
|
||||
@@ -36,7 +34,6 @@ declare global {
|
||||
var PAGE_FILES: PageFiles[];
|
||||
var ROOT_FILE_UPDATED: boolean;
|
||||
var SKIPPED_BROWSER_MODULES: Set<string>;
|
||||
// var BUNDLER_CTX: BuildContext | undefined;
|
||||
}
|
||||
|
||||
const { PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
|
||||
@@ -63,18 +60,8 @@ export default async function bunextInit() {
|
||||
const is_dev = isDevelopment();
|
||||
|
||||
if (is_dev) {
|
||||
// await allPagesBundler();
|
||||
await allPagesBunBundler();
|
||||
watcher();
|
||||
} else {
|
||||
const artifacts = EJSON.parse(
|
||||
readFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, "utf-8"),
|
||||
) as { [k: string]: BundlerCTXMap } | undefined;
|
||||
if (!artifacts) {
|
||||
log.error("Please build first.");
|
||||
process.exit(1);
|
||||
}
|
||||
global.BUNDLER_CTX_MAP = artifacts;
|
||||
cron();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +82,9 @@ async function fullRebuild(params?: { msg?: string }) {
|
||||
|
||||
global.RECOMPILING = true;
|
||||
|
||||
const target_file_paths = global.HMR_CONTROLLERS.map(
|
||||
(hmr) => hmr.target_map?.local_path,
|
||||
).filter((f) => typeof f == "string");
|
||||
// const target_file_paths = global.HMR_CONTROLLERS.map(
|
||||
// (hmr) => hmr.target_map?.local_path,
|
||||
// ).filter((f) => typeof f == "string");
|
||||
|
||||
// await rewritePagesModule();
|
||||
|
||||
@@ -92,7 +92,8 @@ async function fullRebuild(params?: { msg?: string }) {
|
||||
log.watch(msg);
|
||||
}
|
||||
|
||||
await rebuildBundler({ target_file_paths });
|
||||
await rebuildBundler();
|
||||
// await rebuildBundler({ target_file_paths });
|
||||
} catch (error: any) {
|
||||
log.error(error);
|
||||
} finally {
|
||||
|
||||
@@ -69,23 +69,22 @@ export default async function genWebHTML({
|
||||
|
||||
const dev = isDevelopment();
|
||||
const devSuffix = dev ? "?dev" : "";
|
||||
const browser_imports: Record<string, string> = {
|
||||
react: `https://esm.sh/react@${_reactVersion}`,
|
||||
"react-dom": `https://esm.sh/react-dom@${_reactVersion}`,
|
||||
"react-dom/client": `https://esm.sh/react-dom@${_reactVersion}/client`,
|
||||
"react/jsx-runtime": `https://esm.sh/react@${_reactVersion}/jsx-runtime`,
|
||||
};
|
||||
|
||||
if (dev) {
|
||||
browser_imports["react/jsx-dev-runtime"] =
|
||||
`https://esm.sh/react@${_reactVersion}/jsx-dev-runtime`;
|
||||
}
|
||||
|
||||
const importMap = JSON.stringify({
|
||||
imports: {
|
||||
react: `https://esm.sh/react@${_reactVersion}${devSuffix}`,
|
||||
"react-dom": `https://esm.sh/react-dom@${_reactVersion}${devSuffix}`,
|
||||
"react-dom/client": `https://esm.sh/react-dom@${_reactVersion}/client${devSuffix}`,
|
||||
"react/jsx-runtime": `https://esm.sh/react@${_reactVersion}/jsx-runtime${devSuffix}`,
|
||||
"react/jsx-dev-runtime": `https://esm.sh/react@${_reactVersion}/jsx-dev-runtime${devSuffix}`,
|
||||
},
|
||||
imports: browser_imports,
|
||||
});
|
||||
|
||||
// let skipped_modules_import_map: { [k: string]: string } = {};
|
||||
|
||||
// [...global.SKIPPED_BROWSER_MODULES].forEach((sk) => {
|
||||
// skipped_modules_import_map[sk] =
|
||||
// "data:text/javascript,export default {}";
|
||||
// });
|
||||
|
||||
let final_component = (
|
||||
<html {...html_props}>
|
||||
<head>
|
||||
@@ -151,12 +150,32 @@ export default async function genWebHTML({
|
||||
{Head ? <Head serverRes={pageProps} ctx={routeParams} /> : null}
|
||||
</head>
|
||||
<body>
|
||||
<div id={ClientRootElementIDName}>{component}</div>
|
||||
<div
|
||||
id={ClientRootElementIDName}
|
||||
suppressHydrationWarning={!dev}
|
||||
>
|
||||
{component}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
let html = `<!DOCTYPE html>\n`;
|
||||
|
||||
// const stream = await renderToReadableStream(final_component, {
|
||||
// onError(error: any) {
|
||||
// // This is where you "omit" or handle the errors
|
||||
// // You can log it silently or ignore it
|
||||
// if (error.message.includes('unique "key" prop')) return;
|
||||
// console.error(error);
|
||||
// },
|
||||
// });
|
||||
|
||||
// // 2. Convert the Web Stream to a String (Bun-optimized)
|
||||
// const htmlBody = await new Response(stream).text();
|
||||
|
||||
// html += htmlBody;
|
||||
|
||||
html += renderToString(final_component);
|
||||
|
||||
return html;
|
||||
|
||||
@@ -4,6 +4,7 @@ import AppNames from "./grab-app-names";
|
||||
const prefix = {
|
||||
info: chalk.bgCyan.bold(" ℹnfo "),
|
||||
success: chalk.green.bold("✓"),
|
||||
zap: chalk.green.bold("⚡"),
|
||||
error: chalk.red.bold("✗"),
|
||||
warn: chalk.yellow.bold("⚠"),
|
||||
build: chalk.magenta.bold("⚙"),
|
||||
|
||||
Reference in New Issue
Block a user