From ca9d6a75ffca829559a30df37fce03210730b316 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Mon, 16 Mar 2026 04:33:33 +0100 Subject: [PATCH] Updates --- bun.lock | 31 ++++ package.json | 1 + src/build/build.ts | 155 ++++++++++++++++++ src/functions/bundler/all-pages-bundler.ts | 20 +++ src/functions/server/watcher.tsx | 3 +- .../server/web-pages/grab-page-component.tsx | 9 +- .../server/web-pages/handle-web-pages.tsx | 18 +- .../write-web-page-hydration-script.tsx | 12 +- src/utils/bundle.ts | 1 + 9 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 src/build/build.ts diff --git a/bun.lock b/bun.lock index 0104663..2e6a85a 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,7 @@ "": { "name": "bun-next", "dependencies": { + "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", "commander": "^14.0.2", "micromatch": "^4.0.8", @@ -19,11 +20,37 @@ "react-dom": "^19.2.4", }, "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", "typescript": "^5.0.0", }, }, }, "packages": { + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PXgg5gqcS/rHwa1hF0JdM1y5TiyejVrMHoBmWY/DjtfYZoFTXie1RCFOkoG0b5diOOmUcuYarMpH7CSNTqwj+w=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-Nhssuh7GBpP5PiDSOl3+qnoIG7PJo+ec2oomDevnl9pRY6x6aD2gRt0JE+uf+A8Om2D6gjeHCxjEdrw5ZHE8mA=="], + + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-w1gaTlqU0IJCmJ1X+PGHkdNU1n8Gemx5YKkjhkJIguvFINXEBB5U1KG82QsT65Tk4KyNMfbLTlmy4giAvUoKfA=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-OUgPHfL6+PM2Q+tFZjcaycN3D7gdQdYlWnwMI31DXZKY1r4HINWk9aEz9t/rNaHg65edwNrt7dsv9TF7xK8xIA=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ui5pAgM7JE9MzHokF0VglRMkbak3lTisY4Mf1AZutPACXWgKJC5aGrgnHBfkl7QS6fEeYb0juy1q4eRznRHOsw=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-bzUgYj/PIZziB/ZesIP9HUyfvh6Vlf3od+TrbTTyVEuCSMKzDPQVW/yEbRp0tcHO3alwiEXwJDrWrHAguXlgiQ=="], + + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-oqvMDYpX6dGJO03HgO5bXuccEsH3qbdO3MaAiAlO4CfkBPLUXz3N0DDElg5hz0L6ktdDVKbQVE5lfe+LAUISQg=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-poVXvOShekbexHq45b4MH/mRjQKwACAC8lHp3Tz/hEDuz0/20oncqScnmKwzhBPEpqJvydXficXfBYuSim8opw=="], + + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.10", "", { "os": "linux", "cpu": "x64" }, "sha512-/hOZ6S1VsTX6vtbhWVL9aAnOrdpuO54mAGUWpTdMz7dFG5UBZ/VUEiK0pBkq9A1rlBk0GeD/6Y4NBFl8Ha7cRA=="], + + "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.3.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-GXbz2swvN2DLw2dXZFeedMxSJtI64xQ9xp9Eg7Hjejg6mS2E4dP1xoQ2yAo2aZPi/2OBPAVaGzppI2q20XumHA=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.10", "", { "os": "win32", "cpu": "x64" }, "sha512-qaS1In3yfC/Z/IGQriVmF8GWwKuNqiw7feTSJWaQhH5IbL6ENR+4wGNPniZSJFaM/SKUO0e/YCRdoVBvgU4C1g=="], + + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.10", "", { "os": "win32", "cpu": "x64" }, "sha512-gh3UAHbUdDUG6fhLc1Csa4IGdtghue6U8oAIXWnUqawp6lwb3gOCRvp25IUnLF5vUHtgfMxuEUYV7YA2WxVutw=="], + "@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=="], @@ -42,6 +69,10 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "bun": ["bun@1.3.10", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.10", "@oven/bun-darwin-x64": "1.3.10", "@oven/bun-darwin-x64-baseline": "1.3.10", "@oven/bun-linux-aarch64": "1.3.10", "@oven/bun-linux-aarch64-musl": "1.3.10", "@oven/bun-linux-x64": "1.3.10", "@oven/bun-linux-x64-baseline": "1.3.10", "@oven/bun-linux-x64-musl": "1.3.10", "@oven/bun-linux-x64-musl-baseline": "1.3.10", "@oven/bun-windows-aarch64": "1.3.10", "@oven/bun-windows-x64": "1.3.10", "@oven/bun-windows-x64-baseline": "1.3.10" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-S/CXaXXIyA4CMjdMkYQ4T2YMqnAn4s0ysD3mlsY4bUiOCqGlv28zck4Wd4H4kpvbekx15S9mUeLQ7Uxd0tYTLA=="], + + "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=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], diff --git a/package.json b/package.json index 6d9230c..85e3652 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react-dom": "^19.0.0" }, "dependencies": { + "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", "commander": "^14.0.2", "micromatch": "^4.0.8", diff --git a/src/build/build.ts b/src/build/build.ts new file mode 100644 index 0000000..f7cb11d --- /dev/null +++ b/src/build/build.ts @@ -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 Output directory (default: "dist") + --minify Enable minification (or --minify.whitespace, --minify.syntax, etc) + --sourcemap Sourcemap type: none|linked|inline|external + --target Build target: browser|bun|node + --format Output format: esm|cjs|iife + --splitting Enable code splitting + --packages Package handling: bundle|external + --public-path Public path for assets + --env Environment handling: inline|disable|prefix* + --conditions Package.json export conditions (comma separated) + --external External packages (comma separated) + --banner Add banner text to output + --footer Add footer text to output + --define 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 { + const config: Partial = {}; + 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`); diff --git a/src/functions/bundler/all-pages-bundler.ts b/src/functions/bundler/all-pages-bundler.ts index b21ddfb..53afacd 100644 --- a/src/functions/bundler/all-pages-bundler.ts +++ b/src/functions/bundler/all-pages-bundler.ts @@ -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"); } diff --git a/src/functions/server/watcher.tsx b/src/functions/server/watcher.tsx index fa14e7b..eb1be9b 100644 --- a/src/functions/server/watcher.tsx +++ b/src/functions/server/watcher.tsx @@ -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(); diff --git a/src/functions/server/web-pages/grab-page-component.tsx b/src/functions/server/web-pages/grab-page-component.tsx index 2f4128a..46a4aa3 100644 --- a/src/functions/server/web-pages/grab-page-component.tsx +++ b/src/functions/server/web-pages/grab-page-component.tsx @@ -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; const component = ; diff --git a/src/functions/server/web-pages/handle-web-pages.tsx b/src/functions/server/web-pages/handle-web-pages.tsx index 2a9fe99..13bef52 100644 --- a/src/functions/server/web-pages/handle-web-pages.tsx +++ b/src/functions/server/web-pages/handle-web-pages.tsx @@ -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 { // 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, diff --git a/src/functions/server/web-pages/write-web-page-hydration-script.tsx b/src/functions/server/web-pages/write-web-page-hydration-script.tsx index 66db5f5..8158f1c 100644 --- a/src/functions/server/web-pages/write-web-page-hydration-script.tsx +++ b/src/functions/server/web-pages/write-web-page-hydration-script.tsx @@ -27,9 +27,10 @@ export default async function (params: PageDistGenParams) { script += `}\n`; script += `let root: any = null;\n\n`; + script += `const component = ;\n\n`; script += `const container = document.getElementById("${ClientRootElementIDName}");\n\n`; script += `if (container) {\n`; - script += ` root = hydrateRoot(container, );\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!, );\n`; - script += ` root.render();\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`; } diff --git a/src/utils/bundle.ts b/src/utils/bundle.ts index 59ade2c..53d4670 100644 --- a/src/utils/bundle.ts +++ b/src/utils/bundle.ts @@ -1,3 +1,4 @@ +import plugin from "bun-plugin-tailwind"; import { execSync, type ExecSyncOptions } from "child_process"; type Params = {