This commit is contained in:
2026-03-16 04:33:33 +01:00
parent 075e2f88d6
commit ca9d6a75ff
9 changed files with 239 additions and 11 deletions
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env bun
import plugin from "bun-plugin-tailwind";
import { existsSync } from "fs";
import { rm } from "fs/promises";
import path from "path";
if (process.argv.includes("--help") || process.argv.includes("-h")) {
console.log(`
🏗️ Bun Build Script
Usage: bun run build.ts [options]
Common Options:
--outdir <path> Output directory (default: "dist")
--minify Enable minification (or --minify.whitespace, --minify.syntax, etc)
--sourcemap <type> Sourcemap type: none|linked|inline|external
--target <target> Build target: browser|bun|node
--format <format> Output format: esm|cjs|iife
--splitting Enable code splitting
--packages <type> Package handling: bundle|external
--public-path <path> Public path for assets
--env <mode> Environment handling: inline|disable|prefix*
--conditions <list> Package.json export conditions (comma separated)
--external <list> External packages (comma separated)
--banner <text> Add banner text to output
--footer <text> Add footer text to output
--define <obj> Define global constants (e.g. --define.VERSION=1.0.0)
--help, -h Show this help message
Example:
bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom
`);
process.exit(0);
}
const toCamelCase = (str: string): string =>
str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const parseValue = (value: string): any => {
if (value === "true") return true;
if (value === "false") return false;
if (/^\d+$/.test(value)) return parseInt(value, 10);
if (/^\d*\.\d+$/.test(value)) return parseFloat(value);
if (value.includes(",")) return value.split(",").map((v) => v.trim());
return value;
};
function parseArgs(): Partial<Bun.BuildConfig> {
const config: Partial<Bun.BuildConfig> = {};
const args = process.argv.slice(2);
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === undefined) continue;
if (!arg.startsWith("--")) continue;
if (arg.startsWith("--no-")) {
const key = toCamelCase(arg.slice(5));
config[key] = false;
continue;
}
if (
!arg.includes("=") &&
(i === args.length - 1 || args[i + 1]?.startsWith("--"))
) {
const key = toCamelCase(arg.slice(2));
config[key] = true;
continue;
}
let key: string;
let value: string;
if (arg.includes("=")) {
[key, value] = arg.slice(2).split("=", 2) as [string, string];
} else {
key = arg.slice(2);
value = args[++i] ?? "";
}
key = toCamelCase(key);
if (key.includes(".")) {
const [parentKey, childKey] = key.split(".");
config[parentKey] = config[parentKey] || {};
config[parentKey][childKey] = parseValue(value);
} else {
config[key] = parseValue(value);
}
}
return config;
}
const formatFileSize = (bytes: number): string => {
const units = ["B", "KB", "MB", "GB"];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
};
console.log("\n🚀 Starting build process...\n");
const cliConfig = parseArgs();
const outdir = cliConfig.outdir || path.join(process.cwd(), "dist");
if (existsSync(outdir)) {
console.log(`🗑️ Cleaning previous build at ${outdir}`);
await rm(outdir, { recursive: true, force: true });
}
const start = performance.now();
const entrypoints = [...new Bun.Glob("**.html").scanSync("src/app")]
.map((a) => path.resolve("src/app", a))
.filter((dir) => !dir.includes("node_modules"));
console.log(
`📄 Found ${entrypoints.length} HTML ${entrypoints.length === 1 ? "file" : "files"} to process\n`,
);
const result = await Bun.build({
entrypoints,
outdir,
plugins: [plugin],
minify: true,
target: "browser",
sourcemap: "linked",
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
},
...cliConfig,
});
const end = performance.now();
const outputTable = result.outputs.map((output) => ({
File: path.relative(process.cwd(), output.path),
Type: output.kind,
Size: formatFileSize(output.size),
}));
console.table(outputTable);
const buildTime = (end - start).toFixed(2);
console.log(`\n✅ Build completed in ${buildTime}ms\n`);
@@ -1,3 +1,4 @@
import plugin from "bun-plugin-tailwind";
import { readdirSync, statSync, unlinkSync } from "fs";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
@@ -7,6 +8,7 @@ import path from "path";
import bundle from "../../utils/bundle";
import AppNames from "../../utils/grab-app-names";
import type { PageFiles } from "../../types";
import isDevelopment from "../../utils/is-development";
const { BUNX_HYDRATION_SRC_DIR, HYDRATION_DST_DIR } = grabDirNames();
@@ -65,6 +67,24 @@ export default async function allPagesBundler() {
exec_options: { stdio: "ignore" },
});
// console.log(`Bundling ...`);
// const result = await Bun.build({
// entrypoints,
// outdir: HYDRATION_DST_DIR,
// plugins: [plugin],
// minify: true,
// target: "browser",
// // sourcemap: "linked",
// define: {
// "process.env.NODE_ENV": JSON.stringify(
// isDevelopment() ? "development" : "production",
// ),
// },
// });
// console.log("result", result);
console.timeEnd("build");
}
+2 -1
View File
@@ -27,8 +27,9 @@ export default function watcher() {
try {
global.RECOMPILING = true;
console.log(`File Changed. Rebuilding ...`);
await allPagesBundler();
reloadServer();
global.LAST_BUILD_TIME = Date.now();
@@ -4,7 +4,6 @@ import grabPageName from "../../../utils/grab-page-name";
import grabRouteParams from "../../../utils/grab-route-params";
import grabRouter from "../../../utils/grab-router";
import type { BunextPageModule, GrabPageComponentRes } from "../../../types";
import bundle from "../../../utils/bundle";
import path from "path";
import AppNames from "../../../utils/grab-app-names";
import { existsSync } from "fs";
@@ -110,9 +109,11 @@ export default async function grabPageComponent({
// });
// }
const module: BunextPageModule = await import(
`${filePath}?t=${global.LAST_BUILD_TIME ?? 0}`
);
const module: BunextPageModule = await import(filePath);
// const module: BunextPageModule = await import(
// `${filePath}?t=${global.LAST_BUILD_TIME ?? 0}`
// );
const Component = module.default as FC<any>;
const component = <Component />;
@@ -1,3 +1,4 @@
import isDevelopment from "../../../utils/is-development";
import genWebHTML from "./generate-web-html";
import grabPageComponent from "./grab-page-component";
@@ -24,11 +25,24 @@ export default async function ({ req }: Params): Promise<Response> {
// pageProps: serverRes,
// });
return new Response(html, {
const res_opts: ResponseInit = {
headers: {
"Content-Type": "text/html",
},
});
};
if (isDevelopment()) {
res_opts.headers = {
...res_opts.headers,
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
Expires: "0",
};
}
const res = new Response(html, res_opts);
return res;
} catch (error: any) {
return new Response(error.message || `Page Not Found`, {
status: 404,
@@ -27,9 +27,10 @@ export default async function (params: PageDistGenParams) {
script += `}\n`;
script += `let root: any = null;\n\n`;
script += `const component = <App {...window.${ClientWindowPagePropsName}} />;\n\n`;
script += `const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
script += `if (container) {\n`;
script += ` root = hydrateRoot(container, <App {...window.${ClientWindowPagePropsName}} />);\n`;
script += ` root = hydrateRoot(container, component);\n`;
script += `}\n\n`;
if (isDevelopment()) {
script += `const hmr = new EventSource("/__hmr");\n`;
@@ -37,12 +38,15 @@ export default async function (params: PageDistGenParams) {
// script += ` console.log(\`HMR even received:\`, event);\n`;
script += ` if (event.data && root) {\n`;
script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`;
// script += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`;
// script += ` root.render(module.default);\n`;
// script += ` })\n`;
// script += ` console.log("root", root);\n`;
// script += ` root.unmount();\n`;
// script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`;
// script += ` root = hydrateRoot(container!, <App {...window.${ClientWindowPagePropsName}} />);\n`;
script += ` root.render(<App {...window.${ClientWindowPagePropsName}} />);\n`;
// script += ` window.location.reload();\n`;
// script += ` root = hydrateRoot(container!, component);\n`;
// script += ` root.render(component);\n`;
script += ` window.location.reload();\n`;
script += ` }\n`;
script += ` });\n`;
}
+1
View File
@@ -1,3 +1,4 @@
import plugin from "bun-plugin-tailwind";
import { execSync, type ExecSyncOptions } from "child_process";
type Params = {