Update bundler. Handle non-existent file error.

This commit is contained in:
2026-03-21 16:35:30 +01:00
parent 632c70fc90
commit 4ee3876710
18 changed files with 194 additions and 200 deletions
-156
View File
@@ -1,156 +0,0 @@
#!/usr/bin/env bun
import type { BuildConfig } from "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<BuildConfig> {
const config: any = {};
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`);
+6 -3
View File
@@ -4,6 +4,7 @@ import { program } from "commander";
import start from "./start";
import dev from "./dev";
import build from "./build";
import { log } from "../utils/log";
/**
* # Describe Program
@@ -24,10 +25,12 @@ program.addCommand(build());
* # Handle Unavailable Commands
*/
program.on("command:*", () => {
console.error(
"Invalid command: %s\nSee --help for a list of available commands.",
program.args.join(" "),
log.error(
"Invalid command: %s\nSee --help for a list of available commands." +
" " +
program.args.join(" "),
);
process.exit(1);
});
+26 -3
View File
@@ -1,4 +1,4 @@
import { writeFileSync } from "fs";
import { existsSync, statSync, writeFileSync } from "fs";
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
@@ -9,8 +9,13 @@ import { log } from "../../utils/log";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabClientHydrationScript from "./grab-client-hydration-script";
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
import path from "path";
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE, ROOT_DIR } =
grabDirNames();
let build_starts = 0;
const MAX_BUILD_STARTS = 10;
type Params = {
watch?: boolean;
@@ -56,11 +61,27 @@ export default async function allPagesBundler(params?: Params) {
let buildStart = 0;
build.onStart(() => {
build_starts++;
buildStart = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
// process.exit(1);
}
});
build.onEnd((result) => {
if (result.errors.length > 0) return;
if (result.errors.length > 0) {
for (const error of result.errors) {
const loc = error.location;
const location = loc
? ` ${loc.file}:${loc.line}:${loc.column}`
: "";
log.error(`[Build]${location} ${error.text}`);
}
return;
}
const artifacts = grabArtifactsFromBundledResults({
pages,
@@ -86,6 +107,8 @@ export default async function allPagesBundler(params?: Params) {
if (params?.exit_after_first_build) {
process.exit();
}
build_starts = 0;
});
},
};
+16 -4
View File
@@ -1,4 +1,4 @@
import { watch, existsSync } from "fs";
import { watch, existsSync, statSync } from "fs";
import path from "path";
import grabDirNames from "../../utils/grab-dir-names";
import rebuildBundler from "./rebuild-bundler";
@@ -6,7 +6,9 @@ import { log } from "../../utils/log";
const { ROOT_DIR } = grabDirNames();
export default function watcher() {
export default async function watcher() {
await Bun.sleep(1000);
const pages_src_watcher = watch(
ROOT_DIR,
{
@@ -16,6 +18,17 @@ export default function watcher() {
async (event, filename) => {
if (!filename) return;
const full_file_path = path.join(ROOT_DIR, filename);
if (full_file_path.match(/\/styles$/)) {
global.RECOMPILING = true;
await Bun.sleep(1000);
await fullRebuild({
msg: `Detected new \`styles\` directory. Rebuilding ...`,
});
return;
}
const excluded_match =
/node_modules\/|^public\/|^\.bunext\/|^\.git\/|^dist\/|bun\.lockb$/;
@@ -52,8 +65,7 @@ export default function watcher() {
if (global.RECOMPILING) return;
const fullPath = path.join(ROOT_DIR, filename);
const action = existsSync(fullPath) ? "created" : "deleted";
const action = existsSync(full_file_path) ? "created" : "deleted";
const type = filename.match(/\.css$/) ? "Sylesheet" : "Page";
await fullRebuild({
@@ -58,7 +58,7 @@ export default async function grabPageComponent({
if (!file_path) {
const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`;
// console.error(errMsg);
// log.error(errMsg);
throw new Error(errMsg);
}
@@ -68,7 +68,7 @@ export default async function grabPageComponent({
if (!bundledMap?.path) {
const errMsg = `No Bundled File Path for this request path!`;
console.error(errMsg);
log.error(errMsg);
throw new Error(errMsg);
}
@@ -172,7 +172,7 @@ export default async function grabPageComponent({
head: Head,
};
} catch (error: any) {
console.error(`Error Grabbing Page Component: ${error.message}`);
log.error(`Error Grabbing Page Component: ${error.message}`);
return await grabPageErrorComponent({
error,
@@ -36,14 +36,18 @@ export default async function (params?: Params) {
script += ` try {\n`;
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
script += ` const data = JSON.parse(event.data);\n`;
// script += ` console.log("data", data);\n`;
script += ` const oldCSSLink = document.querySelector('link[rel="stylesheet"]');\n`;
script += ` if (data.target_map.css_path) {\n`;
script += ` const oldLink = document.querySelector('link[rel="stylesheet"]');\n`;
script += ` const newLink = document.createElement("link");\n`;
script += ` newLink.rel = "stylesheet";\n`;
script += ` newLink.href = \`/\${data.target_map.css_path}?t=\${Date.now()}\`;\n`;
script += ` newLink.onload = () => oldLink?.remove();\n`;
script += ` newLink.onload = () => oldCSSLink?.remove();\n`;
script += ` document.head.appendChild(newLink);\n`;
script += ` } else if (oldCSSLink) {\n`;
script += ` oldCSSLink.remove();\n`;
script += ` }\n`;
script += ` const newScriptPath = \`/\${data.target_map.path}?t=\${Date.now()}\`;\n\n`;
@@ -1,4 +1,5 @@
import isDevelopment from "../../../utils/is-development";
import { log } from "../../../utils/log";
import getCache from "../../cache/get-cache";
import generateWebPageResponseFromComponentReturn from "./generate-web-page-response-from-component-return";
import grabPageComponent from "./grab-page-component";
@@ -38,7 +39,7 @@ export default async function handleWebPages({
...componentRes,
});
} catch (error: any) {
console.error(`Error Handling Web Page: ${error.message}`);
log.error(`Error Handling Web Page: ${error.message}`);
const componentRes = await grabPageErrorComponent({
error,
@@ -2,19 +2,59 @@ import * as esbuild from "esbuild";
import postcss from "postcss";
import tailwindcss from "@tailwindcss/postcss";
import { readFile } from "fs/promises";
import path from "path";
import { existsSync } from "fs";
import grabDirNames from "../../../utils/grab-dir-names";
import { log } from "../../../utils/log";
const { ROOT_DIR } = grabDirNames();
let error_logged = false;
const tailwindEsbuildPlugin: esbuild.Plugin = {
name: "tailwindcss",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const source = await readFile(args.path, "utf-8");
const result = await postcss([tailwindcss()]).process(source, {
from: args.path,
});
try {
const source = await readFile(args.path, "utf-8");
const result = await postcss([tailwindcss()]).process(source, {
from: args.path,
});
error_logged = false;
return { contents: result.css, loader: "css" };
} catch (error: any) {
return { errors: [{ text: error.message }] };
}
});
build.onResolve({ filter: /\.css$/ }, async (args) => {
const css_path = path.resolve(
args.resolveDir,
args.path.replace(/\@\//g, ROOT_DIR + "/"),
);
const does_path_exist = existsSync(css_path);
if (!does_path_exist && !error_logged) {
const err_msg = `CSS Error: ${css_path} file does not exist.`;
log.error(err_msg);
error_logged = true;
// return {
// errors: [
// {
// text: err_msg,
// },
// ],
// pluginName: "tailwindcss",
// };
}
return {
contents: result.css,
loader: "css",
path: css_path,
};
});
},