at main 4.6 kB view raw
1#!/usr/bin/env bun 2import plugin from "bun-plugin-tailwind"; 3import { existsSync } from "fs"; 4import { rm, cp } from "fs/promises"; 5import path from "path"; 6 7if (process.argv.includes("--help") || process.argv.includes("-h")) { 8 console.log(` 9🏗️ Bun Build Script 10 11Usage: bun run build.ts [options] 12 13Common Options: 14 --outdir <path> Output directory (default: "dist") 15 --minify Enable minification (or --minify.whitespace, --minify.syntax, etc) 16 --sourcemap <type> Sourcemap type: none|linked|inline|external 17 --target <target> Build target: browser|bun|node 18 --format <format> Output format: esm|cjs|iife 19 --splitting Enable code splitting 20 --packages <type> Package handling: bundle|external 21 --public-path <path> Public path for assets 22 --env <mode> Environment handling: inline|disable|prefix* 23 --conditions <list> Package.json export conditions (comma separated) 24 --external <list> External packages (comma separated) 25 --banner <text> Add banner text to output 26 --footer <text> Add footer text to output 27 --define <obj> Define global constants (e.g. --define.VERSION=1.0.0) 28 --help, -h Show this help message 29 30Example: 31 bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom 32`); 33 process.exit(0); 34} 35 36const toCamelCase = (str: string): string => str.replace(/-([a-z])/g, g => g[1].toUpperCase()); 37 38const parseValue = (value: string): any => { 39 if (value === "true") return true; 40 if (value === "false") return false; 41 42 if (/^\d+$/.test(value)) return parseInt(value, 10); 43 if (/^\d*\.\d+$/.test(value)) return parseFloat(value); 44 45 if (value.includes(",")) return value.split(",").map(v => v.trim()); 46 47 return value; 48}; 49 50function parseArgs(): Partial<Bun.BuildConfig> { 51 const config: Partial<Bun.BuildConfig> = {}; 52 const args = process.argv.slice(2); 53 54 for (let i = 0; i < args.length; i++) { 55 const arg = args[i]; 56 if (arg === undefined) continue; 57 if (!arg.startsWith("--")) continue; 58 59 if (arg.startsWith("--no-")) { 60 const key = toCamelCase(arg.slice(5)); 61 config[key] = false; 62 continue; 63 } 64 65 if (!arg.includes("=") && (i === args.length - 1 || args[i + 1]?.startsWith("--"))) { 66 const key = toCamelCase(arg.slice(2)); 67 config[key] = true; 68 continue; 69 } 70 71 let key: string; 72 let value: string; 73 74 if (arg.includes("=")) { 75 [key, value] = arg.slice(2).split("=", 2) as [string, string]; 76 } else { 77 key = arg.slice(2); 78 value = args[++i] ?? ""; 79 } 80 81 key = toCamelCase(key); 82 83 if (key.includes(".")) { 84 const [parentKey, childKey] = key.split("."); 85 config[parentKey] = config[parentKey] || {}; 86 config[parentKey][childKey] = parseValue(value); 87 } else { 88 config[key] = parseValue(value); 89 } 90 } 91 92 return config; 93} 94 95const formatFileSize = (bytes: number): string => { 96 const units = ["B", "KB", "MB", "GB"]; 97 let size = bytes; 98 let unitIndex = 0; 99 100 while (size >= 1024 && unitIndex < units.length - 1) { 101 size /= 1024; 102 unitIndex++; 103 } 104 105 return `${size.toFixed(2)} ${units[unitIndex]}`; 106}; 107 108console.log("\n🚀 Starting build process...\n"); 109 110const cliConfig = parseArgs(); 111const outdir = cliConfig.outdir || path.join(process.cwd(), "dist"); 112 113if (existsSync(outdir)) { 114 console.log(`🗑️ Cleaning previous build at ${outdir}`); 115 await rm(outdir, { recursive: true, force: true }); 116} 117 118const start = performance.now(); 119 120const entrypoints = [...new Bun.Glob("**.html").scanSync("src")] 121 .map(a => path.resolve("src", a)) 122 .filter(dir => !dir.includes("node_modules")); 123console.log(`📄 Found ${entrypoints.length} HTML ${entrypoints.length === 1 ? "file" : "files"} to process\n`); 124 125const result = await Bun.build({ 126 entrypoints, 127 outdir, 128 plugins: [plugin], 129 minify: true, 130 target: "browser", 131 sourcemap: "linked", 132 define: { 133 "process.env.NODE_ENV": JSON.stringify("production"), 134 }, 135 ...cliConfig, 136}); 137 138const end = performance.now(); 139 140const outputTable = result.outputs.map(output => ({ 141 File: path.relative(process.cwd(), output.path), 142 Type: output.kind, 143 Size: formatFileSize(output.size), 144})); 145 146console.table(outputTable); 147const buildTime = (end - start).toFixed(2); 148 149// Copy public folder to dist 150const publicDir = path.join(process.cwd(), "public"); 151if (existsSync(publicDir)) { 152 console.log("📁 Copying public folder to dist..."); 153 await cp(publicDir, outdir, { recursive: true }); 154} 155 156console.log(`\n✅ Build completed in ${buildTime}ms\n`);