Major Bugfix. Fix server component client compatibility
This commit is contained in:
parent
9f619c8898
commit
a9af20a8b2
2
.gitignore
vendored
2
.gitignore
vendored
@ -178,4 +178,4 @@ out
|
|||||||
/build
|
/build
|
||||||
__fixtures__
|
__fixtures__
|
||||||
/public
|
/public
|
||||||
/.data
|
/.*
|
||||||
@ -485,12 +485,12 @@ Create `src/pages/__root.tsx` to wrap every page in a shared layout. The root co
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/pages/__root.tsx
|
// src/pages/__root.tsx
|
||||||
import type { PropsWithChildren } from "react";
|
import type { BunextRootComponentProps } from "@moduletrace/bunext/types";
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
props,
|
props,
|
||||||
}: PropsWithChildren<any>) {
|
}: BunextRootComponentProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header>My App</header>
|
<header>My App</header>
|
||||||
|
|||||||
18
bun.lock
18
bun.lock
@ -6,6 +6,10 @@
|
|||||||
"name": "bun-next",
|
"name": "bun-next",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/postcss": "^4.2.2",
|
"@tailwindcss/postcss": "^4.2.2",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "^24.10.0",
|
||||||
|
"@types/react": "^19.2.2",
|
||||||
|
"@types/react-dom": "^19.2.2",
|
||||||
"bun-plugin-tailwind": "^0.1.2",
|
"bun-plugin-tailwind": "^0.1.2",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"commander": "^14.0.2",
|
"commander": "^14.0.2",
|
||||||
@ -19,12 +23,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/bun": "latest",
|
|
||||||
"@types/lodash": "^4.17.24",
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/micromatch": "^4.0.10",
|
"@types/micromatch": "^4.0.10",
|
||||||
"@types/node": "^24.10.0",
|
|
||||||
"@types/react": "^19.2.2",
|
|
||||||
"@types/react-dom": "^19.2.2",
|
|
||||||
"happy-dom": "^20.8.4",
|
"happy-dom": "^20.8.4",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
@ -169,7 +169,7 @@
|
|||||||
|
|
||||||
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
|
"@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.3", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="],
|
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||||
|
|
||||||
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
|
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
|
||||||
|
|
||||||
@ -197,7 +197,7 @@
|
|||||||
|
|
||||||
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
|
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.3", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-P7AeyTseLKAvgaZqQrvp3RqFM3yN9PlcLuSTe7SoJOfZkER73mLdT2vEQi8U64S1YvM/ldcNiQjn0Sn7H9lGgg=="],
|
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
||||||
|
|
||||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||||
|
|
||||||
@ -339,14 +339,8 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"bun-types/@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="],
|
|
||||||
|
|
||||||
"bun-types/@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
|
||||||
|
|
||||||
"lightningcss-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
|
"lightningcss-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
|
||||||
|
|
||||||
"strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
"strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||||
|
|
||||||
"bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
dist/commands/index.js
vendored
2
dist/commands/index.js
vendored
@ -4,6 +4,7 @@ import start from "./start";
|
|||||||
import dev from "./dev";
|
import dev from "./dev";
|
||||||
import build from "./build";
|
import build from "./build";
|
||||||
import { log } from "../utils/log";
|
import { log } from "../utils/log";
|
||||||
|
import rewritePages from "./rewrite-pages";
|
||||||
/**
|
/**
|
||||||
* # Describe Program
|
* # Describe Program
|
||||||
*/
|
*/
|
||||||
@ -17,6 +18,7 @@ program
|
|||||||
program.addCommand(dev());
|
program.addCommand(dev());
|
||||||
program.addCommand(start());
|
program.addCommand(start());
|
||||||
program.addCommand(build());
|
program.addCommand(build());
|
||||||
|
program.addCommand(rewritePages());
|
||||||
/**
|
/**
|
||||||
* # Handle Unavailable Commands
|
* # Handle Unavailable Commands
|
||||||
*/
|
*/
|
||||||
|
|||||||
2
dist/commands/rewrite-pages/index.d.ts
vendored
Normal file
2
dist/commands/rewrite-pages/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
export default function (): Command;
|
||||||
16
dist/commands/rewrite-pages/index.js
vendored
Normal file
16
dist/commands/rewrite-pages/index.js
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
import { log } from "../../utils/log";
|
||||||
|
import init from "../../functions/init";
|
||||||
|
import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||||
|
export default function () {
|
||||||
|
return new Command("rewrite-pages")
|
||||||
|
.description("Rewrite pages from src to .bunext dir")
|
||||||
|
.action(async () => {
|
||||||
|
process.env.NODE_ENV = "production";
|
||||||
|
process.env.BUILD = "true";
|
||||||
|
await init();
|
||||||
|
log.banner();
|
||||||
|
log.build("Rewriting Pages ...");
|
||||||
|
await rewritePagesModule();
|
||||||
|
});
|
||||||
|
}
|
||||||
22
dist/functions/bundler/all-pages-bundler.js
vendored
22
dist/functions/bundler/all-pages-bundler.js
vendored
@ -1,4 +1,4 @@
|
|||||||
import { existsSync, statSync, writeFileSync } from "fs";
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
import * as esbuild from "esbuild";
|
import * as esbuild from "esbuild";
|
||||||
import grabAllPages from "../../utils/grab-all-pages";
|
import grabAllPages from "../../utils/grab-all-pages";
|
||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
@ -8,7 +8,7 @@ import { log } from "../../utils/log";
|
|||||||
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
|
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
|
||||||
import grabClientHydrationScript from "./grab-client-hydration-script";
|
import grabClientHydrationScript from "./grab-client-hydration-script";
|
||||||
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
|
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
|
||||||
import path from "path";
|
import stripServerSideLogic from "./strip-server-side-logic";
|
||||||
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE, ROOT_DIR } = grabDirNames();
|
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE, ROOT_DIR } = grabDirNames();
|
||||||
let build_starts = 0;
|
let build_starts = 0;
|
||||||
const MAX_BUILD_STARTS = 10;
|
const MAX_BUILD_STARTS = 10;
|
||||||
@ -18,9 +18,11 @@ export default async function allPagesBundler(params) {
|
|||||||
const dev = isDevelopment();
|
const dev = isDevelopment();
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
const key = page.local_path;
|
const key = page.local_path;
|
||||||
const txt = grabClientHydrationScript({
|
const txt = await grabClientHydrationScript({
|
||||||
page_local_path: page.local_path,
|
page_local_path: page.local_path,
|
||||||
});
|
});
|
||||||
|
if (!txt)
|
||||||
|
continue;
|
||||||
virtualEntries[key] = txt;
|
virtualEntries[key] = txt;
|
||||||
}
|
}
|
||||||
const virtualPlugin = {
|
const virtualPlugin = {
|
||||||
@ -35,6 +37,19 @@ export default async function allPagesBundler(params) {
|
|||||||
loader: "tsx",
|
loader: "tsx",
|
||||||
resolveDir: process.cwd(),
|
resolveDir: process.cwd(),
|
||||||
}));
|
}));
|
||||||
|
build.onLoad({ filter: /\.tsx$/ }, (args) => {
|
||||||
|
if (args.path.includes("node_modules"))
|
||||||
|
return;
|
||||||
|
const source = readFileSync(args.path, "utf8");
|
||||||
|
if (!source.includes("server")) {
|
||||||
|
return { contents: source, loader: "tsx" };
|
||||||
|
}
|
||||||
|
const strippedCode = stripServerSideLogic({ txt_code: source });
|
||||||
|
return {
|
||||||
|
contents: strippedCode,
|
||||||
|
loader: "tsx",
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const artifactTracker = {
|
const artifactTracker = {
|
||||||
@ -47,7 +62,6 @@ export default async function allPagesBundler(params) {
|
|||||||
if (build_starts == MAX_BUILD_STARTS) {
|
if (build_starts == MAX_BUILD_STARTS) {
|
||||||
const error_msg = `Build Failed. Please check all your components and imports.`;
|
const error_msg = `Build Failed. Please check all your components and imports.`;
|
||||||
log.error(error_msg);
|
log.error(error_msg);
|
||||||
// process.exit(1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
build.onEnd((result) => {
|
build.onEnd((result) => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
type Params = {
|
type Params = {
|
||||||
page_local_path: string;
|
page_local_path: string;
|
||||||
};
|
};
|
||||||
export default function grabClientHydrationScript({ page_local_path }: Params): string;
|
export default function grabClientHydrationScript({ page_local_path, }: Params): Promise<string>;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@ -3,9 +3,11 @@ import path from "path";
|
|||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
import AppNames from "../../utils/grab-app-names";
|
import AppNames from "../../utils/grab-app-names";
|
||||||
import grabConstants from "../../utils/grab-constants";
|
import grabConstants from "../../utils/grab-constants";
|
||||||
|
import pagePathTransform from "../../utils/page-path-transform";
|
||||||
const { PAGES_DIR } = grabDirNames();
|
const { PAGES_DIR } = grabDirNames();
|
||||||
export default function grabClientHydrationScript({ page_local_path }) {
|
export default async function grabClientHydrationScript({ page_local_path, }) {
|
||||||
const { ClientRootElementIDName, ClientRootComponentWindowName, ClientWindowPagePropsName, } = grabConstants();
|
const { ClientRootElementIDName, ClientRootComponentWindowName, ClientWindowPagePropsName, } = grabConstants();
|
||||||
|
const target_path = pagePathTransform({ page_path: page_local_path });
|
||||||
const root_component_path = path.join(PAGES_DIR, `${AppNames["RootPagesComponentName"]}.tsx`);
|
const root_component_path = path.join(PAGES_DIR, `${AppNames["RootPagesComponentName"]}.tsx`);
|
||||||
const does_root_exist = existsSync(root_component_path);
|
const does_root_exist = existsSync(root_component_path);
|
||||||
let txt = ``;
|
let txt = ``;
|
||||||
@ -13,7 +15,7 @@ export default function grabClientHydrationScript({ page_local_path }) {
|
|||||||
if (does_root_exist) {
|
if (does_root_exist) {
|
||||||
txt += `import Root from "${root_component_path}";\n`;
|
txt += `import Root from "${root_component_path}";\n`;
|
||||||
}
|
}
|
||||||
txt += `import Page from "${page_local_path}";\n\n`;
|
txt += `import Page from "${target_path}";\n\n`;
|
||||||
txt += `const pageProps = window.${ClientWindowPagePropsName} || {};\n`;
|
txt += `const pageProps = window.${ClientWindowPagePropsName} || {};\n`;
|
||||||
if (does_root_exist) {
|
if (does_root_exist) {
|
||||||
txt += `const component = <Root suppressHydrationWarning={true} {...pageProps}><Page {...pageProps} /></Root>\n`;
|
txt += `const component = <Root suppressHydrationWarning={true} {...pageProps}><Page {...pageProps} /></Root>\n`;
|
||||||
|
|||||||
5
dist/functions/bundler/strip-server-side-logic.d.ts
vendored
Normal file
5
dist/functions/bundler/strip-server-side-logic.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
type Params = {
|
||||||
|
txt_code: string;
|
||||||
|
};
|
||||||
|
export default function stripServerSideLogic({ txt_code }: Params): string;
|
||||||
|
export {};
|
||||||
61
dist/functions/bundler/strip-server-side-logic.js
vendored
Normal file
61
dist/functions/bundler/strip-server-side-logic.js
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import ts from "typescript";
|
||||||
|
export default function stripServerSideLogic({ txt_code }) {
|
||||||
|
const sourceFile = ts.createSourceFile("temp.tsx", txt_code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
||||||
|
const transformer = (context) => {
|
||||||
|
return (rootNode) => {
|
||||||
|
const visitor = (node) => {
|
||||||
|
if (ts.isVariableStatement(node) &&
|
||||||
|
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
||||||
|
const isServerExport = node.declarationList.declarations.some((d) => ts.isIdentifier(d.name) &&
|
||||||
|
d.name.text === "server");
|
||||||
|
if (isServerExport)
|
||||||
|
return undefined; // Remove it
|
||||||
|
}
|
||||||
|
return ts.visitEachChild(node, visitor, context);
|
||||||
|
};
|
||||||
|
return ts.visitNode(rootNode, visitor);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const result = ts.transform(sourceFile, [transformer]);
|
||||||
|
const printer = ts.createPrinter();
|
||||||
|
const strippedCode = printer.printFile(result.transformed[0]);
|
||||||
|
const cleanSourceFile = ts.createSourceFile("clean.tsx", strippedCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
||||||
|
// Simple reference check: if a named import isn't found in the text, drop it
|
||||||
|
const cleanupTransformer = (context) => {
|
||||||
|
return (rootNode) => {
|
||||||
|
const visitor = (node) => {
|
||||||
|
if (ts.isImportDeclaration(node)) {
|
||||||
|
const clause = node.importClause;
|
||||||
|
if (!clause)
|
||||||
|
return node;
|
||||||
|
// Handle named imports like { BunextPageProps, BunextPageServerFn }
|
||||||
|
if (clause.namedBindings &&
|
||||||
|
ts.isNamedImports(clause.namedBindings)) {
|
||||||
|
const activeElements = clause.namedBindings.elements.filter((el) => {
|
||||||
|
const name = el.name.text;
|
||||||
|
// Check if the name appears anywhere else in the file
|
||||||
|
const regex = new RegExp(`\\b${name}\\b`, "g");
|
||||||
|
const matches = strippedCode.match(regex);
|
||||||
|
return matches && matches.length > 1; // 1 for the import itself, >1 for usage
|
||||||
|
});
|
||||||
|
if (activeElements.length === 0)
|
||||||
|
return undefined;
|
||||||
|
return ts.factory.updateImportDeclaration(node, node.modifiers, ts.factory.updateImportClause(clause, clause.isTypeOnly, clause.name, ts.factory.createNamedImports(activeElements)), node.moduleSpecifier, node.attributes);
|
||||||
|
}
|
||||||
|
// Handle default imports like 'import BunSQLite'
|
||||||
|
if (clause.name) {
|
||||||
|
const name = clause.name.text;
|
||||||
|
const regex = new RegExp(`\\b${name}\\b`, "g");
|
||||||
|
const matches = strippedCode.match(regex);
|
||||||
|
if (!matches || matches.length <= 1)
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ts.visitEachChild(node, visitor, context);
|
||||||
|
};
|
||||||
|
return ts.visitNode(rootNode, visitor);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const finalResult = ts.transform(cleanSourceFile, [cleanupTransformer]);
|
||||||
|
return printer.printFile(finalResult.transformed[0]);
|
||||||
|
}
|
||||||
2
dist/functions/bunext-init.d.ts
vendored
2
dist/functions/bunext-init.d.ts
vendored
@ -9,7 +9,7 @@ import { type FSWatcher } from "fs";
|
|||||||
declare global {
|
declare global {
|
||||||
var ORA_SPINNER: Ora;
|
var ORA_SPINNER: Ora;
|
||||||
var CONFIG: BunextConfig;
|
var CONFIG: BunextConfig;
|
||||||
var SERVER: Server | undefined;
|
var SERVER: Server<any> | undefined;
|
||||||
var RECOMPILING: boolean;
|
var RECOMPILING: boolean;
|
||||||
var WATCHER_TIMEOUT: any;
|
var WATCHER_TIMEOUT: any;
|
||||||
var ROUTER: FileSystemRouter;
|
var ROUTER: FileSystemRouter;
|
||||||
|
|||||||
2
dist/functions/server/start-server.d.ts
vendored
2
dist/functions/server/start-server.d.ts
vendored
@ -1 +1 @@
|
|||||||
export default function startServer(): Promise<import("bun").Server>;
|
export default function startServer(): Promise<Bun.Server<undefined>>;
|
||||||
|
|||||||
2
dist/functions/server/watcher.js
vendored
2
dist/functions/server/watcher.js
vendored
@ -3,6 +3,7 @@ import path from "path";
|
|||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
import rebuildBundler from "./rebuild-bundler";
|
import rebuildBundler from "./rebuild-bundler";
|
||||||
import { log } from "../../utils/log";
|
import { log } from "../../utils/log";
|
||||||
|
import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||||
const { ROOT_DIR } = grabDirNames();
|
const { ROOT_DIR } = grabDirNames();
|
||||||
export default async function watcher() {
|
export default async function watcher() {
|
||||||
await Bun.sleep(1000);
|
await Bun.sleep(1000);
|
||||||
@ -36,6 +37,7 @@ export default async function watcher() {
|
|||||||
if (global.RECOMPILING)
|
if (global.RECOMPILING)
|
||||||
return;
|
return;
|
||||||
global.RECOMPILING = true;
|
global.RECOMPILING = true;
|
||||||
|
await rewritePagesModule({ page_url: full_file_path });
|
||||||
await global.BUNDLER_CTX.rebuild();
|
await global.BUNDLER_CTX.rebuild();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,25 +1,16 @@
|
|||||||
import { jsx as _jsx } from "react/jsx-runtime";
|
import { jsx as _jsx } from "react/jsx-runtime";
|
||||||
import EJSON from "../../../utils/ejson";
|
import grabPageReactComponentString from "./grab-page-react-component-string";
|
||||||
import grabTsxStringModule from "./grab-tsx-string-module";
|
import grabTsxStringModule from "./grab-tsx-string-module";
|
||||||
export default async function grabPageBundledReactComponent({ file_path, root_file, server_res, }) {
|
export default async function grabPageBundledReactComponent({ file_path, root_file, server_res, }) {
|
||||||
try {
|
try {
|
||||||
let tsx = ``;
|
let tsx = grabPageReactComponentString({
|
||||||
const server_res_json = JSON.stringify(EJSON.stringify(server_res || {}) ?? "{}");
|
file_path,
|
||||||
if (root_file) {
|
root_file,
|
||||||
tsx += `import Root from "${root_file}"\n`;
|
server_res,
|
||||||
|
});
|
||||||
|
if (!tsx) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
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 += ` return (\n`;
|
|
||||||
if (root_file) {
|
|
||||||
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
|
|
||||||
}
|
|
||||||
tsx += ` )\n`;
|
|
||||||
tsx += `}\n`;
|
|
||||||
const mod = await grabTsxStringModule({ tsx, file_path });
|
const mod = await grabTsxStringModule({ tsx, file_path });
|
||||||
const Main = mod.default;
|
const Main = mod.default;
|
||||||
const component = _jsx(Main, {});
|
const component = _jsx(Main, {});
|
||||||
|
|||||||
7
dist/functions/server/web-pages/grab-page-react-component-string.d.ts
vendored
Normal file
7
dist/functions/server/web-pages/grab-page-react-component-string.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
type Params = {
|
||||||
|
file_path: string;
|
||||||
|
root_file?: string;
|
||||||
|
server_res?: any;
|
||||||
|
};
|
||||||
|
export default function grabPageReactComponentString({ file_path, root_file, server_res, }: Params): string | undefined;
|
||||||
|
export {};
|
||||||
28
dist/functions/server/web-pages/grab-page-react-component-string.js
vendored
Normal file
28
dist/functions/server/web-pages/grab-page-react-component-string.js
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import EJSON from "../../../utils/ejson";
|
||||||
|
import pagePathTransform from "../../../utils/page-path-transform";
|
||||||
|
export default function grabPageReactComponentString({ file_path, root_file, server_res, }) {
|
||||||
|
try {
|
||||||
|
const target_path = pagePathTransform({ page_path: file_path });
|
||||||
|
let tsx = ``;
|
||||||
|
const server_res_json = JSON.stringify(EJSON.stringify(server_res || {}) ?? "{}");
|
||||||
|
if (root_file) {
|
||||||
|
tsx += `import Root from "${root_file}"\n`;
|
||||||
|
}
|
||||||
|
tsx += `import Page from "${target_path}"\n`;
|
||||||
|
tsx += `export default function Main() {\n\n`;
|
||||||
|
tsx += `const props = JSON.parse(${server_res_json})\n\n`;
|
||||||
|
tsx += ` return (\n`;
|
||||||
|
if (root_file) {
|
||||||
|
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
|
||||||
|
}
|
||||||
|
tsx += ` )\n`;
|
||||||
|
tsx += `}\n`;
|
||||||
|
return tsx;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
dist/types/index.d.ts
vendored
5
dist/types/index.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
import type { MatchedRoute, ServeOptions, Server, WebSocketHandler } from "bun";
|
import type { MatchedRoute, ServeOptions, Server, WebSocketHandler } from "bun";
|
||||||
import type { FC, JSX, ReactNode } from "react";
|
import type { FC, JSX, PropsWithChildren, ReactNode } from "react";
|
||||||
export type ServerProps = {
|
export type ServerProps = {
|
||||||
params: Record<string, string>;
|
params: Record<string, string>;
|
||||||
searchParams: Record<string, string>;
|
searchParams: Record<string, string>;
|
||||||
@ -71,7 +71,7 @@ export type BunxRouteParams = {
|
|||||||
* Intercept and Transform the response object
|
* Intercept and Transform the response object
|
||||||
*/
|
*/
|
||||||
resTransform?: (res: Response) => Promise<Response> | Response;
|
resTransform?: (res: Response) => Promise<Response> | Response;
|
||||||
server?: Server;
|
server?: Server<any>;
|
||||||
};
|
};
|
||||||
export interface PostInsertReturn {
|
export interface PostInsertReturn {
|
||||||
fieldCount?: number;
|
fieldCount?: number;
|
||||||
@ -270,3 +270,4 @@ export type BunextCacheFileMeta = {
|
|||||||
paradigm: "html" | "json";
|
paradigm: "html" | "json";
|
||||||
expiry_seconds?: number;
|
expiry_seconds?: number;
|
||||||
};
|
};
|
||||||
|
export type BunextRootComponentProps = PropsWithChildren & BunextPageProps;
|
||||||
|
|||||||
2
dist/utils/grab-dir-names.d.ts
vendored
2
dist/utils/grab-dir-names.d.ts
vendored
@ -5,6 +5,7 @@ export default function grabDirNames(): {
|
|||||||
API_DIR: string;
|
API_DIR: string;
|
||||||
PUBLIC_DIR: string;
|
PUBLIC_DIR: string;
|
||||||
HYDRATION_DST_DIR: string;
|
HYDRATION_DST_DIR: string;
|
||||||
|
BUNX_CWD_DIR: string;
|
||||||
BUNX_ROOT_DIR: string;
|
BUNX_ROOT_DIR: string;
|
||||||
CONFIG_FILE: string;
|
CONFIG_FILE: string;
|
||||||
BUNX_TMP_DIR: string;
|
BUNX_TMP_DIR: string;
|
||||||
@ -18,4 +19,5 @@ export default function grabDirNames(): {
|
|||||||
HYDRATION_DST_DIR_MAP_JSON_FILE: string;
|
HYDRATION_DST_DIR_MAP_JSON_FILE: string;
|
||||||
BUNEXT_CACHE_DIR: string;
|
BUNEXT_CACHE_DIR: string;
|
||||||
BUNX_CWD_MODULE_CACHE_DIR: string;
|
BUNX_CWD_MODULE_CACHE_DIR: string;
|
||||||
|
BUNX_CWD_PAGES_REWRITE_DIR: string;
|
||||||
};
|
};
|
||||||
|
|||||||
3
dist/utils/grab-dir-names.js
vendored
3
dist/utils/grab-dir-names.js
vendored
@ -12,6 +12,7 @@ export default function grabDirNames() {
|
|||||||
const CONFIG_FILE = path.join(ROOT_DIR, "bunext.config.ts");
|
const CONFIG_FILE = path.join(ROOT_DIR, "bunext.config.ts");
|
||||||
const BUNX_CWD_DIR = path.resolve(ROOT_DIR, ".bunext");
|
const BUNX_CWD_DIR = path.resolve(ROOT_DIR, ".bunext");
|
||||||
const BUNX_CWD_MODULE_CACHE_DIR = path.resolve(BUNX_CWD_DIR, "module-cache");
|
const BUNX_CWD_MODULE_CACHE_DIR = path.resolve(BUNX_CWD_DIR, "module-cache");
|
||||||
|
const BUNX_CWD_PAGES_REWRITE_DIR = path.resolve(BUNX_CWD_DIR, "pages");
|
||||||
const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp");
|
const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp");
|
||||||
const BUNX_HYDRATION_SRC_DIR = path.resolve(BUNX_CWD_DIR, "client", "hydration-src");
|
const BUNX_HYDRATION_SRC_DIR = path.resolve(BUNX_CWD_DIR, "client", "hydration-src");
|
||||||
const BUNX_ROOT_DIR = path.resolve(__dirname, "../../");
|
const BUNX_ROOT_DIR = path.resolve(__dirname, "../../");
|
||||||
@ -28,6 +29,7 @@ export default function grabDirNames() {
|
|||||||
API_DIR,
|
API_DIR,
|
||||||
PUBLIC_DIR,
|
PUBLIC_DIR,
|
||||||
HYDRATION_DST_DIR,
|
HYDRATION_DST_DIR,
|
||||||
|
BUNX_CWD_DIR,
|
||||||
BUNX_ROOT_DIR,
|
BUNX_ROOT_DIR,
|
||||||
CONFIG_FILE,
|
CONFIG_FILE,
|
||||||
BUNX_TMP_DIR,
|
BUNX_TMP_DIR,
|
||||||
@ -41,5 +43,6 @@ export default function grabDirNames() {
|
|||||||
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
||||||
BUNEXT_CACHE_DIR,
|
BUNEXT_CACHE_DIR,
|
||||||
BUNX_CWD_MODULE_CACHE_DIR,
|
BUNX_CWD_MODULE_CACHE_DIR,
|
||||||
|
BUNX_CWD_PAGES_REWRITE_DIR,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
2
dist/utils/grab-router.d.ts
vendored
2
dist/utils/grab-router.d.ts
vendored
@ -1 +1 @@
|
|||||||
export default function grabRouter(): import("bun").FileSystemRouter;
|
export default function grabRouter(): Bun.FileSystemRouter;
|
||||||
|
|||||||
9
dist/utils/page-path-transform.d.ts
vendored
Normal file
9
dist/utils/page-path-transform.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type Params = {
|
||||||
|
page_path: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* # Transform a page path to the destination
|
||||||
|
* path in the .bunext directory
|
||||||
|
*/
|
||||||
|
export default function pagePathTransform({ page_path }: Params): string;
|
||||||
|
export {};
|
||||||
14
dist/utils/page-path-transform.js
vendored
Normal file
14
dist/utils/page-path-transform.js
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import path from "path";
|
||||||
|
import grabDirNames from "./grab-dir-names";
|
||||||
|
const { ROOT_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames();
|
||||||
|
/**
|
||||||
|
* # Transform a page path to the destination
|
||||||
|
* path in the .bunext directory
|
||||||
|
*/
|
||||||
|
export default function pagePathTransform({ page_path }) {
|
||||||
|
const page_path_relative_dir = page_path
|
||||||
|
.replace(ROOT_DIR, "")
|
||||||
|
.replace(/\/src\/pages/, "");
|
||||||
|
const target_path = path.join(BUNX_CWD_PAGES_REWRITE_DIR, page_path_relative_dir);
|
||||||
|
return target_path;
|
||||||
|
}
|
||||||
5
dist/utils/rewrite-pages-module.d.ts
vendored
Normal file
5
dist/utils/rewrite-pages-module.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
type Params = {
|
||||||
|
page_url?: string | string[];
|
||||||
|
};
|
||||||
|
export default function rewritePagesModule(params?: Params): Promise<void>;
|
||||||
|
export {};
|
||||||
25
dist/utils/rewrite-pages-module.js
vendored
Normal file
25
dist/utils/rewrite-pages-module.js
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import grabAllPages from "./grab-all-pages";
|
||||||
|
import pagePathTransform from "./page-path-transform";
|
||||||
|
import stripServerSideLogic from "../functions/bundler/strip-server-side-logic";
|
||||||
|
export default async function rewritePagesModule(params) {
|
||||||
|
const { page_url } = params || {};
|
||||||
|
let target_pages;
|
||||||
|
if (page_url) {
|
||||||
|
target_pages = Array.isArray(page_url) ? page_url : [page_url];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const pages = grabAllPages({ exclude_api: true });
|
||||||
|
target_pages = pages.map((p) => p.local_path);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < target_pages.length; i++) {
|
||||||
|
const page_path = target_pages[i];
|
||||||
|
const dst_path = pagePathTransform({ page_path });
|
||||||
|
const origin_page_content = await Bun.file(page_path).text();
|
||||||
|
const dst_page_content = stripServerSideLogic({
|
||||||
|
txt_code: origin_page_content,
|
||||||
|
});
|
||||||
|
await Bun.write(dst_path, dst_page_content, {
|
||||||
|
createPath: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
12
package.json
12
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "@moduletrace/bunext",
|
"name": "@moduletrace/bunext",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.9",
|
"version": "1.0.10",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
@ -51,6 +51,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/postcss": "^4.2.2",
|
"@tailwindcss/postcss": "^4.2.2",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "^24.10.0",
|
||||||
|
"@types/react": "^19.2.2",
|
||||||
|
"@types/react-dom": "^19.2.2",
|
||||||
"bun-plugin-tailwind": "^0.1.2",
|
"bun-plugin-tailwind": "^0.1.2",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"commander": "^14.0.2",
|
"commander": "^14.0.2",
|
||||||
@ -59,10 +63,6 @@
|
|||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"micromatch": "^4.0.8",
|
"micromatch": "^4.0.8",
|
||||||
"ora": "^9.0.0",
|
"ora": "^9.0.0",
|
||||||
"postcss": "^8.5.8",
|
"postcss": "^8.5.8"
|
||||||
"@types/node": "^24.10.0",
|
|
||||||
"@types/bun": "latest",
|
|
||||||
"@types/react": "^19.2.2",
|
|
||||||
"@types/react-dom": "^19.2.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import start from "./start";
|
|||||||
import dev from "./dev";
|
import dev from "./dev";
|
||||||
import build from "./build";
|
import build from "./build";
|
||||||
import { log } from "../utils/log";
|
import { log } from "../utils/log";
|
||||||
|
import rewritePages from "./rewrite-pages";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Describe Program
|
* # Describe Program
|
||||||
@ -20,6 +21,7 @@ program
|
|||||||
program.addCommand(dev());
|
program.addCommand(dev());
|
||||||
program.addCommand(start());
|
program.addCommand(start());
|
||||||
program.addCommand(build());
|
program.addCommand(build());
|
||||||
|
program.addCommand(rewritePages());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Handle Unavailable Commands
|
* # Handle Unavailable Commands
|
||||||
|
|||||||
20
src/commands/rewrite-pages/index.ts
Normal file
20
src/commands/rewrite-pages/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Command } from "commander";
|
||||||
|
import { log } from "../../utils/log";
|
||||||
|
import init from "../../functions/init";
|
||||||
|
import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return new Command("rewrite-pages")
|
||||||
|
.description("Rewrite pages from src to .bunext dir")
|
||||||
|
.action(async () => {
|
||||||
|
process.env.NODE_ENV = "production";
|
||||||
|
process.env.BUILD = "true";
|
||||||
|
|
||||||
|
await init();
|
||||||
|
|
||||||
|
log.banner();
|
||||||
|
log.build("Rewriting Pages ...");
|
||||||
|
|
||||||
|
await rewritePagesModule();
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { existsSync, statSync, writeFileSync } from "fs";
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
import * as esbuild from "esbuild";
|
import * as esbuild from "esbuild";
|
||||||
import grabAllPages from "../../utils/grab-all-pages";
|
import grabAllPages from "../../utils/grab-all-pages";
|
||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
@ -9,7 +9,7 @@ import { log } from "../../utils/log";
|
|||||||
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
|
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
|
||||||
import grabClientHydrationScript from "./grab-client-hydration-script";
|
import grabClientHydrationScript from "./grab-client-hydration-script";
|
||||||
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
|
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
|
||||||
import path from "path";
|
import stripServerSideLogic from "./strip-server-side-logic";
|
||||||
|
|
||||||
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE, ROOT_DIR } =
|
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE, ROOT_DIR } =
|
||||||
grabDirNames();
|
grabDirNames();
|
||||||
@ -32,10 +32,12 @@ export default async function allPagesBundler(params?: Params) {
|
|||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
const key = page.local_path;
|
const key = page.local_path;
|
||||||
|
|
||||||
const txt = grabClientHydrationScript({
|
const txt = await grabClientHydrationScript({
|
||||||
page_local_path: page.local_path,
|
page_local_path: page.local_path,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!txt) continue;
|
||||||
|
|
||||||
virtualEntries[key] = txt;
|
virtualEntries[key] = txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +54,23 @@ export default async function allPagesBundler(params?: Params) {
|
|||||||
loader: "tsx",
|
loader: "tsx",
|
||||||
resolveDir: process.cwd(),
|
resolveDir: process.cwd(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
build.onLoad({ filter: /\.tsx$/ }, (args) => {
|
||||||
|
if (args.path.includes("node_modules")) return;
|
||||||
|
|
||||||
|
const source = readFileSync(args.path, "utf8");
|
||||||
|
|
||||||
|
if (!source.includes("server")) {
|
||||||
|
return { contents: source, loader: "tsx" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const strippedCode = stripServerSideLogic({ txt_code: source });
|
||||||
|
|
||||||
|
return {
|
||||||
|
contents: strippedCode,
|
||||||
|
loader: "tsx",
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,7 +86,6 @@ export default async function allPagesBundler(params?: Params) {
|
|||||||
if (build_starts == MAX_BUILD_STARTS) {
|
if (build_starts == MAX_BUILD_STARTS) {
|
||||||
const error_msg = `Build Failed. Please check all your components and imports.`;
|
const error_msg = `Build Failed. Please check all your components and imports.`;
|
||||||
log.error(error_msg);
|
log.error(error_msg);
|
||||||
// process.exit(1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import path from "path";
|
|||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
import AppNames from "../../utils/grab-app-names";
|
import AppNames from "../../utils/grab-app-names";
|
||||||
import grabConstants from "../../utils/grab-constants";
|
import grabConstants from "../../utils/grab-constants";
|
||||||
|
import pagePathTransform from "../../utils/page-path-transform";
|
||||||
|
|
||||||
const { PAGES_DIR } = grabDirNames();
|
const { PAGES_DIR } = grabDirNames();
|
||||||
|
|
||||||
@ -10,13 +11,17 @@ type Params = {
|
|||||||
page_local_path: string;
|
page_local_path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function grabClientHydrationScript({ page_local_path }: Params) {
|
export default async function grabClientHydrationScript({
|
||||||
|
page_local_path,
|
||||||
|
}: Params) {
|
||||||
const {
|
const {
|
||||||
ClientRootElementIDName,
|
ClientRootElementIDName,
|
||||||
ClientRootComponentWindowName,
|
ClientRootComponentWindowName,
|
||||||
ClientWindowPagePropsName,
|
ClientWindowPagePropsName,
|
||||||
} = grabConstants();
|
} = grabConstants();
|
||||||
|
|
||||||
|
const target_path = pagePathTransform({ page_path: page_local_path });
|
||||||
|
|
||||||
const root_component_path = path.join(
|
const root_component_path = path.join(
|
||||||
PAGES_DIR,
|
PAGES_DIR,
|
||||||
`${AppNames["RootPagesComponentName"]}.tsx`,
|
`${AppNames["RootPagesComponentName"]}.tsx`,
|
||||||
@ -30,7 +35,7 @@ export default function grabClientHydrationScript({ page_local_path }: Params) {
|
|||||||
if (does_root_exist) {
|
if (does_root_exist) {
|
||||||
txt += `import Root from "${root_component_path}";\n`;
|
txt += `import Root from "${root_component_path}";\n`;
|
||||||
}
|
}
|
||||||
txt += `import Page from "${page_local_path}";\n\n`;
|
txt += `import Page from "${target_path}";\n\n`;
|
||||||
txt += `const pageProps = window.${ClientWindowPagePropsName} || {};\n`;
|
txt += `const pageProps = window.${ClientWindowPagePropsName} || {};\n`;
|
||||||
|
|
||||||
if (does_root_exist) {
|
if (does_root_exist) {
|
||||||
106
src/functions/bundler/strip-server-side-logic.tsx
Normal file
106
src/functions/bundler/strip-server-side-logic.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import ts from "typescript";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
txt_code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function stripServerSideLogic({ txt_code }: Params) {
|
||||||
|
const sourceFile = ts.createSourceFile(
|
||||||
|
"temp.tsx",
|
||||||
|
txt_code,
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.TSX,
|
||||||
|
);
|
||||||
|
|
||||||
|
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
|
||||||
|
return (rootNode) => {
|
||||||
|
const visitor = (node: ts.Node): ts.Node | undefined => {
|
||||||
|
if (
|
||||||
|
ts.isVariableStatement(node) &&
|
||||||
|
node.modifiers?.some(
|
||||||
|
(m) => m.kind === ts.SyntaxKind.ExportKeyword,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const isServerExport =
|
||||||
|
node.declarationList.declarations.some(
|
||||||
|
(d) =>
|
||||||
|
ts.isIdentifier(d.name) &&
|
||||||
|
d.name.text === "server",
|
||||||
|
);
|
||||||
|
if (isServerExport) return undefined; // Remove it
|
||||||
|
}
|
||||||
|
return ts.visitEachChild(node, visitor, context);
|
||||||
|
};
|
||||||
|
return ts.visitNode(rootNode, visitor) as ts.SourceFile;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = ts.transform(sourceFile, [transformer]);
|
||||||
|
const printer = ts.createPrinter();
|
||||||
|
const strippedCode = printer.printFile(result.transformed[0]);
|
||||||
|
|
||||||
|
const cleanSourceFile = ts.createSourceFile(
|
||||||
|
"clean.tsx",
|
||||||
|
strippedCode,
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.TSX,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simple reference check: if a named import isn't found in the text, drop it
|
||||||
|
const cleanupTransformer: ts.TransformerFactory<ts.SourceFile> = (
|
||||||
|
context,
|
||||||
|
) => {
|
||||||
|
return (rootNode) => {
|
||||||
|
const visitor = (node: ts.Node): ts.Node | undefined => {
|
||||||
|
if (ts.isImportDeclaration(node)) {
|
||||||
|
const clause = node.importClause;
|
||||||
|
if (!clause) return node;
|
||||||
|
|
||||||
|
// Handle named imports like { BunextPageProps, BunextPageServerFn }
|
||||||
|
if (
|
||||||
|
clause.namedBindings &&
|
||||||
|
ts.isNamedImports(clause.namedBindings)
|
||||||
|
) {
|
||||||
|
const activeElements =
|
||||||
|
clause.namedBindings.elements.filter((el) => {
|
||||||
|
const name = el.name.text;
|
||||||
|
// Check if the name appears anywhere else in the file
|
||||||
|
const regex = new RegExp(`\\b${name}\\b`, "g");
|
||||||
|
const matches = strippedCode.match(regex);
|
||||||
|
return matches && matches.length > 1; // 1 for the import itself, >1 for usage
|
||||||
|
});
|
||||||
|
|
||||||
|
if (activeElements.length === 0) return undefined;
|
||||||
|
return ts.factory.updateImportDeclaration(
|
||||||
|
node,
|
||||||
|
node.modifiers,
|
||||||
|
ts.factory.updateImportClause(
|
||||||
|
clause,
|
||||||
|
clause.isTypeOnly,
|
||||||
|
clause.name,
|
||||||
|
ts.factory.createNamedImports(activeElements),
|
||||||
|
),
|
||||||
|
node.moduleSpecifier,
|
||||||
|
node.attributes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle default imports like 'import BunSQLite'
|
||||||
|
if (clause.name) {
|
||||||
|
const name = clause.name.text;
|
||||||
|
const regex = new RegExp(`\\b${name}\\b`, "g");
|
||||||
|
const matches = strippedCode.match(regex);
|
||||||
|
if (!matches || matches.length <= 1) return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ts.visitEachChild(node, visitor, context);
|
||||||
|
};
|
||||||
|
return ts.visitNode(rootNode, visitor) as ts.SourceFile;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalResult = ts.transform(cleanSourceFile, [cleanupTransformer]);
|
||||||
|
return printer.printFile(finalResult.transformed[0]);
|
||||||
|
}
|
||||||
@ -24,7 +24,7 @@ import cron from "./server/cron";
|
|||||||
declare global {
|
declare global {
|
||||||
var ORA_SPINNER: Ora;
|
var ORA_SPINNER: Ora;
|
||||||
var CONFIG: BunextConfig;
|
var CONFIG: BunextConfig;
|
||||||
var SERVER: Server | undefined;
|
var SERVER: Server<any> | undefined;
|
||||||
var RECOMPILING: boolean;
|
var RECOMPILING: boolean;
|
||||||
var WATCHER_TIMEOUT: any;
|
var WATCHER_TIMEOUT: any;
|
||||||
var ROUTER: FileSystemRouter;
|
var ROUTER: FileSystemRouter;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import path from "path";
|
|||||||
import grabDirNames from "../../utils/grab-dir-names";
|
import grabDirNames from "../../utils/grab-dir-names";
|
||||||
import rebuildBundler from "./rebuild-bundler";
|
import rebuildBundler from "./rebuild-bundler";
|
||||||
import { log } from "../../utils/log";
|
import { log } from "../../utils/log";
|
||||||
|
import rewritePagesModule from "../../utils/rewrite-pages-module";
|
||||||
|
|
||||||
const { ROOT_DIR } = grabDirNames();
|
const { ROOT_DIR } = grabDirNames();
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ export default async function watcher() {
|
|||||||
if (filename.match(target_files_match) && global.BUNDLER_CTX) {
|
if (filename.match(target_files_match) && global.BUNDLER_CTX) {
|
||||||
if (global.RECOMPILING) return;
|
if (global.RECOMPILING) return;
|
||||||
global.RECOMPILING = true;
|
global.RECOMPILING = true;
|
||||||
|
await rewritePagesModule({ page_url: full_file_path });
|
||||||
await global.BUNDLER_CTX.rebuild();
|
await global.BUNDLER_CTX.rebuild();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { GrabPageReactBundledComponentRes } from "../../../types";
|
import type { GrabPageReactBundledComponentRes } from "../../../types";
|
||||||
import EJSON from "../../../utils/ejson";
|
import grabPageReactComponentString from "./grab-page-react-component-string";
|
||||||
import grabTsxStringModule from "./grab-tsx-string-module";
|
import grabTsxStringModule from "./grab-tsx-string-module";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@ -14,28 +14,16 @@ export default async function grabPageBundledReactComponent({
|
|||||||
server_res,
|
server_res,
|
||||||
}: Params): Promise<GrabPageReactBundledComponentRes | undefined> {
|
}: Params): Promise<GrabPageReactBundledComponentRes | undefined> {
|
||||||
try {
|
try {
|
||||||
let tsx = ``;
|
let tsx = grabPageReactComponentString({
|
||||||
|
file_path,
|
||||||
|
root_file,
|
||||||
|
server_res,
|
||||||
|
});
|
||||||
|
|
||||||
const server_res_json = JSON.stringify(
|
if (!tsx) {
|
||||||
EJSON.stringify(server_res || {}) ?? "{}",
|
return undefined;
|
||||||
);
|
|
||||||
|
|
||||||
if (root_file) {
|
|
||||||
tsx += `import Root from "${root_file}"\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 += ` return (\n`;
|
|
||||||
if (root_file) {
|
|
||||||
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
|
|
||||||
} else {
|
|
||||||
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
|
|
||||||
}
|
|
||||||
tsx += ` )\n`;
|
|
||||||
tsx += `}\n`;
|
|
||||||
|
|
||||||
const mod = await grabTsxStringModule({ tsx, file_path });
|
const mod = await grabTsxStringModule({ tsx, file_path });
|
||||||
const Main = mod.default;
|
const Main = mod.default;
|
||||||
const component = <Main />;
|
const component = <Main />;
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
import EJSON from "../../../utils/ejson";
|
||||||
|
import pagePathTransform from "../../../utils/page-path-transform";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
file_path: string;
|
||||||
|
root_file?: string;
|
||||||
|
server_res?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function grabPageReactComponentString({
|
||||||
|
file_path,
|
||||||
|
root_file,
|
||||||
|
server_res,
|
||||||
|
}: Params): string | undefined {
|
||||||
|
try {
|
||||||
|
const target_path = pagePathTransform({ page_path: file_path });
|
||||||
|
let tsx = ``;
|
||||||
|
|
||||||
|
const server_res_json = JSON.stringify(
|
||||||
|
EJSON.stringify(server_res || {}) ?? "{}",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (root_file) {
|
||||||
|
tsx += `import Root from "${root_file}"\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
tsx += `import Page from "${target_path}"\n`;
|
||||||
|
tsx += `export default function Main() {\n\n`;
|
||||||
|
tsx += `const props = JSON.parse(${server_res_json})\n\n`;
|
||||||
|
tsx += ` return (\n`;
|
||||||
|
if (root_file) {
|
||||||
|
tsx += ` <Root suppressHydrationWarning={true} {...props}><Page {...props} /></Root>\n`;
|
||||||
|
} else {
|
||||||
|
tsx += ` <Page suppressHydrationWarning={true} {...props} />\n`;
|
||||||
|
}
|
||||||
|
tsx += ` )\n`;
|
||||||
|
tsx += `}\n`;
|
||||||
|
|
||||||
|
return tsx;
|
||||||
|
} catch (error: any) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import type { MatchedRoute, ServeOptions, Server, WebSocketHandler } from "bun";
|
import type { MatchedRoute, ServeOptions, Server, WebSocketHandler } from "bun";
|
||||||
import type { FC, JSX, ReactNode } from "react";
|
import type { FC, JSX, PropsWithChildren, ReactNode } from "react";
|
||||||
|
|
||||||
export type ServerProps = {
|
export type ServerProps = {
|
||||||
params: Record<string, string>;
|
params: Record<string, string>;
|
||||||
@ -84,7 +84,7 @@ export type BunxRouteParams = {
|
|||||||
* Intercept and Transform the response object
|
* Intercept and Transform the response object
|
||||||
*/
|
*/
|
||||||
resTransform?: (res: Response) => Promise<Response> | Response;
|
resTransform?: (res: Response) => Promise<Response> | Response;
|
||||||
server?: Server;
|
server?: Server<any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PostInsertReturn {
|
export interface PostInsertReturn {
|
||||||
@ -293,3 +293,5 @@ export type BunextCacheFileMeta = {
|
|||||||
paradigm: "html" | "json";
|
paradigm: "html" | "json";
|
||||||
expiry_seconds?: number;
|
expiry_seconds?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BunextRootComponentProps = PropsWithChildren & BunextPageProps;
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export default function grabDirNames() {
|
|||||||
BUNX_CWD_DIR,
|
BUNX_CWD_DIR,
|
||||||
"module-cache",
|
"module-cache",
|
||||||
);
|
);
|
||||||
|
const BUNX_CWD_PAGES_REWRITE_DIR = path.resolve(BUNX_CWD_DIR, "pages");
|
||||||
const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp");
|
const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp");
|
||||||
const BUNX_HYDRATION_SRC_DIR = path.resolve(
|
const BUNX_HYDRATION_SRC_DIR = path.resolve(
|
||||||
BUNX_CWD_DIR,
|
BUNX_CWD_DIR,
|
||||||
@ -49,6 +50,7 @@ export default function grabDirNames() {
|
|||||||
API_DIR,
|
API_DIR,
|
||||||
PUBLIC_DIR,
|
PUBLIC_DIR,
|
||||||
HYDRATION_DST_DIR,
|
HYDRATION_DST_DIR,
|
||||||
|
BUNX_CWD_DIR,
|
||||||
BUNX_ROOT_DIR,
|
BUNX_ROOT_DIR,
|
||||||
CONFIG_FILE,
|
CONFIG_FILE,
|
||||||
BUNX_TMP_DIR,
|
BUNX_TMP_DIR,
|
||||||
@ -62,5 +64,6 @@ export default function grabDirNames() {
|
|||||||
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
||||||
BUNEXT_CACHE_DIR,
|
BUNEXT_CACHE_DIR,
|
||||||
BUNX_CWD_MODULE_CACHE_DIR,
|
BUNX_CWD_MODULE_CACHE_DIR,
|
||||||
|
BUNX_CWD_PAGES_REWRITE_DIR,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/utils/page-path-transform.ts
Normal file
24
src/utils/page-path-transform.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import path from "path";
|
||||||
|
import grabDirNames from "./grab-dir-names";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
page_path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { ROOT_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # Transform a page path to the destination
|
||||||
|
* path in the .bunext directory
|
||||||
|
*/
|
||||||
|
export default function pagePathTransform({ page_path }: Params) {
|
||||||
|
const page_path_relative_dir = page_path
|
||||||
|
.replace(ROOT_DIR, "")
|
||||||
|
.replace(/\/src\/pages/, "");
|
||||||
|
const target_path = path.join(
|
||||||
|
BUNX_CWD_PAGES_REWRITE_DIR,
|
||||||
|
page_path_relative_dir,
|
||||||
|
);
|
||||||
|
|
||||||
|
return target_path;
|
||||||
|
}
|
||||||
33
src/utils/rewrite-pages-module.ts
Normal file
33
src/utils/rewrite-pages-module.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import grabAllPages from "./grab-all-pages";
|
||||||
|
import pagePathTransform from "./page-path-transform";
|
||||||
|
import stripServerSideLogic from "../functions/bundler/strip-server-side-logic";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
page_url?: string | string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function rewritePagesModule(params?: Params) {
|
||||||
|
const { page_url } = params || {};
|
||||||
|
let target_pages: string[] | undefined;
|
||||||
|
|
||||||
|
if (page_url) {
|
||||||
|
target_pages = Array.isArray(page_url) ? page_url : [page_url];
|
||||||
|
} else {
|
||||||
|
const pages = grabAllPages({ exclude_api: true });
|
||||||
|
target_pages = pages.map((p) => p.local_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < target_pages.length; i++) {
|
||||||
|
const page_path = target_pages[i];
|
||||||
|
const dst_path = pagePathTransform({ page_path });
|
||||||
|
|
||||||
|
const origin_page_content = await Bun.file(page_path).text();
|
||||||
|
const dst_page_content = stripServerSideLogic({
|
||||||
|
txt_code: origin_page_content,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Bun.write(dst_path, dst_page_content, {
|
||||||
|
createPath: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user