# 字节码缓存 Source: https://bun.zhcndoc.com/bundler/bytecode 使用 Bun 打包器中的字节码缓存加速 JavaScript 执行 字节码缓存是一种构建时优化,通过预先将 JavaScript 编译为字节码来缩短启动时间。例如,启用字节码后编译 TypeScript 的 `tsc`,启动时间可提升 **2 倍**。 ## 使用方法 ### 基本用法(CommonJS) 使用 `--bytecode` 标志启用字节码缓存。不使用 `--format` 时,输出格式默认为 CommonJS: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --target=bun --bytecode --outdir=./dist ``` 构建会写入两个文件: * `dist/index.js` - 打包后的 JavaScript(CommonJS) * `dist/index.js.jsc` - 字节码缓存文件 运行时,Bun 会自动检测并使用 `.jsc` 文件: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./dist/index.js # 自动使用 index.js.jsc ``` ### 生成独立可执行文件时 使用 `--compile` 创建可执行文件时,Bun 会将字节码嵌入二进制文件中。ESM 和 CommonJS 均可使用: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # ESM(需要 --compile) bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli # CommonJS(使用或不使用 --compile 均可) bun build ./cli.ts --compile --bytecode --outfile=mycli ``` 生成的可执行文件同时包含代码和字节码。 ### ESM 字节码 ESM 字节码需要使用 `--compile`,因为 Bun 会将模块元数据(导入/导出信息)嵌入编译后的二进制文件中。借助这些元数据,JavaScript 引擎可以在运行时完全跳过解析。 如果不使用 `--compile`,ESM 字节码仍需要解析源代码以分析模块依赖,这会违背字节码缓存的目的。 ### 与其他优化结合 将字节码与压缩和源映射结合使用: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli ``` * `--minify` 在生成字节码前压缩代码体积(代码更少,字节码更小) * `--sourcemap` 保持错误定位(错误仍指向原始源码) * `--bytecode` 消除解析开销。 ## 性能影响 性能提升与代码体量成正比: | 应用大小 | 典型启动速度提升 | | ------------------ | --------- | | 小型 CLI (\< 100 KB) | 快 1.5-2 倍 | | 中大型应用 (> 5 MB) | 快 2.5-4 倍 | 体量越大,受益越明显,因为解析的代码更多。 ## 何时使用字节码 ### 非常适合: #### CLI 工具 * 经常调用(如 linter、格式化工具、git 钩子) * 启动时间即全部用户体验 * 用户能明显感受到 90ms 与 45ms 启动时间的差异 * 例子:TypeScript 编译器、Prettier、ESLint #### 构建工具和任务运行器 * 在开发过程中执行数百次甚至数千次 * 每次节省数毫秒,累计提升明显 * 改善开发者体验 * 例子:构建脚本、测试运行器、代码生成器 #### 独立可执行文件 * 发布给重视性能的用户 * 单文件分发方便 * 文件体积不如启动速度重要 * 例子:通过 npm 或二进制发布的 CLI ### 不适合: * ❌ **小型脚本** * ❌ **只运行一次的代码** * ❌ **开发构建** * ❌ **受大小限制的环境** ## 限制 ### 仅支持 CommonJS 字节码缓存目前仅支持 CommonJS 输出格式。Bun 打包器会自动将大部分 ESM 代码转换为 CommonJS,但 **顶层 await** 是例外: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} // 会阻止字节码缓存 const data = await fetch("https://api.example.com"); export default data; ``` **原因**:顶层 await 需要异步模块评估,这无法用 CommonJS 表示。模块图异步化,CommonJS 包装函数模型失效。 **解决方案**:将异步初始化放入函数: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} async function init() { const data = await fetch("https://api.example.com"); return data; } export default init; ``` 现在模块导出的是函数,由调用方根据需要使用 `await`。 ### 版本兼容性 字节码**不跨 Bun 版本通用**。字节码格式绑定于 JavaScriptCore 内部表示,不同版本会改变。 更新 Bun 后必须重新生成字节码: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # 更新 Bun 后 bun build --bytecode ./index.ts --outdir=./dist ``` 如果字节码与当前 Bun 版本不匹配,Bun 会忽略它,并回退到解析 JavaScript 源码。 **最佳实践**:将字节码生成纳入 CI/CD 构建流程,不要将 `.jsc` 文件提交到 Git。更新 Bun 时重新生成字节码。 ### 仍需源码文件 字节码不会替代 JavaScript。你必须同时部署两个文件: * `.js` 文件(打包后的源代码) * `.jsc` 文件(字节码缓存) 运行时流程: 1. Bun 加载 `.js` 文件,发现 `@bytecode` 指令,检查 `.jsc` 文件 2. Bun 加载 `.jsc` 3. Bun 验证字节码哈希与源码匹配 4. 验证通过则使用字节码 5. 否则回退为解析源码 ### 字节码不是混淆手段 字节码**不会隐藏你的源代码**。它是性能优化,而非安全措施。 ## 生产部署 ### Docker 在 Dockerfile 中集成字节码生成: ```dockerfile Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}} FROM oven/bun:1 AS builder WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . RUN bun build --bytecode --minify --sourcemap \ --target=bun \ --compile \ ./src/server.ts --outfile=./dist/server FROM oven/bun:1 AS runner WORKDIR /app COPY --from=builder /app/dist/server /app/server CMD ["./server"] ``` 字节码文件与架构无关。 ### CI/CD 在构建流水线中生成字节码: ```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} # GitHub Actions - name: Build with bytecode run: | bun install bun build --bytecode --minify \ --outdir=./dist \ --target=bun \ ./src/index.ts ``` ## 调试 ### 验证字节码是否生效 检查 `.jsc` 文件是否存在: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ls -lh dist/ ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} -rw-r--r-- 1 user staff 245K index.js -rw-r--r-- 1 user staff 1.1M index.js.jsc ``` `.jsc` 文件通常比 `.js` 大 2-8 倍。 要记录是否使用了字节码,请在环境中设置 `BUN_JSC_verboseDiskCache=1`。 缓存命中时,Bun 会记录: ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} [Disk Cache] Cache hit for sourceCode ``` 缓存未命中时: ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} [Disk Cache] Cache miss for sourceCode ``` 出现多条缓存未命中日志是正常的:Bun 不会对其内置模块中的 JavaScript 进行字节码缓存。 ### 常见问题 **字节码被静默忽略**:通常是 Bun 版本更新导致的缓存版本不匹配,重新生成可解决。 **文件太大**:这是预期的。建议: * 使用 `--minify` 在生成字节码前减小代码体积 * 压缩 `.jsc` 文件以便通过网络传输(gzip/brotli) * 评估启动速度的提升是否值得文件体积增加 **顶层 await**:不支持,请用异步初始化函数替代。 ## 什么是字节码? 运行 JavaScript 时,JavaScript 引擎并不直接执行源代码,而是经过以下步骤: 1. **解析**:读取代码,生成抽象语法树(AST) 2. **字节码编译**:将 AST 编译为字节码,一种更低级但执行更快的中间表示 3. **执行**:引擎通过解释器或 JIT 编译器执行字节码 以上步骤会在**每次**运行代码时执行。一个每天运行 100 次的 CLI 工具会被解析 100 次;一个无服务器函数会在每次冷启动时进行解析。 每次运行你的代码都会执行以上步骤。如果你的 CLI 每天运行 100 次,则代码被解析 100 次。如果是无服务器函数发生频繁冷启动,解析在每次冷启动时都会进行。 字节码缓存将步骤 1 和 2 移到构建时。运行时直接加载预编译的字节码,加快启动。 现代 JavaScript 引擎使用一种称为**惰性解析**的优化方式。它们不会预先解析所有代码,而是仅在函数首次被调用时解析该函数: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} // 没有字节码缓存时: function rarely_used() { // 这段 500 行的函数仅在调用时被解析 } function main() { console.log("Starting app"); // rarely_used() 从未调用,故无解析 } ``` 这意味着解析开销并不只是启动时的成本——随着应用程序执行不同的代码路径,解析会在整个生命周期内持续发生。使用字节码缓存后,**所有函数都会被预编译**,包括那些引擎原本会延迟解析的函数。 ## 字节码格式 ### .jsc 文件内部结构 `.jsc` 文件包含一个序列化的字节码结构。 **头部部分**(每次加载验证): * **缓存版本**:与 JavaScriptCore 框架版本关联的哈希,确保字节码只在对应版本的 Bun 中运行。 * **代码块类型标签**:指示这是程序、模块、eval 还是函数代码块。 **SourceCodeKey**(验证字节码对应源码): * **源码哈希**:原始 JavaScript 源码的哈希值。Bun 在使用字节码前会验证该哈希是否匹配。 * **源码长度**:源码的确切长度,用于额外验证。 * **编译标志**:编译上下文,例如严格模式、脚本与模块,以及 eval 上下文类型。相同源码使用不同标志编译会生成不同的字节码。 **字节码指令**: * **指令流**:字节码操作码,即 JavaScript 的编译表示,以可变长度的指令序列存储。 * **元数据表**:每个操作码都有相关元数据,例如性能分析计数器、类型提示和执行次数(即使这些数据尚未填充)。 * **跳转目标**:为控制流(if/else、循环、switch 语句)预先计算的地址。 * **Switch 表**:用于 switch 语句的优化查找表。 **常量与标识符**: * **常量池**:代码中的所有字面量值——数字、字符串、布尔值、null、undefined。这些值以 JavaScript 值(JSValues)的形式存储,因此运行时无需从源码解析。 * **标识符表**:代码中使用的所有变量名和函数名,以去重后的字符串形式存储。 * **源码表示标记**:指示常量应如何表示的标志(例如整数、双精度浮点数、大整数等)。 **函数元数据**(每个函数对应): * **寄存器分配**:函数所需寄存器数,如 `thisRegister`、`scopeRegister`、`numVars`、`numCalleeLocals`、`numParameters`。 * **代码特性**:函数特性位掩码,如是否构造函数、箭头函数、使用 `super`、尾调用等,影响执行行为。 * **词法作用域特性**:严格模式及其他词法上下文。 * **解析模式**:函数被解析时的模式(普通、异步、生成器、异步生成器)。 **嵌套结构**: * **函数声明和表达式**:每个内嵌函数含独立字节码块。文件包含 100 个函数即有 100 个嵌套字节码块。 * **异常处理**:try/catch/finally 边界及处理地址经过预计算。 * **表达式信息**:字节码位置映射回源码位置,方便报错及调试。 ### 字节码不包含什么 **字节码不会嵌入你的源码**。相反: * JavaScript 源码保存在 `.js` 文件中 * 字节码只存储源码的哈希和长度 * 加载时 Bun 验证字节码与当前源码匹配 这就是为什么你需要同时部署 `.js` 和 `.jsc` 文件:没有对应的 `.js` 文件,`.jsc` 文件就毫无用处。 ## 权衡:文件大小 Bytecode files are typically 2-8x larger than the source code. ### 为什么字节码更大? **字节码指令冗长** 一行压缩后的 JS 代码可能对应数十条字节码指令,例如: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} const sum = arr.reduce((a, b) => a + b, 0); ``` 产生的字节码包括: * 加载 `arr` 变量 * 取 `reduce` 属性 * 创建箭头函数(其本身含字节码) * 加载初始值 0 * 设定调用参数数量 * 执行调用 * 结果赋值给 `sum` 每步都是独立操作指令附带元信息。 **常量池存储所有字面量** 例如字符串 `"hello"` 出现 100 次,常量池只存储一次,但标识符表和引用增加额外空间。 **每函数元数据** 即使是一行小函数,也有完整的元数据: * 寄存器分配信息 * 代码特性掩码 * 解析模式 * 异常处理 * 调试用表达式信息 文件中有 1000 个小函数即有 1000 份这类数据。 **性能分析结构** 即使未填充,也有用来记录性能数据的结构: * 值类型分析槽 * 数组访问分析槽 * 二元算术分析槽 * 一元算术分析槽 都占用空间。 **预计算控制流** 跳转目标、switch 表、异常边界预先计算存储,加快执行也增加大小。 ### 减少体积的策略 **压缩**: 字节码通过 gzip/brotli 压缩效果很好(压缩率 60-70%)。重复的结构和元数据能够高效压缩。 **先压缩代码** 先用 `--minify` 可带来: * 较短的标识符 → 标识符表更小 * 消除死代码 → 减少字节码 * 常量折叠 → 常量池变小 **权衡考虑** 通常你用 2-4 倍更大的文件换 2-4 倍更快的启动。对 CLI 来说极具价值;对长时间运行服务器,几兆字节无所谓。 ## 版本与可移植性 ### 跨架构可移植:✅ 字节码与架构无关。可以: * macOS ARM64 构建,部署到 Linux x64 * Linux x64 构建,部署到 AWS Lambda ARM64 * Windows x64 构建,部署到 macOS ARM64 字节码是抽象指令,由运行时 JIT 编译针对架构优化。 ### 跨版本可移植:❌ 字节码不兼容不同 Bun 版本,原因: **字节码格式变化**: JavaScriptCore 的字节码格式会随版本变化。新的操作码会被添加,旧的操作码会被移除或修改,元数据结构也会发生变化。 **版本验证** `.jsc` 文件头部包含缓存版本哈希。加载时: 1. 读取 `.jsc` 中缓存版本 2. 计算当前 JavaScriptCore 版本哈希 3. 不匹配时字节码**静默拒绝** 4. 回退解析 `.js` 源码 **优雅降级**: 这种设计意味着字节码缓存会“开放失败”——如果出现任何问题(版本不匹配、文件损坏、文件缺失),代码仍然可以正常运行。你可能会看到启动速度变慢,但不会看到错误。 ## 未链接与已链接字节码 JavaScriptCore 区分“未链接”和“已链接”字节码。这种分离机制使字节码缓存成为可能: ### 未链接字节码(缓存内容) `.jsc` 文件保存的是**未链接字节码**。包含: * 编译的字节码指令 * 代码结构信息 * 常量与标识符 * 控制流信息 不包含: * 指向运行时对象的指针 * JIT 编译的机器码 * 运行时性能分析数据 * 调用链接信息(函数调用关系) 未链接字节码是**不可变且可共享的**,同一代码多次运行共用一份。 ### 已链接字节码(运行时) 运行时,Bun 对字节码完成“链接”过程,生成运行时结构,附加: * **调用链接信息**:优化函数调用路径 * **性能分析数据**:统计指令执行次数、类型流、数组模式等 * **JIT 编译状态**:基线 JIT 或优化 JIT(DFG/FTL)的代码 * **运行时对象**:JavaScript 对象、原型、作用域指针等 这种已链接表示形式会在每次运行代码时重新创建。这种分离机制可以: 1. 缓存成本高昂的解析编译结果(未链接字节码) 2. 收集运行时分析数据 3. 启用基于分析的 JIT 优化 对于生产环境中的 CLI 和无服务器部署,结合使用 `--bytecode --minify --sourcemap` 可以在将错误映射回原始源代码的同时,提供最佳的启动速度。 # CSS Source: https://bun.zhcndoc.com/bundler/css Bun 的打包工具内置支持带有现代特性的 CSS Bun 的打包工具内置支持 CSS,具有以下功能: * 将现代/未来特性转换为可在所有浏览器上运行的代码(包括添加厂商前缀) * 压缩 * CSS 模块 * Tailwind(通过原生打包工具插件) ## 转译 默认启用转译和厂商前缀处理,因此你可以使用现代及未来的 CSS 特性,而无需担心浏览器兼容性。 Bun 的 CSS 解析器和打包器直接移植自 LightningCSS,其打包方式受 esbuild 启发。转译器会将现代 CSS 语法转换为向后兼容的等效写法,使其能够在各种浏览器中正常运行。 感谢 LightningCSS 和 esbuild 的作者所做的工作。 ## 浏览器兼容性 默认情况下,Bun 的 CSS 打包目标浏览器包括: * ES2020 * Edge 88+ * Firefox 78+ * Chrome 87+ * Safari 14+ ## 语法降级 ### 嵌套 借助 CSS 嵌套,你可以直接在父级代码块中编写子级样式,而无需在 CSS 文件中重复父级选择器。 ```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 使用嵌套 */ .card { background: white; border-radius: 4px; .title { font-size: 1.2rem; font-weight: bold; } .content { padding: 1rem; } } ``` Bun 的 CSS 打包工具会自动将此嵌套语法转换成传统的扁平 CSS,以兼容所有浏览器: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 编译输出 */ .card { background: white; border-radius: 4px; } .card .title { font-size: 1.2rem; font-weight: bold; } .card .content { padding: 1rem; } ``` 你还可以在选择器中嵌套媒体查询和其他 at 规则: ```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .responsive-element { display: block; @media (min-width: 768px) { display: flex; } } ``` 编译后为: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .responsive-element { display: block; } @media (min-width: 768px) { .responsive-element { display: flex; } } ``` ### 颜色混合 `color-mix()` 函数会在所选色彩空间中按给定比例混合两种颜色。使用它可以创建颜色变体,而无需自行计算结果值。 ```scss title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { /* 在 RGB 色彩空间中以 30%:70% 混合蓝色和红色 */ background-color: color-mix(in srgb, blue 30%, red); /* 为悬停状态创建较浅的变体 */ &:hover { background-color: color-mix(in srgb, blue 30%, red, white 20%); } } ``` Bun 的 CSS 打包工具会在构建时评估这些颜色混合(当所有颜色值已确定,非 CSS 变量),生成适用于所有浏览器的静态颜色值: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { /* 精确计算得到的结果色 */ background-color: #b31a1a; } .button:hover { background-color: #c54747; } ``` ### 相对颜色 相对颜色语法可以修改现有颜色的单个组成部分。无需重新计算整个颜色,即可调整亮度、饱和度或单独的颜色通道。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .theme-color { /* 以基础色为起点,将亮度增加 15% */ --accent: lch(from purple calc(l + 15%) c h); /* 以我们的品牌蓝色为基色,制作一个去饱和版本 */ --subtle-blue: oklch(from var(--brand-blue) l calc(c * 0.8) h); } ``` Bun 的 CSS 打包工具会在构建时计算这些相对颜色修改(不使用 CSS 变量时),生成适用于浏览器的静态颜色值: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .theme-color { --accent: lch(69.32% 58.34 328.37); --subtle-blue: oklch(60.92% 0.112 240.01); } ``` 可将其用于主题生成、无障碍颜色变体,或根据基础颜色生成颜色阶,而不是为每个值硬编码。 ### LAB 颜色 现代 CSS 支持感知均匀的颜色空间 LAB、LCH、OKLAB 和 OKLCH,它们可以表示超出标准 RGB 色域的颜色。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vibrant-element { /* 一种超出 sRGB 色域边界的鲜艳红色 */ color: lab(55% 78 35); /* 使用感知色彩空间的平滑渐变 */ background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg)); } ``` Bun 的 CSS 打包器会将这些颜色格式转换为向后兼容的替代方案,以支持不支持这些格式的浏览器: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vibrant-element { /* 退回到最接近的 RGB 近似 */ color: #ff0f52; /* 支持更宽色域的浏览器使用 P3 备选 */ color: color(display-p3 1 0.12 0.37); /* 支持该格式的浏览器保持原值 */ color: lab(55% 78 35); background: linear-gradient(to right, #cd4e15, #3887ab); background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg)); } ``` ### 颜色函数 `color()` 函数可以在传统 RGB 之外的预定义色彩空间中指定颜色,让你能够使用更宽的色域。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vivid-element { /* 使用 Display P3 色彩空间实现更宽色域 */ color: color(display-p3 1 0.1 0.3); /* 使用 A98 RGB 色彩空间 */ background-color: color(a98-rgb 0.44 0.5 0.37); } ``` 对于不支持这些色彩空间的浏览器,Bun 的 CSS 打包器会添加 RGB 备选值: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .vivid-element { /* 优先使用 RGB 备选以获得最大兼容 */ color: #fa1a4c; /* 保留原色以支持兼容浏览器 */ color: color(display-p3 1 0.1 0.3); background-color: #6a805d; background-color: color(a98-rgb 0.44 0.5 0.37); } ``` ### HWB 颜色 HWB(色相、白度、黑度)颜色模型基于纯色相中混合了多少白色或黑色来表示颜色。与 RGB 或 HSL 值相比,使用这种方式可以更直接地创建浅色和深色变体。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .easy-theming { /* 纯青色,无白无黑 */ --primary: hwb(180 0% 0%); /* 同色相,加入 20% 白色(色调) */ --primary-light: hwb(180 20% 0%); /* 同色相,加入 30% 黑色(阴影) */ --primary-dark: hwb(180 0% 30%); /* 添加白色和黑色的柔和版本 */ --primary-muted: hwb(180 30% 20%); } ``` Bun 的 CSS 打包器会将 HWB 颜色转换为 RGB,以兼容所有浏览器: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .easy-theming { --primary: #00ffff; --primary-light: #33ffff; --primary-dark: #00b3b3; --primary-muted: #339999; } ``` ### 颜色表示法 现代 CSS 支持使用空格分隔的 RGB 和 HSL 值(不带逗号),以及带 alpha 通道的十六进制颜色。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .modern-styling { /* 空格分隔的 RGB 表示法(无逗号) */ color: rgb(50 100 200); /* 空格分隔带 alpha 的 RGB */ border-color: rgba(100 50 200 / 75%); /* 带 alpha 通道的 8 位十六进制 */ background-color: #00aaff80; /* 简化的 HSL 表示 */ box-shadow: 0 5px 10px hsl(200 50% 30% / 40%); } ``` Bun 的 CSS 打包器会将这些格式转换为旧版浏览器支持的格式: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .modern-styling { /* 转为旧版需要的逗号格式 */ color: rgb(50, 100, 200); /* 透明度通道妥善处理 */ border-color: rgba(100, 50, 200, 0.75); /* 十六进制带 alpha 转为 rgba */ background-color: rgba(0, 170, 255, 0.5); box-shadow: 0 5px 10px rgba(38, 115, 153, 0.4); } ``` ### light-dark() 颜色函数 `light-dark()` 函数接受两种颜色,并根据当前颜色方案应用其中一种,使样式无需使用媒体查询即可遵循用户的系统偏好。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} :root { /* 定义支持的颜色方案 */ color-scheme: light dark; } .themed-component { /* 根据系统偏好自动选择颜色 */ background-color: light-dark(#ffffff, #121212); color: light-dark(#333333, #eeeeee); border-color: light-dark(#dddddd, #555555); } /* 需要时覆盖系统偏好 */ .light-theme { color-scheme: light; } .dark-theme { color-scheme: dark; } ``` 对于不支持 `light-dark()` 的浏览器,Bun 的 CSS 打包器会将其转换为带有回退值的 CSS 变量: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} :root { --buncss-light: initial; --buncss-dark: ; color-scheme: light dark; } @media (prefers-color-scheme: dark) { :root { --buncss-light: ; --buncss-dark: initial; } } .light-theme { --buncss-light: initial; --buncss-dark: ; color-scheme: light; } .dark-theme { --buncss-light: ; --buncss-dark: initial; color-scheme: dark; } .themed-component { background-color: var(--buncss-light, #ffffff) var(--buncss-dark, #121212); color: var(--buncss-light, #333333) var(--buncss-dark, #eeeeee); border-color: var(--buncss-light, #dddddd) var(--buncss-dark, #555555); } ``` ### 逻辑属性 CSS 逻辑属性根据文档的书写模式和文本方向,而不是屏幕的物理方向来定义布局、间距和尺寸,因此布局可以适应不同的书写系统。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .multilingual-component { /* 适应书写方向的外边距 */ margin-inline-start: 1rem; /* 无论文本方向,都合理的内边距 */ padding-block: 1rem 2rem; /* 顶部起始角的边框半径 */ border-start-start-radius: 4px; /* 尊重书写模式的尺寸 */ inline-size: 80%; block-size: auto; } ``` 对于不完全支持逻辑属性的浏览器,Bun 的 CSS 打包器会针对每种文本方向将其编译为物理属性: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 左到右语言 */ .multilingual-component:dir(ltr) { margin-left: 1rem; padding-top: 1rem; padding-bottom: 2rem; border-top-left-radius: 4px; width: 80%; height: auto; } /* 右到左语言 */ .multilingual-component:dir(rtl) { margin-right: 1rem; padding-top: 1rem; padding-bottom: 2rem; border-top-right-radius: 4px; width: 80%; height: auto; } ``` 如果不支持 `:dir()` 选择器,Bun 会生成额外的回退方案。 ### :dir() 选择器 `:dir()` 伪类根据元素的文本方向(RTL 或 LTR)设置样式,该方向由文档或显式的 direction 属性决定。使用它可以编写无需 JavaScript 的方向感知样式。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 根据文本方向应用不同样式 */ .nav-arrow:dir(ltr) { transform: rotate(0deg); } .nav-arrow:dir(rtl) { transform: rotate(180deg); } /* 基于文本流的位置定位元素 */ .sidebar:dir(ltr) { border-right: 1px solid #ddd; } .sidebar:dir(rtl) { border-left: 1px solid #ddd; } ``` 对于不支持 `:dir()` 选择器的浏览器,Bun 的 CSS 打包器会将其转换为兼容性更广的 `:lang()` 选择器,并使用适当的语言映射: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 作为回退转换为基于语言的选择器 */ .nav-arrow:lang(en, fr, de, es, it, pt, nl) { transform: rotate(0deg); } .nav-arrow:lang(ar, he, fa, ur) { transform: rotate(180deg); } .sidebar:lang(en, fr, de, es, it, pt, nl) { border-right: 1px solid #ddd; } .sidebar:lang(ar, he, fa, ur) { border-left: 1px solid #ddd; } ``` 如果不支持向 `:lang()` 传入多个参数,Bun 会生成进一步的回退方案。 ### `:lang()` 选择器 `:lang()` 伪类根据元素的语言定位元素。要为相关语言组合规则,可以向单个 `:lang()` 传入多个语言代码。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 针对中日韩语言的排版调整 */ :lang(zh, ja, ko) { line-height: 1.8; font-size: 1.05em; } /* 不同语言组的引号样式 */ blockquote:lang(fr, it, es, pt) { font-style: italic; } blockquote:lang(de, nl, da, sv) { font-weight: 500; } ``` 对于不支持在 `:lang()` 选择器中使用多个参数的浏览器,Bun 的 CSS 打包器会将此语法转换为行为相同的 `:is()` 选择器: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 使用 :is() 组合多个语言以提高兼容 */ :is(:lang(zh), :lang(ja), :lang(ko)) { line-height: 1.8; font-size: 1.05em; } blockquote:is(:lang(fr), :lang(it), :lang(es), :lang(pt)) { font-style: italic; } blockquote:is(:lang(de), :lang(nl), :lang(da), :lang(sv)) { font-weight: 500; } ``` 如果需要,Bun 还可以为 `:is()` 生成额外的回退方案。 ### :is() 选择器 `:is()` 伪类函数(之前称为 `:matches()`)接收一个选择器列表,如果列表中的任意选择器匹配,则匹配成功。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 不用分别写这些 */ /* .article h1, .article h2, .article h3 { margin-top: 1.5em; } */ /* 可以写成 */ .article :is(h1, h2, h3) { margin-top: 1.5em; } /* 复杂示例,多个组合 */ :is(header, main, footer) :is(h1, h2, .title) { font-family: "Heading Font", sans-serif; } ``` 对于不支持 `:is()` 的浏览器,Bun 的 CSS 打包工具提供使用厂商前缀的替代写法: ```css theme={"theme":{"light":"github-light","dark":"dracula"}} /* 使用 -webkit-any 回退 */ .article :-webkit-any(h1, h2, h3) { margin-top: 1.5em; } /* 使用 -moz-any 回退 */ .article :-moz-any(h1, h2, h3) { margin-top: 1.5em; } /* 现代浏览器保留原写法 */ .article :is(h1, h2, h3) { margin-top: 1.5em; } /* 复杂示例的回退 */ :-webkit-any(header, main, footer) :-webkit-any(h1, h2, .title) { font-family: "Heading Font", sans-serif; } :-moz-any(header, main, footer) :-moz-any(h1, h2, .title) { font-family: "Heading Font", sans-serif; } :is(header, main, footer) :is(h1, h2, .title) { font-family: "Heading Font", sans-serif; } ``` 与标准化的 `:is()` 选择器相比,带厂商前缀的版本存在一些限制,尤其是在处理复杂选择器时。Bun 只会在带前缀的版本能够正常工作时使用它们。 ### :not() 选择器 `:not()` 伪类会排除匹配某个选择器的元素。现代版本支持多个参数,可以通过一个 `:not()` 排除多种模式。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 选择所有按钮,排除主按钮和次按钮 */ button:not(.primary, .secondary) { background-color: #f5f5f5; border: 1px solid #ddd; } /* 应用样式于所有标题,排除侧边栏或页脚内的 */ h2:not(.sidebar *, footer *) { margin-top: 2em; } ``` 对于不支持 `:not()` 中多个参数的浏览器,Bun 的 CSS 打包器会将此语法转换为行为相同且兼容性更好的形式: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 转换为使用带 :is() 的 :not() */ button:not(:is(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } h2:not(:is(.sidebar *, footer *)) { margin-top: 2em; } ``` 如果不支持 `:is()`,Bun 会生成进一步的回退: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 额外回退以确保最大兼容 */ button:not(:-webkit-any(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } button:not(:-moz-any(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } button:not(:is(.primary, .secondary)) { background-color: #f5f5f5; border: 1px solid #ddd; } ``` 转换后的选择器保留了原始选择器的优先级和行为。 ### 数学函数 CSS 包含标准数学函数(`round()`、`mod()`、`rem()`、`abs()`、`sign()`)、三角函数(`sin()`、`cos()`、`tan()`、`asin()`、`acos()`、`atan()`、`atan2()`)以及指数函数(`pow()`、`sqrt()`、`exp()`、`log()`、`hypot()`)。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .dynamic-sizing { /* 限定值在最小和最大之间 */ width: clamp(200px, 50%, 800px); /* 四舍五入到最接近的倍数 */ padding: round(14.8px, 5px); /* 动画或布局的三角函数 */ transform: rotate(calc(sin(45deg) * 50deg)); /* 组合多个函数的复杂数学 */ --scale-factor: pow(1.25, 3); font-size: calc(16px * var(--scale-factor)); } ``` 当所有值都是已知常量(而非变量)时,Bun 的 CSS 打包器会在构建时计算这些表达式: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .dynamic-sizing { width: clamp(200px, 50%, 800px); padding: 15px; transform: rotate(35.36deg); --scale-factor: 1.953125; font-size: calc(16px * var(--scale-factor)); } ``` ### 媒体查询范围 媒体查询范围语法使用比较运算符(`<`、`>`、`<=`、`>=`)表示断点,而不是使用更冗长的 `min-` 和 `max-` 前缀。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 比较运算符的现代写法 */ @media (width >= 768px) { .container { max-width: 720px; } } /* 使用 <= 和 >= 的包含范围 */ @media (768px <= width <= 1199px) { .sidebar { display: flex; } } /* 使用 < 和 > 的排他范围 */ @media (width > 320px) and (width < 768px) { .mobile-only { display: block; } } ``` Bun 的 CSS 打包器会将范围查询转换为传统的媒体查询语法,以兼容所有浏览器: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 转换为传统 min/max 语法 */ @media (min-width: 768px) { .container { max-width: 720px; } } @media (min-width: 768px) and (max-width: 1199px) { .sidebar { display: flex; } } @media (min-width: 321px) and (max-width: 767px) { .mobile-only { display: block; } } ``` ### 简写属性 CSS 引入了几种将多个长属性组合在一起的简写属性。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} /* 对齐简写 */ .flex-container { /* align-items 和 justify-items 的简写 */ place-items: center start; /* align-content 和 justify-content 的简写 */ place-content: space-between center; } .grid-item { /* align-self 和 justify-self 的简写 */ place-self: end center; } /* 双值溢出属性 */ .content-box { /* 第一个值对应水平,第二个垂直 */ overflow: hidden auto; } /* 增强的文本装饰 */ .fancy-link { /* 结合多条文本装饰属性 */ text-decoration: underline dotted blue 2px; } /* 双值 display 语法 */ .component { /* 外层和内层显示类型 */ display: inline flex; } ``` 对于不支持这些简写属性的浏览器,Bun 会将它们转换为对应的组件长属性: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .flex-container { /* 展开对齐属性 */ align-items: center; justify-items: start; align-content: space-between; justify-content: center; } .grid-item { align-self: end; justify-self: center; } .content-box { /* 分别控制溢出方向 */ overflow-x: hidden; overflow-y: auto; } .fancy-link { /* 单独文本装饰属性 */ text-decoration-line: underline; text-decoration-style: dotted; text-decoration-color: blue; text-decoration-thickness: 2px; } .component { /* 单值 display */ display: inline-flex; } ``` ### 双位置渐变 双位置渐变语法会在两个相邻位置指定相同的颜色,从而创建硬色标:产生锐利的过渡,而不是平滑的渐变。它适用于条纹、色带以及其他多色设计。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .striped-background { /* 在 30%-40% 制造绿色到红色的硬切换 */ background: linear-gradient( to right, yellow 0%, green 20%, green 30%, red 30%, /* 双点位置制造硬边界 */ red 70%, blue 70%, blue 100% ); } .progress-bar { /* 制造明显色块 */ background: linear-gradient( to right, #4caf50 0% 25%, /* 绿色从 0% 到 25% */ #ffc107 25% 50%, /* 黄色从 25% 到 50% */ #2196f3 50% 75%, /* 蓝色从 50% 到 75% */ #9c27b0 75% 100% /* 紫色从 75% 到 100% */ ); } ``` 对于不支持此语法的浏览器,Bun 的 CSS 打包器会通过复制颜色标记,将其转换为传统格式: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .striped-background { background: linear-gradient( to right, yellow 0%, green 20%, green 30%, red 30%, /* 分成两段色点 */ red 70%, blue 70%, blue 100% ); } .progress-bar { background: linear-gradient( to right, #4caf50 0%, #4caf50 25%, /* 两段色点绿段 */ #ffc107 25%, #ffc107 50%, /* 两段色点黄段 */ #2196f3 50%, #2196f3 75%, /* 两段色点蓝段 */ #9c27b0 75%, #9c27b0 100% /* 两段色点紫段 */ ); } ``` ### system-ui 字体 `system-ui` 通用字体系列使用设备的原生 UI 字体。 ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .native-interface { /* 使用系统默认 UI 字体 */ font-family: system-ui; } .fallback-aware { /* 带显式回退的系统 UI 字体 */ font-family: system-ui, sans-serif; } ``` 对于不支持 `system-ui` 的浏览器,Bun 的 CSS 打包器会将其扩展为跨平台字体堆栈: ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .native-interface { /* 为所有主流平台扩展 */ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue"; } .fallback-aware { /* 扩展字体堆栈后保留原轮廓回退 */ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } ``` 扩展后的字体堆栈包含适用于 macOS/iOS、Windows、Android 和 Linux 的系统字体,以及针对旧版浏览器的回退字体。 ## CSS 模块 Bun 的打包器还支持 CSS 模块,具有以下功能: * 无需配置即可检测 CSS 模块文件(`.module.css`) * 组合(`composes` 属性) * 将 CSS 模块导入 JSX/TSX * 针对 CSS 模块无效用法的警告/错误 CSS 模块是一种 CSS 文件(扩展名为 `.module.css`),其中所有类名和动画都限定在该文件的作用域内。这有助于避免类名冲突,因为 CSS 声明默认是全局作用域的。 Bun 的打包器会将局部作用域的类名转换为唯一标识符。 ### 快速入门 创建一个以 `.module.css` 结尾的 CSS 文件: ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { color: red; } ``` ```css title="other-styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { color: blue; } ``` 然后你可以导入此文件,比如在 TSX 文件中: ```tsx title="app.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import styles from "./styles.module.css"; import otherStyles from "./other-styles.module.css"; export default function App() { return ( <> ); } ``` 导入 CSS 模块会得到一个对象,该对象将每个类名映射到其唯一标识符: ```ts title="app.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import styles from "./styles.module.css"; import otherStyles from "./other-styles.module.css"; console.log(styles); console.log(otherStyles); ``` 输出结果如下: ```ts title="app.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} { button: "button_123"; } { button: "button_456"; } ``` 每个文件中的类名都是唯一的,因此不会发生冲突。 ### 组合 CSS 模块可以将类选择器组合在一起,以便在多个类之间复用样式规则。 例如: ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { composes: background; color: red; } .background { background-color: blue; } ``` 这与以下写法相同: ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { background-color: blue; color: red; } .background { background-color: blue; } ``` 使用 `composes` 时需要遵循以下两条规则: **组合规则:** * `composes` 属性必须写在任何常规 CSS 属性之前 * 只能在带有单个类名的简单选择器上使用 `composes` ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} #button { /* 无效!`#button` 不是类选择器 */ composes: background; } .button, .button-secondary { /* 无效!`.button, .button-secondary` 不是简单选择器 */ composes: background; } ``` ### 从独立 CSS 模块文件组合 你也可以从其他 CSS 模块文件中组合类: ```css title="background.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .background { background-color: blue; } ``` ```css title="styles.module.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} .button { composes: background from "./background.module.css"; color: red; } ``` 从独立文件组合类时,请确保它们不包含相同的属性。 CSS 模块规范规定,从独立文件中组合具有冲突属性的类属于未定义行为:输出结果可能有所不同,并且不可靠。 # esbuild Source: https://bun.zhcndoc.com/bundler/esbuild 从 esbuild 迁移到 Bun 的打包器指南 Bun 的打包器 API 深受 esbuild 启发。本页面对这两个 API 进行了并列比较。 有一些行为有所不同: **默认进行打包。** 与 esbuild 不同,Bun 默认会进行打包;无需使用 `--bundle` 标志。若要分别转译每个文件, 请使用 `Bun.Transpiler`。 **仅限打包器。** 与 esbuild 不同,Bun 的打包器没有内置开发服务器。将其与 `Bun.serve` 及其他运行时 API 结合使用,即可实现相同的效果。esbuild 的 HTTP 选项不适用。 ## 性能 Bun 的打包器在 esbuild 的 three.js 基准测试中比 esbuild 快 1.75 倍。 从零开始打包 10 份 three.js,启用源码映射和压缩 ## CLI API ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # esbuild esbuild --outdir=out --bundle # bun bun build --outdir=out ``` 在 Bun 的 CLI 中,像 `--minify` 这样的布尔标志不接受参数。像 `--outdir ` 这样需要一个参数的标志,可以写成 `--outdir out` 或 `--outdir=out`。某些标志(如 `--define`)可以重复使用:`--define foo=bar --define bar=baz`。 | esbuild | bun build | 备注 | | ---------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--bundle` | 不适用 | Bun 始终进行打包;使用 `--no-bundle` 可禁用打包。 | | `--define:K=V` | `--define K=V` | 语法略有不同;没有冒号。
`esbuild --define:foo=bar`
`bun build --define foo=bar` | | `--external:` | `--external ` | 语法略有不同;没有冒号。
`esbuild --external:react`
`bun build --external react` | | `--format` | `--format` | Bun 支持 `"esm"`、`"cjs"` 和 `"iife"`。esbuild 默认为 `"iife"`。 | | `--loader:.ext=loader` | `--loader .ext:loader` | Bun 支持的内置加载器集合与 esbuild 不同;请参阅[加载器](/bundler/loaders)。esbuild 的加载器 `dataurl`、`binary`、`base64`、`copy` 和 `empty` 尚未实现。

`--loader` 的语法有所不同。
`esbuild app.ts --bundle --loader:.svg=text`
`bun build app.ts --loader .svg:text` | | `--minify` | `--minify` | 无差异 | | `--outdir` | `--outdir` | 无差异 | | `--outfile` | `--outfile` | 无差异 | | `--packages` | `--packages` | 无差异 | | `--platform` | `--target` | 为与 tsconfig 保持一致而重命名为 `--target`。不支持 `neutral`。 | | `--serve` | 不适用 | 不适用 | | `--sourcemap` | `--sourcemap` | 无差异 | | `--splitting` | `--splitting` | 无差异 | | `--target` | 不适用 | 不支持。Bun 的打包器不会执行语法降级。 | | `--watch` | `--watch` | 无差异 | | `--allow-overwrite` | 不适用 | 始终不允许覆盖 | | `--analyze` | 不适用 | 不支持 | | `--asset-names` | `--asset-naming` | 为与 JS API 中的命名保持一致而重命名 | | `--banner` | `--banner` | 仅适用于 JS bundle | | `--footer` | `--footer` | 仅适用于 JS bundle | | `--certfile` | 不适用 | 不适用 | | `--charset=utf8` | 不适用 | 不支持 | | `--chunk-names` | `--chunk-naming` | 为与 JS API 中的命名保持一致而重命名 | | `--color` | 不适用 | 始终启用 | | `--drop` | `--drop` | | | 不适用 | `--feature` | Bun 特有。通过 `import { feature } from "bun:bundle"` 启用编译时死代码消除的功能标志 | | `--entry-names` | `--entry-naming` | 为与 JS API 中的命名保持一致而重命名 | | `--global-name` | 不适用 | 不支持 | | `--ignore-annotations` | `--ignore-dce-annotations` | | | `--inject` | 不适用 | 不支持 | | `--jsx` | `--jsx-runtime ` | 支持 `"automatic"`(使用 JSX 转换)和 `"classic"`(使用 `React.createElement`) | | `--jsx-dev` | 不适用 | Bun 从 `tsconfig.json` 中读取 `compilerOptions.jsx` 来确定默认值。如果 `compilerOptions.jsx` 为 `"react-jsx"`,或者 `NODE_ENV=production`,Bun 将使用 JSX 转换。否则将使用 `jsxDEV`。打包器不支持 `preserve`。 | | `--jsx-factory` | `--jsx-factory` | | | `--jsx-fragment` | `--jsx-fragment` | | | `--jsx-import-source` | `--jsx-import-source` | | | `--jsx-side-effects` | 不适用 | 始终假定 JSX 没有副作用 | | `--keep-names` | `--keep-names` | | | `--keyfile` | 不适用 | 不适用 | | `--legal-comments` | 不适用 | 不支持 | | `--log-level` | 不适用 | 不支持。可以在 `bunfig.toml` 中将其设置为 `logLevel`。 | | `--log-limit` | 不适用 | 不支持 | | `--log-override:X=Y` | 不适用 | 不支持 | | `--main-fields` | 不适用 | 不支持 | | `--mangle-cache` | 不适用 | 不支持 | | `--mangle-props` | 不适用 | 不支持 | | `--mangle-quoted` | 不适用 | 不支持 | | `--metafile` | `--metafile` | | | `--minify-whitespace` | `--minify-whitespace` | | | `--minify-identifiers` | `--minify-identifiers` | | | `--minify-syntax` | `--minify-syntax` | | | `--out-extension` | 不适用 | 不支持 | | `--outbase` | `--root` | | | `--preserve-symlinks` | 不适用 | 不支持 | | `--public-path` | `--public-path` | | | `--pure` | 不适用 | 不支持 | | `--reserve-props` | 不适用 | 不支持 | | `--resolve-extensions` | 不适用 | 不支持 | | `--servedir` | 不适用 | 不适用 | | `--source-root` | 不适用 | 不支持 | | `--sourcefile` | 不适用 | 不支持。Bun 不支持标准输入。 | | `--sourcemap` | `--sourcemap` | 无差异 | | `--sources-content` | 不适用 | 不支持 | | `--supported` | 不适用 | 不支持 | | `--tree-shaking` | 不适用 | 始终为 true | | `--tsconfig` | `--tsconfig-override` | | | `--version` | 不适用 | 运行 `bun --version` 查看 Bun 的版本。 | ## JavaScript API | esbuild.build() | Bun.build() | 备注 | | ------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `absWorkingDir` | 不适用 | 始终设置为 `process.cwd()` | | `alias` | 不适用 | 不支持 | | `allowOverwrite` | 不适用 | 始终为 false | | `assetNames` | `naming.asset` | 使用与 esbuild 相同的模板语法,但必须显式包含 `[ext]`。

`ts
Bun.build({
entrypoints: ["./index.tsx"],
naming: {
asset: "[name].[ext]",
},
});
` | | `banner` | `banner` | 仅适用于 js bundle | | `bundle` | 不适用 | 始终为 true。使用 `Bun.Transpiler` 在不打包的情况下进行转译。 | | `charset` | 不适用 | 不支持 | | `chunkNames` | `naming.chunk` | 使用与 esbuild 相同的模板语法,但必须显式包含 `[ext]`。

`ts
Bun.build({
entrypoints: ["./index.tsx"],
naming: {
chunk: "[name].[ext]",
},
});
` | | `color` | 不适用 | Bun 将日志返回在构建结果的 `logs` 属性中。 | | `conditions` | `conditions` | 无差异 | | `define` | `define` | | | `drop` | `drop` | | | `entryNames` | `naming` 或 `naming.entry` | Bun 支持 `naming` 键,该键可以是字符串或对象。使用与 esbuild 相同的模板语法,但必须显式包含 `[ext]`。

`ts
Bun.build({
entrypoints: ["./index.tsx"],
// 当为字符串时,这等价于 entryNames
naming: "[name].[ext]",

// 细粒度的命名选项
naming: {
entry: "[name].[ext]",
asset: "[name].[ext]",
chunk: "[name].[ext]",
},
});
` | | `entryPoints` | `entrypoints` | 大小写差异 | | `external` | `external` | 无差异 | | `footer` | `footer` | 仅适用于 js bundle | | `format` | `format` | 支持 `"esm"`、`"cjs"` 和 `"iife"`。 | | `globalName` | 不适用 | 不支持 | | `ignoreAnnotations` | `ignoreDCEAnnotations` | | | `inject` | 不适用 | 不支持 | | `jsx` | `jsx.runtime` | 支持 `"automatic"` 和 `"classic"` | | `jsxDev` | `jsx.development` | | | `jsxFactory` | `jsx.factory` | | | `jsxFragment` | `jsx.fragment` | | | `jsxImportSource` | `jsx.importSource` | | | `jsxSideEffects` | `jsx.sideEffects` | | | `keepNames` | `minify.keepNames` | | | `legalComments` | 不适用 | 不支持 | | `loader` | `loader` | Bun 支持的内置 loader 集合与 esbuild 不同;请参阅 [加载器](/bundler/loaders)。esbuild 的 loader `dataurl`、`binary`、`base64`、`copy` 和 `empty` 尚未实现。 | | `logLevel` | 不适用 | 不支持 | | `logLimit` | 不适用 | 不支持 | | `logOverride` | 不适用 | 不支持 | | `mainFields` | 不适用 | 不支持 | | `mangleCache` | 不适用 | 不支持 | | `mangleProps` | 不适用 | 不支持 | | `mangleQuoted` | 不适用 | 不支持 | | `metafile` | `metafile` | | | `minify` | `minify` | 在 Bun 中,`minify` 可以是布尔值或对象。

`ts
await Bun.build({
entrypoints: ['./index.tsx'],
// 启用所有压缩选项
minify: true

// 细粒度选项
minify: {
identifiers: true,
syntax: true,
whitespace: true
}
})
` | | `minifyIdentifiers` | `minify.identifiers` | 请参阅 `minify` | | `minifySyntax` | `minify.syntax` | 请参阅 `minify` | | `minifyWhitespace` | `minify.whitespace` | 请参阅 `minify` | | `nodePaths` | 不适用 | 不支持 | | `outExtension` | 不适用 | 不支持 | | `outbase` | `root` | 名称不同 | | `outdir` | `outdir` | 无差异 | | `outfile` | `outfile` | 无差异 | | `packages` | `packages` | 无差异 | | `platform` | `target` | 支持 `"bun"`、`"node"` 和 `"browser"`(默认值)。不支持 `"neutral"`。 | | `plugins` | `plugins` | Bun 的插件 API 是 esbuild 的子集。部分 esbuild 插件无需修改即可与 Bun 配合使用。 | | `preserveSymlinks` | 不适用 | 不支持 | | `publicPath` | `publicPath` | 无差异 | | `pure` | 不适用 | 不支持 | | `reserveProps` | 不适用 | 不支持 | | `resolveExtensions` | 不适用 | 不支持 | | `sourceRoot` | 不适用 | 不支持 | | `sourcemap` | `sourcemap` | 支持 `"none"`、`"linked"`、`"inline"` 和 `"external"` | | `sourcesContent` | 不适用 | 不支持 | | `splitting` | `splitting` | 无差异 | | `stdin` | 不适用 | 不支持 | | `supported` | 不适用 | 不支持 | | `target` | 不适用 | 不支持语法降级 | | `treeShaking` | `treeShaking` | 默认为 `true` | | `tsconfig` | `tsconfig` | | | `write` | 不适用 | 设置了 `outdir`/`outfile` 时设为 true,否则设为 false | ## 插件 API Bun 的插件 API 旨在兼容 esbuild。Bun 不支持 esbuild 的完整插件 API,但已实现核心功能,并且许多第三方 esbuild 插件无需修改即可与 Bun 一起使用。 从长远来看,我们的目标是实现与 esbuild API 的功能对等。如果某些功能无法正常工作,请提交 issue,帮助我们确定优先级。 Bun 和 esbuild 中的插件均通过构造一个 builder 对象定义。 ```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "my-plugin", setup(builder) { // 定义插件 }, }; ``` builder 对象的方法会钩入打包过程的各个部分。Bun 实现了 `onStart`、`onEnd`、`onResolve` 和 `onLoad`;但未实现 esbuild 的 `onDispose` 和 `resolve` 钩子。`initialOptions` 已部分实现:它是只读的,并且仅暴露 esbuild 选项的子集。请改用 `config`(即 Bun 的 `BuildConfig` 格式中的同一配置)。 ```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "my-plugin", setup(builder) { builder.onStart(() => { /* 当打包开始时调用 */ }); builder.onResolve( { /* onResolve 选项 */ }, args => { return { /* onResolve 返回值 */ }; }, ); builder.onLoad( { /* onLoad 选项 */ }, args => { return { /* onLoad 返回值 */ }; }, ); builder.onEnd(result => { /* 当打包完成时调用 */ }); }, }; ``` ### onResolve * 🟢 `filter` * 🟢 `namespace` * 🟢 `path` * 🟢 `importer` * 🟢 `namespace` * 🟢 `resolveDir` * 🟢 `kind` * 🔴 `pluginData` * 🟢 `namespace` * 🟢 `path` * 🔴 `errors` * 🟢 `external` * 🔴 `pluginData` * 🔴 `pluginName` * 🔴 `sideEffects` * 🔴 `suffix` * 🔴 `warnings` * 🔴 `watchDirs` * 🔴 `watchFiles` ### onLoad * 🟢 `filter` * 🟢 `namespace` * 🟢 `path` * 🟢 `namespace` * 🔴 `suffix` * 🔴 `pluginData` * 🟢 `contents` * 🟢 `loader` * 🔴 `errors` * 🔴 `pluginData` * 🔴 `pluginName` * 🔴 `resolveDir` * 🔴 `warnings` * 🔴 `watchDirs` * 🔴 `watchFiles` # 独立可执行文件 Source: https://bun.zhcndoc.com/bundler/executables 使用 Bun 从 TypeScript 或 JavaScript 文件生成独立可执行文件 Bun 的打包器支持 `--compile` 标志,用于从 TypeScript 或 JavaScript 文件生成独立的二进制可执行文件。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./cli.ts --compile --outfile mycli ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./cli.ts"], compile: { outfile: "./mycli", }, }); ``` ```ts cli.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Hello world!"); ``` 这会将 `cli.ts` 打包成一个可以直接运行的可执行文件: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ./mycli ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} Hello world! ``` 所有导入的文件和包都会被打包进可执行文件中,同时包含一份 Bun 运行时。所有内置的 Bun 和 Node.js API 都被支持。 *** ## 跨平台交叉编译 使用 `--target` 标志,可以将独立可执行文件编译为与运行 `bun build` 的机器不同的操作系统、架构或 Bun 版本。 构建 Linux x64 版本(大多数服务器): ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp # 支持 2013 年以前的 CPU,使用 baseline 版本(nehalem) bun build --compile --target=bun-linux-x64-baseline ./index.ts --outfile myapp # 仅支持 2013 年及之后的 CPU,使用 modern 版本(haswell) # modern 版本速度更快,但 baseline 兼容性更广。 bun build --compile --target=bun-linux-x64-modern ./index.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 标准 Linux x64 await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-x64", outfile: "./myapp", }, }); // 基线版(2013 年前 CPU) await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-x64-baseline", outfile: "./myapp", }, }); // 现代版(2013 年及以后 CPU,更快) await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-x64-modern", outfile: "./myapp", }, }); ``` 构建 Linux ARM64 版本(例如 Graviton 或 Raspberry Pi): ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # 注意:如果未指定架构,默认架构为 x64。 bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], compile: { target: "bun-linux-arm64", outfile: "./myapp", }, }); ``` 构建 Windows x64 版本: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-windows-x64 ./path/to/my/app.ts --outfile myapp # 支持 2013 年以前的 CPU,使用 baseline 版本(nehalem) bun build --compile --target=bun-windows-x64-baseline ./path/to/my/app.ts --outfile myapp # 仅支持 2013 年及以后 CPU,使用 modern 版本(haswell) bun build --compile --target=bun-windows-x64-modern ./path/to/my/app.ts --outfile myapp # 注意:如果未提供 .exe 扩展名,Bun 会自动为 Windows 可执行文件添加该扩展名 ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 标准 Windows x64 await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-windows-x64", outfile: "./myapp", // 自动加上 .exe 后缀 }, }); // 基线版或现代版 await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-windows-x64-baseline", outfile: "./myapp", }, }); ``` 为 Windows arm64 构建: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-windows-arm64 ./path/to/my/app.ts --outfile myapp # 注意:如果未提供 .exe 扩展名,Bun 会自动为 Windows 可执行文件添加该扩展名 ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-windows-arm64", outfile: "./myapp", // 自动加上 .exe }, }); ``` 构建 macOS arm64 版本: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-darwin-arm64 ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-darwin-arm64", outfile: "./myapp", }, }); ``` 构建 macOS x64 版本: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { target: "bun-darwin-x64", outfile: "./myapp", }, }); ``` ### 支持的目标平台 `--target` 值的各个部分可以按任意顺序出现,只要它们使用 `-` 分隔即可。 | --target | 操作系统 | 架构 | 现代架构 | 基线架构 | Libc | | -------------------- | ------- | ----- | ---- | ---- | ----- | | bun-linux-x64 | Linux | x64 | ✅ | ✅ | glibc | | bun-linux-arm64 | Linux | arm64 | ✅ | N/A | glibc | | bun-windows-x64 | Windows | x64 | ✅ | ✅ | - | | bun-windows-arm64 | Windows | arm64 | ✅ | N/A | - | | bun-darwin-x64 | macOS | x64 | ✅ | ✅ | - | | bun-darwin-arm64 | macOS | arm64 | ✅ | N/A | - | | bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl | | bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl | 在 x64 平台上,Bun 使用需要 CPU 支持 AVX2 指令的 SIMD 优化。Bun 的 `-baseline` 版本适用于不支持这些指令的旧款 CPU。Bun 安装程序会检测应使用哪个版本,但进行交叉编译时,你可能不知道目标 CPU 的具体情况。这主要影响 Windows x64 和 Linux x64,在 Darwin x64 上则很少遇到。如果你或你的用户看到 `"Illegal instruction"` 错误,可能需要使用 baseline 版本。 *** ## 编译时常量 使用 `--define` 标志可将编译时常量注入可执行文件,例如版本号、构建时间戳或配置值: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./src/cli.ts"], compile: { outfile: "./mycli", }, define: { BUILD_VERSION: JSON.stringify("1.2.3"), BUILD_TIME: JSON.stringify("2024-01-15T10:30:00Z"), }, }); ``` Bun 会在构建时将这些常量内联到二进制文件中,因此它们不会产生任何运行时开销,并且能够实现死代码消除。 如需了解更多示例和模式,请参阅[构建时常量指南](/guides/runtime/build-time-constants)。 *** ## 生产环境部署 编译后的可执行文件降低内存使用并提升 Bun 启动速度。 通常情况下,Bun 会在 `import` 和 `require` 时读取并转译 JavaScript 和 TypeScript 文件。这正是 Bun 能够“开箱即用”的原因之一,但这并非没有代价:从磁盘读取文件、解析路径、解析代码、转译以及打印源代码都会消耗时间和内存。 编译后的可执行文件将这些开销从运行时转移到了构建时。 部署到生产环境推荐做法: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { outfile: "./myapp", }, minify: true, sourcemap: "linked", }); ``` ### 字节码编译 为提升启动速度,启用字节码编译: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --minify --sourcemap --bytecode ./path/to/my/app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./path/to/my/app.ts"], compile: { outfile: "./myapp", }, minify: true, sourcemap: "linked", bytecode: true, }); ``` 使用字节码编译,`tsc` 启动速度提升两倍: ![字节码性能比较](https://github.com/user-attachments/assets/dc8913db-01d2-48f8-a8ef-ac4e984f9763) 字节码编译将大文件的解析开销从运行时转移到打包时,提升了启动速度,但稍微增加了 `bun build` 命令的时长,不影响源码可读性。 字节码编译在搭配 `--compile` 时同时支持 `cjs` 和 `esm` 格式。 ### 各个标志的作用 `--minify` 参数会减小转译后输出代码的大小。对于大型应用,这可以节省数兆字节的空间。对于较小的应用,它仍可能略微改善启动时间。 `--sourcemap` 参数会嵌入使用 zstd 压缩的源映射,这样错误和堆栈跟踪就会指向其原始位置,而不是转译后的位置。发生错误时,Bun 会自动解压并解析源映射。 `--bytecode` 参数会启用字节码编译。每次在 Bun 中运行 JavaScript 代码时,JavaScriptCore(引擎)都会将源代码编译为字节码。`--bytecode` 会将这部分解析工作从运行时转移到打包时,从而缩短启动时间。 *** ## 嵌入运行时参数 **`--compile-exec-argv="args"`** - 嵌入运行时参数,可在运行时通过 `process.execArgv` 获取: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --compile-exec-argv="--smol --user-agent=MyBot" ./app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], compile: { execArgv: ["--smol", "--user-agent=MyBot"], outfile: "./myapp", }, }); ``` ```ts app.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 在编译后的程序中 console.log(process.execArgv); // ["--smol", "--user-agent=MyBot"] ``` ### 通过 `BUN_OPTIONS` 传入运行时参数 独立可执行文件会读取 `BUN_OPTIONS` 环境变量,因此你可以无需重新编译即可传入运行时标志: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # 在编译后的可执行文件上启用 CPU 性能分析 BUN_OPTIONS="--cpu-prof" ./myapp # 启用堆性能分析并输出 Markdown 格式 BUN_OPTIONS="--heap-prof-md" ./myapp # 组合多个标志 BUN_OPTIONS="--smol --cpu-prof-md" ./myapp ``` *** ## 自动加载配置 独立可执行文件可以自动从运行目录加载配置文件。默认: * **禁用** 加载 `tsconfig.json` 和 `package.json` — 这些通常只在开发时需要,编译时已经用过了 * **启用** 加载 `.env` 和 `bunfig.toml` — 这些通常包含运行时配置,部署时可能有所不同 未来版本中,为了更确定的行为,可能会默认禁用 `.env` 和 `bunfig.toml` 加载。 ### 运行时启用配置加载 如果您的可执行文件需要在运行时读取 `tsconfig.json` 或 `package.json`,请使用以下标志启用: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # 启用 tsconfig.json 运行时加载 bun build --compile --compile-autoload-tsconfig ./app.ts --outfile myapp # 启用 package.json 运行时加载 bun build --compile --compile-autoload-package-json ./app.ts --outfile myapp # 同时启用 bun build --compile --compile-autoload-tsconfig --compile-autoload-package-json ./app.ts --outfile myapp ``` ### 运行时禁用配置加载 要禁用 `.env` 或 `bunfig.toml` 以实现确定性执行: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # 禁用 .env 加载 bun build --compile --no-compile-autoload-dotenv ./app.ts --outfile myapp # 禁用 bunfig.toml 加载 bun build --compile --no-compile-autoload-bunfig ./app.ts --outfile myapp # 禁用所有配置加载 bun build --compile --no-compile-autoload-dotenv --no-compile-autoload-bunfig ./app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], compile: { // tsconfig.json 和 package.json 默认禁用 autoloadTsconfig: true, // 启用 tsconfig.json 加载 autoloadPackageJson: true, // 启用 package.json 加载 // .env 和 bunfig.toml 默认启用 autoloadDotenv: false, // 禁用 .env 加载 autoloadBunfig: false, // 禁用 bunfig.toml 加载 outfile: "./myapp", }, }); ``` *** ## 作为 Bun CLI 使用 此功能自 Bun v1.2.16 起支持 设置 `BUN_BE_BUN=1` 环境变量,可以将独立可执行文件作为 `bun` CLI 本身运行。该可执行文件会忽略其打包的入口点,转而提供完整的 `bun` CLI。 例如,考虑一个由以下脚本编译而成的可执行文件: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} echo "console.log(\"you shouldn't see this\");" > such-bun.js bun build --compile ./such-bun.js ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} [3ms] bundle 1 modules [89ms] compile such-bun ``` 通常,使用参数运行 `./such-bun` 时,会执行该脚本。 ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # 默认执行打包入口脚本 ./such-bun install ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} you shouldn't see this ``` 但是,使用 `BUN_BE_BUN=1` 环境变量后,它的行为就会像 `bun` 二进制文件一样: ```bash icon="terminal" terminal theme={"theme":{"light":"github-light","dark":"dracula"}} # 通过环境变量让可执行文件行为等同于 `bun` CLI BUN_BE_BUN=1 ./such-bun install ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} bun install v1.2.16-canary.1 (1d1db811) Checked 63 installs across 64 packages (no changes) [5.00ms] ``` 基于 Bun 构建的 CLI 工具可以利用此功能来安装软件包、打包依赖项或运行其他文件,而无需下载单独的二进制文件或安装 Bun。 ## 全栈可执行文件 此功能自 Bun v1.2.17 起支持 `--compile` 标志可以创建一个同时包含服务器和客户端代码的独立可执行文件,非常适合全栈应用。当你在服务器代码中导入 HTML 文件时,Bun 会将前端资源(JavaScript、CSS 等)打包并嵌入可执行文件中。 ```ts server.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import index from "./index.html"; const server = serve({ routes: { "/": index, "/api/hello": { GET: () => Response.json({ message: "Hello from API" }) }, }, }); console.log(`Server running at http://localhost:${server.port}`); ``` ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} 我的应用

你好,世界

``` ```ts app.ts icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Hello from the client!"); ``` ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-color: #f0f0f0; } ```
构建为单个可执行文件: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./server.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./server.ts"], compile: { outfile: "./myapp", }, }); ``` 生成的独立二进制包含: * 你的服务器代码 * Bun 运行时 * 所有前端资源(HTML、CSS、JavaScript) * 服务器用到的所有 npm 包 最终生成的是一个单独的文件,你可以将其部署到任何地方,而无需安装 Node.js、Bun 或任何依赖项: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ./myapp ``` Bun 会使用正确的 MIME 类型和缓存标头提供前端资源。HTML 导入会被替换为一个清单对象,`Bun.serve` 会使用该对象提供预先打包的资源。 有关构建全栈应用的更多信息,请参阅[全栈指南](/bundler/fullstack)。 *** ## Worker 使用独立可执行文件中的 Worker,需要将 Worker 入口也加入构建: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./index.ts ./my-worker.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts", "./my-worker.ts"], compile: { outfile: "./myapp", }, }); ``` 然后在代码中这样引用 Worker: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Hello from Bun!"); // 以下方式均可: new Worker("./my-worker.ts"); new Worker(new URL("./my-worker.ts", import.meta.url)); new Worker(new URL("./my-worker.ts", import.meta.url).href); ``` 向独立可执行文件添加多个入口时,每个入口都会分别打包到可执行文件中。 我们最终可能会自动检测 `new Worker(path)` 中静态已知的路径并将其自动打包,但目前你需要将 Worker 文件列为入口,就像前面的示例一样。 如果你使用相对路径指向未包含在独立可执行文件中的文件,Bun 会相对于进程当前工作目录从磁盘加载该路径;如果文件不存在,则会报错。 *** ## SQLite 使用 `bun:sqlite` 导入时,支持 `bun build --compile`。 默认情况下,数据库文件路径相对进程当前工作目录。 ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import db from "./my.db" with { type: "sqlite" }; console.log(db.query("select * from users LIMIT 1").get()); ``` 这意味着,如果可执行文件位于 `/usr/bin/hello`,且用户的终端当前位于 `/home/me/Desktop`,Bun 会查找 `/home/me/Desktop/my.db`。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} cd /home/me/Desktop ./hello ``` *** ## 嵌入静态资源及文件 独立可执行文件可以直接将文件嵌入二进制文件中,因此单个可执行文件就可以携带应用程序所需的图像、JSON 配置、模板或其他资源。 ### 机制原理 使用 `with { type: "file" }` [导入属性](https://github.com/tc39/proposal-import-attributes)来嵌入文件: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./icon.png" with { type: "file" }; console.log(icon); // 开发期间:"./icon.png" // 编译后:"/$bunfs/root/icon-a1b2c3d4.png"(内部路径) ``` 此导入返回**路径字符串**,指向嵌入文件。构建时 Bun 会: 1. 读取文件内容 2. 将数据嵌入可执行文件 3. 将导入替换为内部路径(以 `/$bunfs/` 为前缀) 你可以通过 `Bun.file()` 或 Node.js 的 `fs` API 读取嵌入文件。 ### 用 Bun.file() 读取嵌入文件 `Bun.file()` 是读取嵌入文件的推荐方式: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./icon.png" with { type: "file" }; import { file } from "bun"; // 以不同方式读取文件内容 const bytes = await file(icon).arrayBuffer(); // ArrayBuffer const text = await file(icon).text(); // 字符串(文本文件) const blob = file(icon); // Blob 对象 // 在响应中流式传输文件 export default { fetch(req) { return new Response(file(icon), { headers: { "Content-Type": "image/png" }, }); }, }; ``` ### 用 Node.js fs 读取嵌入文件 嵌入文件可以与 Node.js 文件系统 API 一起使用: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./icon.png" with { type: "file" }; import config from "./config.json" with { type: "file" }; import { readFileSync, promises as fs } from "node:fs"; // 同步读取 const iconBuffer = readFileSync(icon); // 异步读取 const configData = await fs.readFile(config, "utf-8"); const parsed = JSON.parse(configData); // 文件状态检查 const stats = await fs.stat(icon); console.log(`Icon size: ${stats.size} bytes`); ``` ### 实践示例 #### 嵌入 JSON 配置 ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import configPath from "./default-config.json" with { type: "file" }; import { file } from "bun"; // 加载内置默认配置 const defaultConfig = await file(configPath).json(); // 尝试加载用户配置,失败则为空对象 const userConfig = await file("./user-config.json") .json() .catch(() => ({})); const config = { ...defaultConfig, ...userConfig }; ``` #### HTTP 服务器中提供静态资源 用 Bun.serve() 的 static 路由高效提供静态文件: ```ts server.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import favicon from "./favicon.ico" with { type: "file" }; import logo from "./logo.png" with { type: "file" }; import styles from "./styles.css" with { type: "file" }; import { file, serve } from "bun"; serve({ static: { "/favicon.ico": file(favicon), "/logo.png": file(logo), "/styles.css": file(styles), }, fetch(req) { return new Response("Not found", { status: 404 }); }, }); ``` Bun 会自动处理静态路由的 Content-Type 头和缓存策略。 #### 嵌入模板文件 ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import templatePath from "./email-template.html" with { type: "file" }; import { file } from "bun"; async function sendWelcomeEmail(user: { name: string; email: string }) { const template = await file(templatePath).text(); const html = template.replace("{{name}}", user.name).replace("{{email}}", user.email); // 发送邮件,使用渲染后的模板... } ``` #### 嵌入二进制文件 ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import wasmPath from "./processor.wasm" with { type: "file" }; import fontPath from "./font.ttf" with { type: "file" }; import { file } from "bun"; // 加载 WebAssembly 模块 const wasmBytes = await file(wasmPath).arrayBuffer(); const wasmModule = await WebAssembly.instantiate(wasmBytes); // 读取字体二进制数据 const fontData = await file(fontPath).bytes(); ``` ### 嵌入 SQLite 数据库 要将 SQLite 数据库嵌入编译后的可执行文件,请在导入属性中设置 `type: "sqlite"`,并将 `embed` 属性设置为 `"true"`。 数据库文件必须已存在于磁盘。然后在代码中导入: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import myEmbeddedDb from "./my.db" with { type: "sqlite", embed: "true" }; console.log(myEmbeddedDb.query("select * from users LIMIT 1").get()); ``` 最后编译为独立可执行文件: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./index.ts --outfile mycli ``` 构建时数据库文件必须存在。`embed: "true"` 告诉打包器将数据库内容内嵌进可执行文件。常规运行 `bun run` 时数据库文件仍从磁盘加载。 在编译后的可执行文件中,嵌入的数据库为读写模式,但所有更改会在程序退出时丢失(内存存储)。 ### 嵌入 N-API 插件 可将 `.node` 文件嵌入可执行文件: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} const addon = require("./addon.node"); console.log(addon.hello()); ``` 如果使用 `@mapbox/node-pre-gyp` 或类似工具,必须直接 require `.node` 文件,否则无法正确打包。 ### 嵌入目录 使用 `--asset`(或 JavaScript API 中的 `compile.assets`)将文件或目录树嵌入可执行文件,并保留其原始相对路径。嵌入的文件在运行时位于 `import.meta.dir` 下,并且可以通过 `node:fs`(`existsSync`、`statSync`、`readdirSync`、`readFileSync`)和 `Bun.file()` 访问。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile ./index.ts --asset ./public --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], compile: { outfile: "./myapp", assets: ["./public"], }, }); ``` ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import fs from "node:fs"; import path from "node:path"; const publicDir = path.join(import.meta.dir, "public"); for (const entry of fs.readdirSync(publicDir, { withFileTypes: true })) { console.log(entry.name, entry.isDirectory() ? "(dir)" : fs.statSync(path.join(publicDir, entry.name)).size); } const html = await Bun.file(path.join(publicDir, "index.html")).text(); ``` 多次传递 `--asset` 可嵌入多个目录(例如,对于 SvelteKit 构建,可以使用 `--asset ./client --asset ./prerendered`)。只有普通文件会被嵌入;目录树中的符号链接和空子目录会被跳过。 你也可以通过将单个文件添加为额外入口点,以旧方式嵌入文件;导入的资源会根据 `--asset-naming` 重命名(默认为 `[name]-[hash].[ext]`): ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} import icon from "./public/assets/icon.png" with { type: "file" }; ``` ### 运行时检测独立模式 使用 `Bun.isStandaloneExecutable` 来检查当前进程是否正在从编译后的二进制文件运行: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} if (Bun.isStandaloneExecutable) { // 从 `bun build --compile` 的输出运行 } else { // 通过 `bun ` 运行,或作为库使用 } ``` 与 `Bun.embeddedFiles.length > 0` 不同,这种检查不会为每个嵌入文件分配 `Blob` 对象,因此即使二进制文件嵌入了大量资源,也可以安全地在启动时调用。 ### 列出嵌入文件 `Bun.embeddedFiles` 将所有嵌入文件以 `Blob` 对象的形式公开: ```ts index.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import "./icon.png" with { type: "file" }; import "./data.json" with { type: "file" }; import "./template.html" with { type: "file" }; import { embeddedFiles } from "bun"; // 列出所有嵌入文件 for (const blob of embeddedFiles) { console.log(`${blob.name} - ${blob.size} bytes`); } // 输出示例: // icon-a1b2c3d4.png - 4096 bytes // data-e5f6g7h8.json - 256 bytes // template-i9j0k1l2.html - 1024 bytes ``` `Bun.embeddedFiles` 中每个元素是带 `name` 属性的 `Blob`: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} embeddedFiles: ReadonlyArray; ``` 可以使用它通过 `static` 路由提供所有嵌入的资源: ```ts server.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import "./public/favicon.ico" with { type: "file" }; import "./public/logo.png" with { type: "file" }; import "./public/styles.css" with { type: "file" }; import { embeddedFiles, serve } from "bun"; // 根据嵌入文件构造静态路由 const staticRoutes: Record = {}; for (const blob of embeddedFiles) { // 去除文件名中的哈希:"icon-a1b2c3d4.png" -> "icon.png" const name = blob.name.replace(/-[a-f0-9]+\./, "."); staticRoutes[`/${name}`] = blob; } serve({ static: staticRoutes, fetch(req) { return new Response("Not found", { status: 404 }); }, }); ``` `Bun.embeddedFiles` 不含源码文件(`.ts`、`.js` 等)以保护源码。 #### 内容哈希 默认情况下,嵌入文件的名称会附加内容哈希,这有助于通过 URL 或 CDN 提供文件时实现缓存失效。若要保留原始名称,请配置资源命名方式: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --asset-naming="[name].[ext]" ./index.ts ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], compile: { outfile: "./myapp", }, naming: { asset: "[name].[ext]", }, }); ``` *** ## 代码压缩(Minification) 启用代码压缩以减少可执行文件体积: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --minify ./index.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], compile: { outfile: "./myapp", }, minify: true, // 启用全部压缩 }); // 或更细粒度控制: await Bun.build({ entrypoints: ["./index.ts"], compile: { outfile: "./myapp", }, minify: { whitespace: true, syntax: true, identifiers: true, }, }); ``` Bun 使用自身压缩器来减小代码体积。不过总体上 Bun 的二进制仍然偏大,未来还会优化。 *** ## Windows 平台特有标志 在 Windows 上编译独立可执行文件时,平台特定选项可以自定义生成的 `.exe` 文件的元数据: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # 自定义图标 bun build --compile --windows-icon=path/to/icon.ico ./app.ts --outfile myapp # 隐藏控制台窗口(GUI 应用) bun build --compile --windows-hide-console ./app.ts --outfile myapp ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], compile: { outfile: "./myapp", windows: { icon: "./path/to/icon.ico", hideConsole: true, // 额外的 Windows 元数据: title: "My Application", publisher: "My Company", version: "1.0.0", description: "A standalone Windows application", copyright: "Copyright 2024", }, }, }); ``` Windows 可用选项说明: * `icon` - 指定 `.ico` 图标文件路径 * `hideConsole` - 隐藏后台终端窗口(GUI 应用用) * `title` - 可执行文件属性中的应用标题 * `publisher` - 发布者名称 * `version` - 版本号字符串 * `description` - 描述信息 * `copyright` - 版权声明 除了 `hideConsole` 之外,这些标志不能在交叉编译时使用,因为它们依赖 Windows API。 *** ## macOS 代码签名 为独立可执行文件进行代码签名以消除 Gatekeeper 警告,使用 `codesign` 命令: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp ``` 推荐附带带有 JIT 权限的 `entitlements.plist` 文件: ```xml icon="xml" title="info.plist" theme={"theme":{"light":"github-light","dark":"dracula"}} com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-executable-page-protection com.apple.security.cs.allow-dyld-environment-variables com.apple.security.cs.disable-library-validation ``` 使用 `--entitlements` 标志来支持 JIT 权限: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp ``` 签名后验证: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} codesign -vvv --verify ./myapp ./myapp: valid on disk ./myapp: satisfies its Designated Requirement ``` 代码签名支持需要 Bun v1.2.4 及以上版本。 *** ## 代码拆分 独立可执行文件支持代码拆分。结合 `--compile` 和 `--splitting` 可生成带有运行时动态加载代码拆分块的可执行文件。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --splitting ./src/entry.ts --outfile ./build/entry ``` ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./src/entry.ts"], compile: true, splitting: true, outdir: "./build", }); ``` ```ts src/entry.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("Entrypoint loaded"); const lazy = await import("./lazy.ts"); lazy.hello(); ``` ```ts src/lazy.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function hello() { console.log("Lazy module loaded"); } ``` 运行编译结果: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} ./build/entry ``` 输出: ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} Entrypoint loaded Lazy module loaded ``` *** ## 使用插件 插件与独立可执行文件协同工作;使用它们在构建期间转换文件: ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const envPlugin: BunPlugin = { name: "env-loader", setup(build) { build.onLoad({ filter: /\.env\.json$/ }, async args => { // 将 .env.json 文件转换成已验证的配置对象 const env = await Bun.file(args.path).json(); return { contents: `export default ${JSON.stringify(env)};`, loader: "js", }; }); }, }; await Bun.build({ entrypoints: ["./cli.ts"], compile: { outfile: "./mycli", }, plugins: [envPlugin], }); ``` 使用场景示例 — 构建时嵌入环境配置: ```ts cli.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import config from "./config.env.json"; console.log(`Running in ${config.environment} mode`); console.log(`API endpoint: ${config.apiUrl}`); ``` 插件可以执行任何转换:编译 YAML/TOML 配置、内联 SQL 查询、生成类型安全的 API 客户端,或预处理模板。请参阅[插件文档](/bundler/plugins)。 *** ## 不支持的 CLI 参数 `--compile` 标志不支持以下标志: * `--outdir` — 请改用 `outfile`。 * `--public-path` * `--target=node` * `--target=browser`(不含 HTML 入口点 — 对于使用 `.html` 文件的 `--compile --target=browser`,请参阅[独立 HTML](/bundler/standalone-html)) * `--no-bundle` - Bun 始终会将所有内容捆绑到可执行文件中。 *** ## API 参考 `Bun.build()` 中的 `compile` 选项支持三种形式: ```ts title="类型" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} interface BuildConfig { entrypoints: string[]; compile: boolean | Bun.Build.CompileTarget | CompileBuildOptions; // ... 其他 BuildConfig 选项(minify、sourcemap、define、plugins 等) } interface CompileBuildOptions { target?: Bun.Build.CompileTarget; // 交叉编译目标 outfile?: string; // 输出可执行文件路径 assets?: string[]; // 要嵌入到 import.meta.dir 下的文件/目录 execArgv?: string[]; // 运行时参数(process.execArgv) autoloadTsconfig?: boolean; // 加载 tsconfig.json(默认:false) autoloadPackageJson?: boolean; // 加载 package.json(默认:false) autoloadDotenv?: boolean; // 加载 .env 文件(默认:true) autoloadBunfig?: boolean; // 加载 bunfig.toml(默认:true) windows?: { icon?: string; // .ico 文件路径 hideConsole?: boolean; // 隐藏控制台窗口 title?: string; // 应用标题 publisher?: string; // 发布者名称 version?: string; // 版本字符串 description?: string; // 描述 copyright?: string; // 版权声明 }; } ``` 用法示例: ```ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 简单布尔值 - 编译为当前平台,输出文件名为入口名 compile: true // 目标字符串 - 交叉编译,输出文件名为入口名 compile: "bun-linux-x64" // 详尽配置对象 - 指定输出文件和其他参数 compile: { target: "bun-linux-x64", outfile: "./myapp", } ``` ### 支持的目标 ```ts title="Bun.Build.CompileTarget" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} type CompileTarget = | "bun-darwin-x64" | "bun-darwin-x64-baseline" | "bun-darwin-arm64" | "bun-linux-x64" | "bun-linux-x64-baseline" | "bun-linux-x64-modern" | "bun-linux-arm64" | "bun-linux-x64-musl" | "bun-linux-arm64-musl" | "bun-windows-x64" | "bun-windows-x64-baseline" | "bun-windows-x64-modern" | "bun-windows-arm64"; ``` ### 完整示例 ```ts build.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "my-plugin", setup(build) { // 插件实现 }, }; const result = await Bun.build({ entrypoints: ["./src/cli.ts"], compile: { target: "bun-linux-x64", outfile: "./dist/mycli", execArgv: ["--smol"], autoloadDotenv: false, autoloadBunfig: false, }, minify: true, sourcemap: "linked", bytecode: true, define: { "process.env.NODE_ENV": JSON.stringify("production"), VERSION: JSON.stringify("1.0.0"), }, plugins: [myPlugin], }); if (result.success) { console.log("构建成功:", result.outputs[0].path); } ``` # 全栈开发服务器 Source: https://bun.zhcndoc.com/bundler/fullstack 使用 Bun 集成的开发服务器构建全栈应用,它能够打包前端资源并处理 API 路由 首先,导入 HTML 文件并将它们传递给 `Bun.serve()` 的 `routes` 选项。 ```ts title="app.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import dashboard from "./dashboard.html"; import homepage from "./index.html"; const server = serve({ routes: { // ** HTML 导入 ** // 将 index.html 打包并路由到 "/" // 这使用 HTMLRewriter 扫描 HTML 中的 ` ``` 经过处理后可能成为: ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} 首页
``` ## React 集成 要在客户端代码中使用 React,导入 `react-dom/client` 并渲染你的应用。 ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import dashboard from "../public/dashboard.html"; import { serve } from "bun"; serve({ routes: { "/": dashboard, }, async fetch(req) { // ...api 请求 return new Response("hello world"); }, }); ``` ```tsx title="src/frontend.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { createRoot } from "react-dom/client"; import App from "./app"; const container = document.getElementById("root"); const root = createRoot(container!); root.render(); ``` ```html title="public/dashboard.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} Dashboard
``` ```tsx title="src/app.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { useState } from "react"; export default function App() { const [count, setCount] = useState(0); return (

Dashboard

); } ```
## 开发模式 本地开发时,通过在 `Bun.serve()` 中设置 `development: true` 启用开发模式。 ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import homepage from "./index.html"; import dashboard from "./dashboard.html"; Bun.serve({ routes: { "/": homepage, "/dashboard": dashboard, }, development: true, fetch(req) { // ... API 请求 }, }); ``` ### 开发模式功能 当 `development` 为 `true` 时,Bun: * 在响应中包含 SourceMap 标头,以便开发者工具显示原始源代码 * 禁用代码压缩 * 每次请求 `.html` 文件时重新打包资源 * 启用热模块重载(除非设置了 `hmr: false`) ### 高级开发配置 要将浏览器中的控制台日志回显到终端,请在 `Bun.serve()` 的 `development` 对象中传入 `console: true`。 ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import homepage from "./index.html"; Bun.serve({ // development 也可以是一个对象 development: { // 启用热模块重载 hmr: true, // 将浏览器中的控制台日志回显到终端 console: true, }, routes: { "/": homepage, }, }); ``` Bun 通过现有的 HMR WebSocket 连接发送日志。 ### 开发 vs 生产 | 功能 | 开发环境 | 生产环境 | | --------------- | ------------ | ----- | | **Source maps** | ✅ 启用 | ❌ 禁用 | | **代码压缩** | ❌ 禁用 | ✅ 启用 | | **热重载** | ✅ 启用 | ❌ 禁用 | | **资源打包** | 🔄 每次请求 | 💾 缓存 | | **控制台日志** | 🖥️ 浏览器 → 终端 | ❌ 禁用 | | **错误详情** | 📝 详细 | 🔒 简洁 | ## 生产模式 热重载和 `development: true` 有助于快速迭代,但在生产环境中,你的服务器应尽可能快速,并尽量减少外部依赖。 ### 预构建打包(推荐) 从 Bun v1.2.17 开始,你可以使用 `Bun.build` 或 `bun build` 提前打包完整的全栈应用。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --target=bun --production --outdir=dist ./src/index.ts ``` 当 Bun 的打包器从服务器端代码中发现 HTML 导入时,它会将引用的 JavaScript/TypeScript/TSX/JSX 和 CSS 文件打包到一个清单对象中,供 `Bun.serve()` 用于提供资源。 ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import index from "./index.html"; serve({ routes: { "/": index }, }); ``` ### 运行时打包 如果你不想添加构建步骤,可以在 `Bun.serve()` 中设置 `development: false`。 这将: * 启用打包资源的内存缓存。Bun 会在首次请求 `.html` 文件时延迟打包资源,并将结果缓存到内存中,直到服务器重启。 * 启用 `Cache-Control` 和 `ETag` 响应头 * 压缩 JavaScript/TypeScript/TSX/JSX 文件 ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; import homepage from "./index.html"; serve({ routes: { "/": homepage, }, // 生产模式 development: false, }); ``` ## API 路由 ### HTTP 方法处理器 通过 HTTP 方法处理器定义 API 端点: ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { serve } from "bun"; serve({ routes: { "/api/users": { async GET(req) { // 处理 GET 请求 const users = await getUsers(); return Response.json(users); }, async POST(req) { // 处理 POST 请求 const userData = await req.json(); const user = await createUser(userData); return Response.json(user, { status: 201 }); }, async PUT(req) { // 处理 PUT 请求 const userData = await req.json(); const user = await updateUser(userData); return Response.json(user); }, async DELETE(req) { // 处理 DELETE 请求 await deleteUser(req.params.id); return new Response(null, { status: 204 }); }, }, }, }); ``` ### 动态路由 路由中使用 URL 参数: ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} serve({ routes: { // 单参数 "/api/users/:id": async req => { const { id } = req.params; const user = await getUserById(id); return Response.json(user); }, // 多参数 "/api/users/:userId/posts/:postId": async req => { const { userId, postId } = req.params; const post = await getPostByUser(userId, postId); return Response.json(post); }, // 通配符路由 "/api/files/*": async req => { const filePath = req.params["*"]; const file = await getFile(filePath); return new Response(file); }, }, }); ``` ### 请求处理 ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} serve({ routes: { "/api/data": { async POST(req) { // 解析 JSON 请求体 const body = await req.json(); // 访问请求头 const auth = req.headers.get("Authorization"); // 访问 URL 参数 const { id } = req.params; // 访问查询参数 const url = new URL(req.url); const page = url.searchParams.get("page") || "1"; // 返回响应 return Response.json({ message: "数据已处理", page: parseInt(page), authenticated: !!auth, }); }, }, }, }); ``` ## 插件 Bun 的打包器插件在打包静态路由时同样支持。 要为 `Bun.serve` 配置插件,在 `bunfig.toml` 的 `[serve.static]` 部分添加 `plugins` 数组。 ### TailwindCSS 插件 要使用 TailwindCSS,请安装 `tailwindcss` 包和 `bun-plugin-tailwind` 插件。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun add tailwindcss bun-plugin-tailwind ``` ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] plugins = ["bun-plugin-tailwind"] ``` 现在,你可以在 HTML 和 CSS 文件中使用 TailwindCSS 工具类。在项目中的某个位置导入 `tailwindcss`: ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` 或者在 CSS 文件中导入 TailwindCSS: ```css title="style.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} @import "tailwindcss"; .custom-class { @apply bg-red-500 text-white; } ``` ```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` ### 自定义插件 任何导出有效打包器插件对象(包含 `name` 和 `setup` 字段的对象)的 JS 文件或模块都可以放置在 plugins 数组中: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] plugins = ["./my-plugin-implementation.ts"] ``` ```ts title="my-plugin-implementation.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "my-custom-plugin", setup(build) { // 插件实现 build.onLoad({ filter: /\.custom$/ }, async args => { const text = await Bun.file(args.path).text(); return { contents: `export default ${JSON.stringify(text)};`, loader: "js", }; }); }, }; export default myPlugin; ``` Bun 会延迟解析并加载每个插件,并使用它们来打包你的路由。 插件位于 `bunfig.toml` 中,这样一来,`bun build` CLI 在支持插件后,就能静态确定正在使用哪些插件。 这些插件可在 `Bun.build()` 的 JS API 中使用,但目前还不能在 CLI 中使用。 ## 内联环境变量 Bun 可以在构建时将前端 JavaScript 和 TypeScript 中的 `process.env.*` 引用替换为其值。在 `bunfig.toml` 中配置 `env` 选项: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] env = "PUBLIC_*" # 仅内联以 PUBLIC_ 开头的环境变量(推荐) # env = "inline" # 内联所有环境变量 # env = "disable" # 禁用环境变量替换(默认) ``` 仅适用于字面量 `process.env.FOO` 引用,不支持 `import.meta.env` 或间接访问如 `const env = process.env; env.FOO`。 如果环境变量未设置,浏览器可能会报错,如 `ReferenceError: process is not defined`。 有关构建时配置和示例,请参阅 [HTML 和静态网站](/bundler/html-static#inline-environment-variables)。 ## Sourcemaps 在开发环境中,Bun 会为打包的路由生成链接的 sourcemaps,并将它们与 JavaScript 和 CSS 分块一起提供。在生产环境中(`development: false`),sourcemaps 默认处于禁用状态,因此服务器不会暴露你的原始源代码。 要覆盖默认设置,请在 `bunfig.toml` 中设置 `sourcemap` 选项: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] sourcemap = "linked" # serve sourcemaps in production too # sourcemap = "inline" # embed sourcemaps in the chunks # sourcemap = "external" # emit .map files without a sourceMappingURL comment # sourcemap = false # never generate sourcemaps ``` ## 工作原理 Bun 使用 `HTMLRewriter` 扫描 HTML 文件中的 ` ``` * 处理 CSS 导入及 `` 标签 * 合并 CSS 文件 * 重写 url 及资源路径,在 URL 中加入内容寻址哈希 ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` * 资源链接被重写以包含内容寻址哈希 * CSS 文件中的小资源被内联为 `data:` URL,减少 HTTP 请求数量 * 合并所有 ` ``` ```tsx title="src/main.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}} import { createRoot } from "react-dom/client"; import { App } from "./App"; const container = document.getElementById("root")!; const root = createRoot(container); root.render(); ``` ```tsx title="src/App.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}} import { useState, useEffect } from "react"; interface User { id: number; name: string; email: string; created_at: string; } export function App() { const [users, setUsers] = useState([]); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [loading, setLoading] = useState(false); const fetchUsers = async () => { const response = await fetch("/api/users"); const data = await response.json(); setUsers(data); }; const createUser = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { const response = await fetch("/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email }), }); if (response.ok) { setName(""); setEmail(""); await fetchUsers(); } else { const error = await response.json(); alert(error.error); } } catch (error) { alert("创建用户失败"); } finally { setLoading(false); } }; const deleteUser = async (id: number) => { if (!confirm("确定吗?")) return; try { const response = await fetch(`/api/users/${id}`, { method: "DELETE", }); if (response.ok) { await fetchUsers(); } } catch (error) { alert("删除用户失败"); } }; useEffect(() => { fetchUsers(); }, []); return (

用户管理

setName(e.target.value)} required /> setEmail(e.target.value)} required />

用户({users.length})

{users.map(user => (
{user.name}
{user.email}
))}
); } ``` ```css title="src/styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: #f5f5f5; color: #333; } .container { max-width: 800px; margin: 0 auto; padding: 2rem; } h1 { color: #2563eb; margin-bottom: 2rem; } .form { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; display: flex; gap: 1rem; flex-wrap: wrap; } .form input { flex: 1; min-width: 200px; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; } .form button { padding: 0.75rem 1.5rem; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer; } .form button:hover { background: #1d4ed8; } .form button:disabled { opacity: 0.5; cursor: not-allowed; } .users { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .user-card { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid #eee; } .user-card:last-child { border-bottom: none; } .delete-btn { padding: 0.5rem 1rem; background: #dc2626; color: white; border: none; border-radius: 4px; cursor: pointer; } .delete-btn:hover { background: #b91c1c; } ``` ## 最佳实践 ### 项目结构 ``` my-app/ ├── src/ │ ├── components/ │ │ ├── Header.tsx │ │ └── UserList.tsx │ ├── styles/ │ │ ├── globals.css │ │ └── components.css │ ├── utils/ │ │ └── api.ts │ ├── App.tsx │ └── main.tsx ├── public/ │ ├── index.html │ ├── dashboard.html │ └── favicon.ico ├── server/ │ ├── routes/ │ │ ├── users.ts │ │ └── auth.ts │ ├── db/ │ │ └── schema.sql │ └── index.ts ├── bunfig.toml └── package.json ``` ### 基于环境的配置 ```ts title="server/config.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export const config = { development: process.env.NODE_ENV !== "production", port: process.env.PORT || 3000, database: { url: process.env.DATABASE_URL || "./dev.db", }, cors: { origin: process.env.CORS_ORIGIN || "*", }, }; ``` ### 错误处理 ```ts title="server/middleware.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function errorHandler(error: Error, req: Request) { console.error("Server error:", error); if (process.env.NODE_ENV === "production") { return Response.json({ error: "Internal server error" }, { status: 500 }); } return Response.json( { error: error.message, stack: error.stack, }, { status: 500 }, ); } ``` ### API 响应辅助函数 ```ts title="server/utils.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function json(data: any, status = 200) { return Response.json(data, { status }); } export function error(message: string, status = 400) { return Response.json({ error: message }, { status }); } export function notFound(message = "Not found") { return error(message, 404); } export function unauthorized(message = "Unauthorized") { return error(message, 401); } ``` ### 类型安全 ```ts title="types/api.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export interface User { id: number; name: string; email: string; created_at: string; } export interface CreateUserRequest { name: string; email: string; } export interface ApiResponse { data?: T; error?: string; } ``` ## 部署 ### 生产构建 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # 构建生产版本 bun build --target=bun --production --outdir=dist ./server/index.ts # 运行生产服务器 NODE_ENV=production bun dist/index.js ``` ### Docker 部署 ```dockerfile title="Dockerfile" icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}} FROM oven/bun:1 as base WORKDIR /usr/src/app # 安装依赖 COPY package.json bun.lock ./ RUN bun install --frozen-lockfile # 复制源代码 COPY . . # 构建应用 RUN bun build --target=bun --production --outdir=dist ./server/index.ts # 生产阶段 FROM oven/bun:1-slim WORKDIR /usr/src/app COPY --from=base /usr/src/app/dist ./ COPY --from=base /usr/src/app/public ./public EXPOSE 3000 CMD ["bun", "index.js"] ``` ### 环境变量 ```ini title=".env.production" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} NODE_ENV=production PORT=3000 DATABASE_URL=postgresql://user:pass@localhost:5432/myapp CORS_ORIGIN=https://myapp.com ``` ## 从其他框架迁移 ### 从 Express + Webpack ```ts title="server.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 之前(Express + Webpack) app.use(express.static("dist")); app.get("/api/users", (req, res) => { res.json(users); }); // 之后(Bun 全栈) serve({ routes: { "/": homepage, // 替代 express.static "/api/users": { GET() { return Response.json(users); }, }, }, }); ``` ### 从 Next.js API 路由 ```ts title="server.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 之前(Next.js) export default function handler(req, res) { if (req.method === 'GET') { res.json(users); } } // 之后(Bun) "/api/users": { GET() { return Response.json(users); } } ``` ## 限制与未来计划 ### 当前限制 * 尚未实现 API 路由的自动发现 * 未内置服务端渲染(SSR) ### 规划功能 * 基于文件的 API 端点路由 * 内置 SSR 支持 * 更完善的插件生态系统 这项工作仍在进行中。功能和 API 可能会发生变化。 # 热重载 Source: https://bun.zhcndoc.com/bundler/hot-reloading Bun 开发服务器的热模块替换(HMR) 热模块替换(HMR)可以在不重新加载整个页面的情况下更新运行中应用程序的模块,同时保留应用程序状态。 在使用 Bun 全栈开发服务器时,HMR 默认启用。 ## `import.meta.hot` API 参考 Bun 实现了一个客户端 HMR API,其设计参考了 [Vite 的 `import.meta.hot` API](https://vite.dev/guide/api-hmr)。你可以通过 `if (import.meta.hot)` 检查它,在生产环境中该代码会被摇树优化移除。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} if (import.meta.hot) { // HMR API 可用。 } ``` 通常不需要进行此检查,因为 Bun 会在生产构建中消除对所有 HMR API 的调用。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 整个函数调用都会在生产环境中被移除。 import.meta.hot.dispose(() => { console.log("dispose"); }); ``` 为了让这正常工作,Bun 强制要求这些 API 必须直接调用,不能通过间接引用。也就是说,以下用法是无效的: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 无效:将 `hot` 赋值给变量 const hot = import.meta.hot; hot.accept(); // 无效:将 `import.meta` 赋值给变量 const meta = import.meta; meta.hot.accept(); console.log(meta.hot.data); // 无效:作为参数传递 doSomething(import.meta.hot.dispose); // 有效:必须直接调用完整表达式 "import.meta.hot.": import.meta.hot.accept(); // 有效:`data` 可以作为参数传递: doSomething(import.meta.hot.data); ``` HMR API 仍在开发中,部分功能尚未实现。要在 `Bun.serve` 中禁用 HMR,请将 development 选项设置为 `{ hmr: false }`。 ## API 方法 | Method | Status | Notes | | ------------------ | ------ | ---------------------------------- | | `hot.accept()` | ✅ | 表示可以平稳地替换热更新。 | | `hot.data` | ✅ | 在模块求值之间持久化数据。 | | `hot.dispose()` | ✅ | 添加一个回调函数,在模块即将被替换时运行。 | | `hot.invalidate()` | ❌ | | | `hot.on()` | ✅ | 添加事件监听器。 | | `hot.off()` | ✅ | 移除通过 `on` 添加的事件监听器。 | | `hot.send()` | ❌ | | | `hot.prune()` | 🚧 | 当前不会调用回调。 | | `hot.decline()` | ✅ | 空操作,用于匹配 Vite 的 `import.meta.hot`。 | ## import.meta.hot.accept() `accept()` 方法表示模块可以进行热替换。不带参数调用时,表示可以通过重新评估文件来替换此模块。热更新后,Bun 会自动修补该模块的导入者。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // index.ts import { getCount } from "./foo.ts"; console.log("count is ", getCount()); import.meta.hot.accept(); export function getNegativeCount() { return -getCount(); } ``` 这会为 `index.ts` 导入的所有文件创建一个热重载边界。每当保存 `foo.ts` 或其任何依赖项时,更新就会向上冒泡到 `index.ts`,使其重新评估。导入 `index.ts` 的文件随后会被修补,以导入新版本的 `getNegativeCount()`。如果只有 `index.ts` 被更新,则只会重新评估这一个文件,并复用 `foo.ts` 中的计数器。 将此功能与 `import.meta.hot.data` 结合使用,可以将状态从之前的模块传递到新模块。 当没有模块调用 `import.meta.hot.accept()`(并且没有 React Fast Refresh 或插件代替你调用它)时, 文件更新后页面会重新加载,同时控制台会显示哪些文件失效。如果依赖完整的页面重新加载更合理, 则可以安全地忽略此警告。 ### 带回调 传入回调时,`import.meta.hot.accept` 的行为与在 Vite 中相同。它不会修补此模块的导入者,而是使用新模块调用回调。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export const count = 0; import.meta.hot.accept(newModule => { if (newModule) { // 当出现语法错误时 newModule 会是 undefined console.log("updated: count is now ", newModule.count); } }); ``` 建议优先使用不带参数的 `import.meta.hot.accept()`;这样通常更容易理解代码。 ### 接受其它模块 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { count } from "./foo"; import.meta.hot.accept("./foo", () => { if (!newModule) return; console.log("updated: count is now ", count); }); ``` 表示可以接受某个依赖项的模块。依赖项更新时,Bun 会使用新模块调用回调。 ### 多依赖 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import.meta.hot.accept(["./foo", "./bar"], newModules => { // newModules 是数组,每项对应一个更新的模块 // 或者如果模块语法有错误则对应 undefined }); ``` 此变体接受一个依赖项数组。回调会接收更新后的模块;对于出现错误的模块,对应的值为 `undefined`。 ## import.meta.hot.data `import.meta.hot.data` 会在热替换过程中,将模块先前版本的状态传递给新版本。向 `import.meta.hot.data` 写入内容也会将模块标记为自行接受(等同于调用 `import.meta.hot.accept()`)。 ```tsx title="index.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { createRoot } from "react-dom/client"; import { App } from "./app"; const root = (import.meta.hot.data.root ??= createRoot(elem)); root.render(); // 复用已有 root ``` 在生产环境中,`data` 会被内联为 `{}`,因此不能用作状态持有。 对于有状态模块,推荐使用这种模式,因为 Bun 可以在生产环境中将 `{}.prop ??= value` 缩减为 `value`。 ## import.meta.hot.dispose() 绑定一个销毁回调。该回调在以下时机被调用: * 模块即将被替换(即新模块加载前) * 模块被卸载(所有对该模块的导入被移除,见 `import.meta.hot.prune()`) ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} const sideEffect = setupSideEffect(); import.meta.hot.dispose(() => { sideEffect.cleanup(); }); ``` 该回调不会在路由导航或浏览器标签关闭时调用。 返回一个 promise 会延迟模块替换,直到模块被销毁。所有销毁回调会并行调用。 ## import.meta.hot.prune() 绑定一个清理回调。在所有导入这个模块的引用被移除之后调用,但该模块之前已经加载过。 可使用它来清理模块加载时创建的资源。与 `import.meta.hot.dispose()` 不同,它与 `accept` 和 `data` 配合管理有状态资源时更加合适。以下是一个管理 WebSocket 的完整示例: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { something } from "./something"; // 初始化或复用 WebSocket 连接 export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin)); // 模块的所有导入被移除时,清理 WebSocket 连接 import.meta.hot.prune(() => { ws.close(); }); ``` 如果改用 `dispose`,WebSocket 将在每次热更新时关闭并重新打开。两种代码版本都能在导入的文件更新时避免页面重新加载。 ## import.meta.hot.on() 和 off() 使用 `on()` 和 `off()` 监听来自 HMR 运行时的事件。事件名称带有前缀,因此插件之间不会发生冲突。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import.meta.hot.on("bun:beforeUpdate", () => { console.log("热更新前"); }); ``` 当文件被替换时,所有相关事件监听器会自动移除。 ### 内置事件 | 事件 | 触发时机 | | ---------------------- | ----------------------------------------- | | `bun:beforeUpdate` | 热更新应用前。 | | `bun:afterUpdate` | 热更新应用后。 | | `bun:beforeFullReload` | 完整页面重新加载前。 | | `bun:beforePrune` | 清理回调调用前。 | | `bun:invalidate` | 使用 `import.meta.hot.invalidate()` 使模块失效时。 | | `bun:error` | 发生构建或运行时错误时。 | | `bun:ws:disconnect` | HMR WebSocket 连接断开时。这可能表示开发服务器已离线。 | | `bun:ws:connect` | HMR WebSocket 连接或重新连接时。 | 为了兼容 Vite,这些事件也可以使用 `vite:*` 前缀,而不是 `bun:*` 前缀。 # HTML 和静态站点 Source: https://bun.zhcndoc.com/bundler/html-static 使用 Bun 的打包工具构建静态站点、落地页和 web 应用 Bun 的打包工具原生支持 HTML。无需任何配置即可构建静态站点、落地页和 Web 应用:将 HTML 文件交给 Bun,它就会打包该文件所引用的脚本、样式表和资源。 ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` 要开始使用,只需将 HTML 文件传入 `bun`。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Press h + Enter to show shortcuts ``` 无需配置时,Bun 的开发服务器提供: * **自动打包** - 打包并提供你的 HTML、JavaScript 和 CSS * **多入口支持** - 处理多个 HTML 入口点和 glob 入口点 * **现代 JavaScript** - 默认支持 TypeScript 和 JSX * **智能配置** - 读取 `tsconfig.json` 中的路径、JSX 选项和实验性装饰器配置 * **插件** - 支持插件,包括 TailwindCSS * **ESM 和 CommonJS** - 在 JavaScript、TypeScript 和 JSX 文件中使用 ESM 和 CommonJS * **CSS 打包与压缩** - 打包来自 `` 标签和 `@import` 语句的 CSS * **资源管理** - 复制并生成哈希化的图像和资源,同时重写 JavaScript、CSS 和 HTML 中的资源路径 ## 单页应用 (SPA) 当你将一个 `.html` 文件传递给 Bun 时,Bun 会将其用作所有路径的回退路由。这适用于使用客户端路由的单页应用: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun index.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Press h + Enter to show shortcuts ``` 你的 React 或其他 SPA 无需任何配置即可运行。像 `/about` 和 `/users/123` 这样的路由会提供同一个 HTML 文件,因此由你的客户端路由器处理导航。 ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} 我的 SPA
``` ## 多页应用 (MPA) 有些项目有几个独立的路由或多个 HTML 文件作为入口点。要支持多个入口点,可以全部传给 `bun`: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html ./about.html ``` ```txt theme={"theme":{"light":"github-light","dark":"dracula"}} Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Routes: / ./index.html /about ./about.html Press h + Enter to show shortcuts ``` 这会提供: * `/` 路由对应 `index.html` * `/about` 路由对应 `about.html` ### 通配符模式 要指定多个文件,请使用以 `.html` 结尾的 glob 模式: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./**/*.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Routes: / ./index.html /about ./about.html Press h + Enter to show shortcuts ``` ### 路径规范化 Bun 根据所有文件中最长的公共前缀选择基础路径。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html ./about/index.html ./about/foo/index.html ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Routes: / ./index.html /about ./about/index.html /about/foo ./about/foo/index.html Press h + Enter to show shortcuts ``` ## JavaScript、TypeScript 和 JSX Bun 的转译器原生支持 JavaScript、TypeScript 和 JSX。请参阅[加载器](/bundler/loaders)。 Bun 的转译器也在运行时使用。 ### ES 模块与 CommonJS 您可以在 JavaScript、TypeScript 和 JSX 文件中使用 ESM 和 CommonJS。Bun 会自动转译并打包它们。 无需预先构建或单独优化步骤,全程同步完成。 请参阅[模块解析](/runtime/module-resolution)。 ## CSS Bun 的 CSS 解析器也是原生实现的(约 70,000 行 Rust 代码)。 它也是一个 CSS 打包器。你可以在 CSS 文件中使用 `@import` 导入其他 CSS 文件。 例如: ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} @import "./abc.css"; .container { background-color: blue; } ``` ```css abc.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-color: red; } ``` 最终输出为: ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-color: red; } .container { background-color: blue; } ``` ### CSS 中引用本地资源 ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-image: url("./logo.png"); } ``` Bun 会将 `./logo.png` 复制到输出目录,并重写 CSS 文件中的路径以包含内容哈希。 ```css styles.css icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} body { background-image: url("./logo-[ABC123].png"); } ``` ### 在 JavaScript 中导入 CSS 要将 CSS 文件与 JavaScript 文件关联,请在 JavaScript 文件中导入它。 ```ts app.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import "./styles.css"; import "./more-styles.css"; ``` 这会在输出目录中生成 `./app.css` 和 `./app.js`。从 JavaScript 导入的所有 CSS 文件都会被打包到每个入口点对应的单个 CSS 文件中。如果从多个 JavaScript 文件中导入同一个 CSS 文件,它在输出 CSS 文件中只会被包含一次。 ## 插件 开发服务器支持插件。 ### Tailwind CSS 要使用 TailwindCSS,安装 `bun-plugin-tailwind` 插件: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # 或任意 npm 客户端 bun install --dev bun-plugin-tailwind ``` 然后,将插件添加到你的 `bunfig.toml`: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] plugins = ["bun-plugin-tailwind"] ``` 然后,在 HTML 中通过 `` 标签、CSS 中的 `@import`,或 JavaScript 中的导入来引用 TailwindCSS。 ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} ``` ```css title="styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} @import "tailwindcss"; ``` ```ts title="app.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import "tailwindcss"; ``` 只需要其中一种方式,无需全部使用。 ## 内联环境变量 Bun 可以在构建时将 JavaScript 和 TypeScript 中的 `process.env.*` 引用替换为其实际值。你可以使用此功能将 API URL 或功能标志等配置注入前端代码。 ### 开发服务器(运行时) 使用 `bun ./index.html` 时,可在 `bunfig.toml` 中配置 `env` 选项以内联环境变量: ```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}} [serve.static] env = "PUBLIC_*" # 仅内联以 PUBLIC_ 开头的环境变量(推荐) # env = "inline" # 内联所有环境变量 # env = "disable" # 禁用环境变量替换(默认) ``` 仅支持直接字面量的 `process.env.FOO`,不支持 `import.meta.env` 或间接访问如 `const env = process.env; env.FOO`。 若环境变量未设置,浏览器可能会出现运行时错误,如 `ReferenceError: process is not defined`。 然后运行开发服务器: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} PUBLIC_API_URL=https://api.example.com bun ./index.html ``` ### 生产构建 构建静态 HTML 生产版本时,使用 `env` 选项内联环境变量: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} # 内联所有环境变量 bun build ./index.html --outdir=dist --env=inline # 仅内联带指定前缀的环境变量(推荐) bun build ./index.html --outdir=dist --env=PUBLIC_* ``` ```ts title="build.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} // 内联所有环境变量 await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", env: "inline", // [!code highlight] }); // 仅内联带指定前缀的环境变量(推荐) await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", env: "PUBLIC_*", // [!code highlight] }); ``` ### 示例 给定以下源文件: ```ts title="app.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} const apiUrl = process.env.PUBLIC_API_URL; console.log(`API URL: ${apiUrl}`); ``` 使用 `PUBLIC_API_URL=https://api.example.com` 运行: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} PUBLIC_API_URL=https://api.example.com bun build ./index.html --outdir=dist --env=PUBLIC_* ``` 捆绑后的输出包含: ```js title="dist/app.js" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/javascript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=14ec2cbdf78f597f421d87adcffda9ac" theme={"theme":{"light":"github-light","dark":"dracula"}} const apiUrl = "https://api.example.com"; console.log(`API URL: ${apiUrl}`); ``` ## 将浏览器控制台日志回显至终端 Bun 的开发服务器可以将浏览器中的控制台日志流式传输到终端。要启用此功能,请传入 `--console` CLI 标志。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun ./index.html --console ``` ``` Bun v1.3.3 ready in 6.62ms → http://localhost:3000/ Press h + Enter to show shortcuts ``` 每次调用 `console.log` 或 `console.error` 时,日志都会广播到启动服务器的终端,因此浏览器错误会显示在运行服务器的同一位置。这也有助于监视终端输出的 AI 代理。 在内部,此功能会复用热模块替换(HMR)现有的 WebSocket 连接来发送日志。 ## 在浏览器中编辑文件 Bun 的前端开发服务器支持 Chrome DevTools 中的自动工作区文件夹,因此你可以从浏览器保存对文件的编辑。 ## 键盘快捷键 服务器运行时: * `o + Enter` - 在浏览器中打开 * `c + Enter` - 清除控制台 * `q + Enter` 或 `Ctrl+C` - 退出服务器。 ## 生产构建 准备部署时,使用 `bun build` 创建优化的生产包: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.html --minify --outdir=dist ``` ```ts title="build.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", minify: true, }); ``` 插件仅支持通过 `Bun.build` 的 API 使用,或通过带有前端开发服务器的 `bunfig.toml` 使用,不支持通过 `bun build` 的 CLI 使用。 ### 监听模式 运行 `bun build --watch` 以监听更改并自动重新构建。这非常适合库开发。 你从未见过如此快速的监听模式。 ## 插件 API 如需进行更多控制,可以通过 JavaScript API 配置打包器,并使用 Bun 内置的 `HTMLRewriter` 预处理 HTML。 ```ts title="build.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.html"], outdir: "./dist", minify: true, plugins: [ { // 一个将所有 HTML 标签名改为小写的插件 name: "lowercase-html-plugin", setup({ onLoad }) { const rewriter = new HTMLRewriter().on("*", { element(element) { element.tagName = element.tagName.toLowerCase(); }, text(element) { element.replace(element.text.toLowerCase()); }, }); onLoad({ filter: /\.html$/ }, async args => { const html = await Bun.file(args.path).text(); return { // Bun 的打包器会自动扫描 HTML 中的 ``` Bun 会输出包含已打包资源的新 HTML 文件: ```html title="dist/index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}} 本地图片 外部图片 ``` 该加载器使用 [`lol-html`](https://github.com/cloudflare/lol-html) 将 script 和 link 标签提取为入口点,并将其他资源作为外部资源处理。 选择器包括: * `audio[src]` * `img[src]` * `img[srcset]` * `link[as='font'][href], link[type^='font/'][href]` * `link[as='image'][href]` * `link[as='style'][href]` * `link[as='video'][href], link[as='audio'][href]` * `link[as='worker'][href]` * `link[rel='icon'][href], link[rel='apple-touch-icon'][href]` * `link[rel='manifest'][href]` * `link[rel='stylesheet'][href]` * `script[src]` * `source[src]` * `source[srcset]` * `video[poster]` * `video[src]` **HTML 加载器在不同场景下的行为** `html` 加载器的行为取决于它的使用方式: * 静态构建:运行 `bun build ./index.html` 时,Bun 会生成一个包含所有已打包并哈希处理资源的静态网站。 * 运行时:运行 `bun run server.ts` 时(其中 `server.ts` 导入了 HTML 文件),Bun 会在开发过程中即时打包资源,从而支持热模块替换等功能。 * 全栈构建:运行 `bun build --target=bun server.ts` 时(其中 `server.ts` 导入了 HTML 文件),该导入会解析为一个清单对象,`Bun.serve` 会使用它在生产环境中提供预先打包的资源。 *** ### `css` **CSS 加载器。** 默认用于 `.css`。 CSS 文件可以直接导入。打包器会解析并打包这些文件,处理 `@import` 语句和 `url()` 引用。 ```js theme={"theme":{"light":"github-light","dark":"dracula"}} import "./styles.css"; ``` 打包时,所有导入的 CSS 文件会合并成一个单独的 `.css` 文件输出到目标目录。 ```css theme={"theme":{"light":"github-light","dark":"dracula"}} .my-class { background: url("./image.png"); } ``` *** ### `sh` **Bun Shell 加载器。** 默认用于 `.sh` 文件。 该加载器会解析 Bun Shell 脚本。它仅在启动 Bun 本身时受支持,因此无法在打包器或运行时中使用。 ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun run ./script.sh ``` *** ### `file` **文件加载器。** 默认用于所有未识别的文件类型。 该加载器将导入解析为导入文件的路径或 URL,通常用于引用媒体或字体资源。 ```js theme={"theme":{"light":"github-light","dark":"dracula"}} // logo.ts import logo from "./logo.svg"; console.log(logo); ``` 在运行时,Bun 会检查 `logo.svg` 是否存在,并将导入解析为磁盘上的绝对路径。 ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun run logo.ts # 输出: /path/to/project/logo.svg ``` 在打包器中,文件会原样复制到 `outdir`,导入则解析为指向所复制文件的相对路径。 ```js theme={"theme":{"light":"github-light","dark":"dracula"}} // 输出 var logo = "./logo.svg"; console.log(logo); ``` 如果设置了 `publicPath`,导入会使用其值作为前缀,以构造绝对路径或 URL。 | 公共路径 | 解析后的导入 | | ---------------------------- | ---------------------------------- | | `""`(默认) | `./logo.svg` | | `"/assets/"` | `/assets/logo.svg` | | `"https://cdn.example.com/"` | `https://cdn.example.com/logo.svg` | 复制文件的位置和文件名由 `naming.asset` 的值决定。 # 宏 Source: https://bun.zhcndoc.com/bundler/macros 使用 Bun 宏在打包时运行 JavaScript 函数 宏是会在打包时运行的 JavaScript 函数。它们的返回值会直接内联到你的打包文件中。 作为一个简单示例,考虑下面这个返回随机数的函数。 ```ts title="random.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function random() { return Math.random(); } ``` 这是一个普通文件中的普通函数,但你可以将它用作宏: ```tsx title="cli.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { random } from "./random.ts" with { type: "macro" }; console.log(`你的随机数是 ${random()}`); ``` 宏使用导入属性语法进行标记。这是一项处于第 3 阶段的 TC39 提案,用于向导入语句附加额外的元数据。 使用 `bun build` 打包该文件。打包后的文件会输出到 stdout。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./cli.tsx ``` ```js theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(`你的随机数是 ${0.6805550949689833}`); ``` `random` 函数的源代码不会出现在打包文件中的任何位置。相反,它会在打包过程中运行,并且调用(`random()`)会被替换为其结果。由于源代码从未包含在打包文件中,因此宏可以安全地执行读取数据库等需要特权的操作。 ## 何时使用宏 对于那些原本需要编写一次性构建脚本来完成的小任务,在打包时执行代码可能更易于维护。它与其余代码共存,与其余构建过程一同运行,会自动并行执行,而且如果它失败,构建也会失败。 不过,如果你发现自己在打包时运行大量代码,考虑改为运行服务器。 ## 导入属性 宏是带有以下注解之一的导入语句: * `with { type: 'macro' }` — 导入属性,一项处于第 3 阶段的 ECMAScript 提案 * `assert { type: 'macro' }` — 导入断言,这是导入属性的早期形式,现已被弃用(但已得到许多浏览器和运行时的支持) ## 安全注意事项 必须使用 `{ type: "macro" }` 显式导入宏,才能在打包时运行。如果未调用这些宏导入,它们不会产生任何影响;这与可能产生副作用的常规 JavaScript 导入不同。 你可以使用 `--no-macros` 标志完全禁用宏。它会产生如下构建错误: ``` error: Macros are disabled foo(); ^ ./hello.js:3:1 53 ``` 为减少恶意包的潜在攻击面,宏不能从 `node_modules/**/*` 内部调用。如果包试图调用宏,会看到如下错误: ``` error: For security reasons, macros cannot be run from node_modules. beEvil(); ^ node_modules/evil/index.js:3:1 50 ``` 你的应用代码仍然可以从 `node_modules` 引入宏并调用它们。 ```ts title="cli.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { macro } from "some-package" with { type: "macro" }; macro(); ``` ## 导出条件 "macro" 当将包含宏的库发布到 npm 或其他包注册表时,使用 `"macro"` 导出条件,为宏环境提供专用的包版本。 ```json title="package.json" icon="file-json" theme={"theme":{"light":"github-light","dark":"dracula"}} { "name": "my-package", "exports": { "import": "./index.js", "require": "./index.js", "default": "./index.js", "macro": "./index.macro.js" } } ``` 这个配置允许用户使用相同的导入标识符在运行时或打包时消费你的包: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import pkg from "my-package"; // 运行时导入 import { macro } from "my-package" with { type: "macro" }; // 宏导入 ``` 第一个导入解析为 `./node_modules/my-package/index.js`;Bun 的打包器会将第二个导入解析为 `./node_modules/my-package/index.macro.js`。 ## 执行 当 Bun 的转译器看到宏导入时,它会使用 Bun 的 JavaScript 运行时调用该函数,并将返回值转换为 AST 节点。 宏会在访问阶段由转译器同步运行,此时插件尚未运行,转译器也尚未生成 AST。宏按照导入顺序运行。转译器会等待每个宏完成后再继续,并等待宏返回的任何 Promise。 Bun 的打包器是多线程的,因此宏会在多个生成的 JavaScript“工作线程”中并行执行。 ## 死代码消除 打包器会在运行并内联宏之后执行死代码消除。给定以下宏: ```ts title="returnFalse.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function returnFalse() { return false; } ``` ……在启用 `minify` 语法选项的情况下,打包以下文件会生成一个空的 bundle。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { returnFalse } from "./returnFalse.ts" with { type: "macro" }; if (returnFalse()) { console.log("这段代码会被消除"); } ``` ## 可序列化性 Bun 的转译器必须能够序列化宏的结果,以便将其内联到 AST 中。所有与 JSON 兼容的数据结构都受支持: ```ts title="macro.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getObject() { return { foo: "bar", baz: 123, array: [1, 2, { nested: "value" }], }; } ``` 宏可以是异步的,也可以返回 Promise 实例。Bun 的转译器会等待 Promise,并将结果内联。 ```ts title="macro.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export async function getText() { return "异步值"; } ``` 转译器实现了特殊逻辑,用于序列化 `Response` 和 `Blob` 等常见数据格式。 * **Response**:Bun 会读取 `Content-Type` 并据此进行序列化;例如,类型为 `application/json` 的 Response 会被解析为对象,而 `text/plain` 会被内联为字符串。类型无法识别或未定义的 Response 会被编码为 base64。 * **Blob**:与 Response 一样,序列化方式取决于 `type` 属性。 `fetch` 返回的是 `Promise`,可以直接返回。 ```ts title="macro.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getObject() { return fetch("https://bun.com"); } ``` 函数和大多数类的实例(之前列出的实例除外)都无法序列化。 ```ts title="macro.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getText(url: string) { // 这行不行! return () => {}; } ``` ## 参数 宏可以接受输入,但仅限于有限情况。参数值必须是静态已知的。例如,以下用法不被允许: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { getText } from "./getText.ts" with { type: "macro" }; export function howLong() { // `foo` 的值不能被静态获知 const foo = Math.random() ? "foo" : "bar"; const text = getText(`https://example.com/${foo}`); console.log("页面长度为 ", text.length, " 字符"); } ``` 但是,如果 `foo` 的值在打包时已知(例如,它是一个常量或另一个宏的结果),那么这种用法就是允许的: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { getText } from "./getText.ts" with { type: "macro" }; import { getFoo } from "./getFoo.ts" with { type: "macro" }; export function howLong() { // 这里有效,因为 getFoo() 是静态已知的 const foo = getFoo(); const text = getText(`https://example.com/${foo}`); console.log("页面长度为", text.length, "字符"); } ``` 其输出为: ```js theme={"theme":{"light":"github-light","dark":"dracula"}} function howLong() { console.log("页面长度为", 1322, "字符"); } export { howLong }; ``` ## 示例 ### 嵌入最新 git 提交哈希 ```ts title="getGitCommitHash.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export function getGitCommitHash() { const { stdout } = Bun.spawnSync({ cmd: ["git", "rev-parse", "HEAD"], stdout: "pipe", }); return stdout.toString(); } ``` 构建时,`getGitCommitHash` 调用会被替换为调用该函数的结果: ```ts input theme={"theme":{"light":"github-light","dark":"dracula"}} import { getGitCommitHash } from "./getGitCommitHash.ts" with { type: "macro" }; console.log(`当前 Git 提交哈希是 ${getGitCommitHash()}`); ``` ```ts output theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(`当前 Git 提交哈希是 3ee3259104e4507cf62c160f0ff5357ec4c7a7f8`); ``` 你可能在想 “为什么不直接用 `process.env.GIT_COMMIT_HASH`?”可以用,但你能用环境变量做下面这个吗? ### 在打包时执行 fetch() 请求 此示例使用 `fetch()` 发出 HTTP 请求,使用 `HTMLRewriter` 解析 HTML 响应,并在打包时返回一个包含标题和 meta 标签的对象。 ```ts title="meta.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} export async function extractMetaTags(url: string) { const response = await fetch(url); const meta = { title: "", }; new HTMLRewriter() .on("title", { text(element) { meta.title += element.text; }, }) .on("meta", { element(element) { const name = element.getAttribute("name") || element.getAttribute("property") || element.getAttribute("itemprop"); if (name) meta[name] = element.getAttribute("content"); }, }) .transform(response); return meta; } ``` `extractMetaTags` 函数会在打包时被移除,并替换为函数调用的结果:fetch 请求会在打包时执行,结果会被嵌入到打包产物中。由于抛出错误的分支不可达,因此也会被移除。 ```jsx input theme={"theme":{"light":"github-light","dark":"dracula"}} import { extractMetaTags } from "./meta.ts" with { type: "macro" }; export const Head = () => { const headTags = extractMetaTags("https://example.com"); if (headTags.title !== "Example Domain") { throw new Error("预期标题应为 'Example Domain'"); } return ( {headTags.title} ); }; ``` ```jsx output theme={"theme":{"light":"github-light","dark":"dracula"}} export const Head = () => { const headTags = { title: "Example Domain", viewport: "width=device-width, initial-scale=1", }; return ( {headTags.title} ); }; ``` # Minifier Source: https://bun.zhcndoc.com/bundler/minifier 使用 Bun 的 JavaScript 和 TypeScript 压缩器减少包大小 Bun 内置了一个快速的 JavaScript 和 TypeScript 压缩器,可以根据代码库情况将包大小减少 80% 甚至更多,同时提升输出代码的运行速度。该压缩器执行了数十种优化,包括常量折叠、死代码消除和语法转换。与其他压缩器不同,Bun 的压缩器还能让 `bun build` 运行更快,因为打印的代码更少。 ## CLI 用法 ### 启用所有压缩 使用 `--minify` 标志启用所有压缩模式: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --minify --outfile=out.js ``` `--minify` 标志启用: * 空白字符压缩 * 语法压缩 * 标识符压缩 ### 生产模式 `--production` 标志会自动启用压缩: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --production --outfile=out.js ``` `--production` 标志还会: * 设置 `process.env.NODE_ENV` 为 `production` * 启用生产模式下的 JSX 导入和转换 ### 细粒度控制 分别启用特定的压缩模式: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} # 仅移除空白字符 bun build ./index.ts --minify-whitespace --outfile=out.js # 仅压缩语法 bun build ./index.ts --minify-syntax --outfile=out.js # 仅压缩标识符 bun build ./index.ts --minify-identifiers --outfile=out.js # 组合特定模式 bun build ./index.ts --minify-whitespace --minify-syntax --outfile=out.js ``` ## JavaScript API 使用 Bun 的打包器进行编程时,通过 `minify` 选项配置压缩: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], outdir: "./out", minify: true, // 启用所有压缩模式 }); ``` 细粒度控制则传入对象: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], outdir: "./out", minify: { whitespace: true, syntax: true, identifiers: true, }, }); ``` ## 压缩模式 Bun 的压缩器有三种彼此独立的模式,你可以分别或同时启用。 ### 空白字符压缩(`--minify-whitespace`) 移除输出中的所有多余空白字符、换行和格式。 ### 语法压缩(`--minify-syntax`) 将 JavaScript 语法重写为更短的等效形式,并执行常量折叠、死代码消除及其他优化。 ### 标识符压缩(`--minify-identifiers`) 根据使用频率重命名局部变量和函数名为更短的标识符。 ## 所有转换。 ### 布尔字面量缩短 **模式:** `--minify-syntax` 将布尔字面量转换为更短的表达式。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} true false ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} !0 !1 ``` ### 布尔代数优化 **模式:** `--minify-syntax` 通过逻辑规则简化布尔表达式。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} !!x x === true x && true x || false !true !false ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x x x x !1 !0 ``` ### undefined 缩短 **模式:** `--minify-syntax` 用更短的等效表达式替换 `undefined`。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} undefined let x = undefined; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} void 0 let x=void 0; ``` ### undefined 相等性优化 **模式:** `--minify-syntax` 优化与 undefined 的宽松相等检查。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} x == undefined x != undefined ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x == null x != null ``` ### Infinity 缩短 **模式:** `--minify-syntax` 将 Infinity 转换为数学表达式。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} Infinity -Infinity ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} 1/0 -1/0 ``` ### typeof 优化 **模式:** `--minify-syntax` 优化 typeof 对比并求值常量 typeof 表达式。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} typeof x === 'undefined' typeof x !== 'undefined' typeof require typeof null typeof true typeof 123 typeof "str" typeof 123n ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} typeof x>'u' typeof x<'u' "function" "object" "boolean" "number" "string" "bigint" ``` ### 数字格式化 **模式:** `--minify-syntax` 以最紧凑形式格式化数字。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 10000 100000 1000000 1.0 -42.0 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 1e4 1e5 1e6 1 -42 ``` ### 算术常量折叠 **模式:** `--minify-syntax` 编译时计算算术运算。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 1 + 2 10 - 5 3 * 4 10 / 2 10 % 3 2 ** 3 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 3 5 12 5 1 8 ``` ### 位运算常量折叠 **模式:** `--minify-syntax` 编译时计算位运算。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 5 & 3 5 | 3 5 ^ 3 8 << 2 32 >> 2 ~5 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 1 7 6 32 8 -6 ``` ### 字符串拼接 **模式:** `--minify-syntax` 编译时合并字符串字面量。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "a" + "b" "x" + 123 "foo" + "bar" + "baz" ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "ab" "x123" "foobarbaz" ``` ### 字符串索引 **模式:** `--minify-syntax` 编译时计算字符串字符访问。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "foo"[2] "hello"[0] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "o" "h" ``` ### 模板字面量折叠 **模式:** `--minify-syntax` 编译时计算带常量表达式的模板字面量。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} `a${123}b` `result: ${5 + 10}` ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "a123b" "result: 15" ``` ### 模板字面量转字符串 **模式:** `--minify-syntax` 将不包含插值的模板字面量转换为普通字符串。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} `Hello World` `Line 1 Line 2` ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} "Hello World" "Line 1\nLine 2" ``` ### 字符串引号优化 **模式:** `--minify-syntax` 选择最优引号以减少转义。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "It's a string" 'He said "hello"' `Simple string` ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "It's a string" 'He said "hello"' "Simple string" ``` ### 数组展开内联 **模式:** `--minify-syntax` 内联对常量数组的数组展开操作。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} [1, ...[2, 3], 4] [...[a, b]] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} [1,2,3,4] [a,b] ``` ### 数组索引 **模式:** `--minify-syntax` 编译时计算常量数组访问。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} [x][0] ['a', 'b', 'c'][1] ['a', , 'c'][1] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x 'b' void 0 ``` ### 属性访问优化 **模式:** `--minify-syntax` 可用时将括号访问转换为点访问。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} obj["property"] obj["validName"] obj["123"] obj["invalid-name"] ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} obj.property obj.validName obj["123"] obj["invalid-name"] ``` ### 比较折叠 **模式:** `--minify-syntax` 编译时计算常量比较。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} 3 < 5 5 > 3 3 <= 3 5 >= 6 "a" < "b" ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} !0 !0 !0 !1 !0 ``` ### 逻辑操作折叠 **模式:** `--minify-syntax` 简化带常量值的逻辑操作。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} true && x false && x true || x false || x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x !1 !0 x ``` ### 空值合并折叠 **模式:** `--minify-syntax` 编译时计算已知值的空值合并操作。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} null ?? x undefined ?? x 42 ?? x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x x 42 ``` ### 逗号表达式简化 **模式:** `--minify-syntax` 移除无副作用的逗号序列表达式。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} (0, x) (123, "str", x) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x x ``` ### 三元条件折叠 **模式:** `--minify-syntax` 编译时计算常量条件的三元表达式。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} true ? a : b false ? a : b x ? true : false x ? false : true ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} a b x ? !0 : !1 x ? !1 : !0 ``` ### 一元表达式折叠 **模式:** `--minify-syntax` 简化一元操作。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} +123 +"123" -(-x) ~~x !!x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 123 123 - -x ~~x !!x ``` ### 双重否定移除 **模式:** `--minify-syntax` 移除不必要的双重否定。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} !!x !!!x ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x !x ``` ### if 语句优化 **模式:** `--minify-syntax` 优化常量条件的 if 语句。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (true) x; if (false) x; if (x) { a; } if (x) {} else y; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x; // 移除 if(x)a; if(!x)y; ``` ### 死代码消除 **模式:** `--minify-syntax` 移除不可达和无副作用代码。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (false) { unreachable(); } function foo() { return x; deadCode(); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function foo(){return x} ``` ### 不可达分支移除 **模式:** `--minify-syntax` 移除永远不会执行的分支。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} while (false) { neverRuns(); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} // 完全移除 ``` ### 空块移除 **模式:** `--minify-syntax` 移除空块和多余的大括号。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} { } if (x) { } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} ; // 移除 ``` ### 单语句块展开 **模式:** `--minify-syntax` 移除单语句块多余的大括号。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (condition) { doSomething(); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} if(condition)doSomething(); ``` ### TypeScript 枚举内联 **模式:** `--minify-syntax` 编译时内联 TypeScript 枚举值。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} enum Color { Red, Green, Blue } const x = Color.Red; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} const x=0; ``` ### 纯净标注支持 **模式:** 始终生效 遵循 `/*@__PURE__*/` 标注以进行摇树优化(移除未使用的代码)。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} const x = /*@__PURE__*/ expensive(); // 如果 x 未使用... ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} // 完全移除 ``` ### 标识符重命名 **模式:** `--minify-identifiers` 根据使用频率重命名局部变量为更短的名称。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function calculateSum(firstNumber, secondNumber) { const result = firstNumber + secondNumber; return result; } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function a(b,c){const d=b+c;return d} ``` **命名策略:** * 使用频率最高的标识符获得最短名称(a, b, c…) * 单字母:a-z(共 26 个) * 双字母:aa-zz(676 个) * 必要时使用三字母及以上 **保留标识符:** * JavaScript 关键字和保留字 * 全局标识符 * 命名导出(保持 API 稳定) * CommonJS 名称:`exports`、`module`。 ### 空白字符移除 **模式:** `--minify-whitespace` 移除所有多余空白。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function add(a, b) { return a + b; } let x = 10; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function add(a,b){return a+b;}let x=10; ``` ### 分号优化 **模式:** `--minify-whitespace` 仅在必要时插入分号。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} let a = 1; let b = 2; return a + b; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} let a=1;let b=2;return a+b ``` ### 操作符空格移除 **模式:** `--minify-whitespace` 移除操作符周围空格。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} a + b x = y * z foo && bar || baz ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} a+b x=y*z foo&&bar||baz ``` ### 注释移除 **模式:** `--minify-whitespace` 移除注释,但保留重要的许可证注释。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} // 该注释被移除 /* 这个也是 */ /*! 但该许可证注释被保留 */ function test() { /* 内联注释 */ } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} /*! 但该许可证注释被保留 */ function test(){} ``` ### 对象和数组格式化 **模式:** `--minify-whitespace` 移除对象和数组字面量内空白。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} const obj = { name: "John", age: 30 }; const arr = [1, 2, 3]; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} const obj={name:"John",age:30};const arr=[1,2,3]; ``` ### 控制流格式化 **模式:** `--minify-whitespace` 移除控制结构内空白。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} if (condition) { doSomething(); } for (let i = 0; i < 10; i++) { console.log(i); } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} if(condition)doSomething();for(let i=0;i<10;i++)console.log(i); ``` ### 函数格式化 **模式:** `--minify-whitespace` 移除函数声明内空白。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function myFunction(param1, param2) { return param1 + param2; } const arrow = (a, b) => a + b; ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function myFunction(param1,param2){return param1+param2}const arrow=(a,b)=>a+b; ``` ### 括号最小化 **模式:** 始终生效 仅在优先级需要时添加括号。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} (a + b) * c a + (b * c) ((x)) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} (a+b)*c a+b*c x ``` ### 模板字面量值折叠 **模式:** `--minify-syntax` 将非字符串插值值转为字符串并折叠进模板。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} `hello ${123}` `value: ${true}` `result: ${null}` `status: ${undefined}` `big: ${10n}` ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "hello 123" "value: true" "result: null" "status: undefined" "big: 10" ``` ### 字符串长度常量折叠 **模式:** `--minify-syntax` 编译时计算字符串字面量的 `.length` 属性。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} "hello world".length "test".length ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} 11 4 ``` ### 构造函数调用简化 **模式:** `--minify-syntax` 简化内置类型的构造函数调用。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} new Object() new Object(null) new Object({a: 1}) new Array() new Array(x, y) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} {} {} {a:1} [] [x,y] ``` ### 单属性对象内联 **模式:** `--minify-syntax` 内联只有单个属性的对象的属性访问。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} ({fn: () => console.log('hi')}).fn ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} () => console.log('hi') ``` ### 字符串 charCodeAt 常量折叠 **模式:** 始终生效 对 ASCII 字符串字面量计算 `charCodeAt()`。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} "hello".charCodeAt(1) "A".charCodeAt(0) ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} 101 65 ``` ### void 0 等价 null 相等转换 **模式:** `--minify-syntax` 将与 `void 0` 的宽松相等转换为 `null`,因为等价。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} x == void 0 x != void 0 ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} x == null x != null ``` ### 取反操作符优化 **模式:** `--minify-syntax` 将取反操作符移至逗号表达式内。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} -(a, b) -(x, y, z) ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} a,-b x,y,-z ``` ### Import.meta 属性内联 **模式:** 打包模式下 构建时内联已知的 `import.meta` 属性值。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} import.meta.dir import.meta.file import.meta.path import.meta.url ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} "/path/to/directory" "filename.js" "/full/path/to/file.js" "file:///full/path/to/file.js" ``` ### 变量声明合并 **模式:** `--minify-syntax` 合并相邻的相同类型变量声明。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} let a = 1; let b = 2; const c = 3; const d = 4; ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} let a=1,b=2; const c=3,d=4; ``` ### 表达式语句合并 **模式:** `--minify-syntax` 使用逗号运算符合并相邻表达式语句。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(1); console.log(2); console.log(3); ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(1),console.log(2),console.log(3); ``` ### return 语句合并 **模式:** `--minify-syntax` 合并 return 前的表达式,使用逗号运算符。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(x); return y; ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} return console.log(x),y; ``` ### throw 语句合并 **模式:** `--minify-syntax` 合并 throw 前的表达式,使用逗号运算符。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} console.log(x); throw new Error(); ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} throw(console.log(x),new Error()); ``` ### TypeScript 枚举跨模块内联 **模式:** `--minify-syntax`(打包模式) 跨模块边界内联枚举值。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} // lib.ts export enum Color { Red, Green, Blue } // 输入(main.ts) import { Color } from './lib'; const x = Color.Red; ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} const x=0; ``` ### 计算属性枚举内联 **模式:** `--minify-syntax` 内联用作计算属性对象属性的枚举值。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} enum Keys { FOO = 'foo' } const obj = { [Keys.FOO]: value } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} const obj={foo:value} ``` ### 箭头函数主体简化 **模式:** `--minify-syntax` 当箭头函数仅返回值时,使用表达式主体语法。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} () => { return x; } (a) => { return a + 1; } ``` ```js 输出 theme={"theme":{"light":"github-light","dark":"dracula"}} () => x a => a + 1 ``` ### 对象属性简写 **模式:** 始终生效 当属性名与值的标识符相同时,使用简写语法。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} { x: x, y: y } { name: name, age: age } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} { x, y } { name, age } ``` ### 移除 `debugger` 语句 **模式:** `--drop=debugger` 移除代码中的 `debugger` 语句。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} function test() { debugger; return x; } ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} function test(){return x} ``` ### 移除 console 调用 **模式:** `--drop=console` 移除所有 `console.*` 方法调用。 ```ts Input theme={"theme":{"light":"github-light","dark":"dracula"}} console.log("debug"); console.warn("warning"); x = console.error("error"); ``` ```js Output theme={"theme":{"light":"github-light","dark":"dracula"}} void 0; void 0; x=void 0; ``` ### 移除自定义函数调用 **模式:** `--drop=` 移除对指定全局函数及其方法的调用。 ```ts 输入 theme={"theme":{"light":"github-light","dark":"dracula"}} assert(condition); assert.equal(a, b); ``` ```js 输出(使用 --drop=assert) theme={"theme":{"light":"github-light","dark":"dracula"}} void 0; void 0; ``` ## 保留名称 为了在压缩标识符的同时保留原始函数名和类名以便调试,请使用 `--keep-names` 标志: ```bash theme={"theme":{"light":"github-light","dark":"dracula"}} bun build ./index.ts --minify --keep-names --outfile=out.js ``` 或者在 JavaScript API 中: ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./index.ts"], outdir: "./out", minify: { identifiers: true, keepNames: true, }, }); ``` `--keep-names` 会在继续压缩标识符的同时,保留函数和类的 `.name` 属性。 ## 组合示例 同时使用三种压缩模式: ```ts input.ts (158 bytes) theme={"theme":{"light":"github-light","dark":"dracula"}} const myVariable = 42; const myFunction = () => { const isValid = true; const result = undefined; return isValid ? myVariable : result; }; const output = myFunction(); ``` ```js output.js theme={"theme":{"light":"github-light","dark":"dracula"}} // 使用 --minify 输出(49 字节,减少 69%) const a=42,b=()=>{const c=!0,d=void 0;return c?a:d},e=b(); ``` ## 何时使用压缩 **适用 `--minify` 的场景:** * 生产环境包 * 降低 CDN 带宽成本 * 提升页面加载速度 **适用单独模式的场景:** * **`--minify-whitespace`:** 在不改变语义的情况下减小体积 * **`--minify-syntax`:** 在保留可读标识符以便调试的同时生成更小的输出 * **`--minify-identifiers`:** 最大限度地减小体积(结合 `--keep-names` 以获得更好的堆栈跟踪) **避免压缩的场景:** * 开发构建(调试更困难) * 需要可读错误信息时 * 代码库供消费者阅读源码的库。 # 插件 Source: https://bun.zhcndoc.com/bundler/plugins 用于扩展 Bun 运行时和打包器的通用插件 API Bun 的通用插件 API 同时扩展了运行时和打包器。 插件会拦截导入并执行自定义加载逻辑,例如读取文件或转译代码。它们可以添加对其他文件类型的支持,例如 `.scss` 或 `.yaml`。在打包器中,插件可以实现框架级功能,例如 CSS 提取、宏以及客户端与服务器代码共置。 ## 生命周期钩子 插件注册在打包生命周期的不同阶段运行的回调: * `onStart()`:打包器开始构建时运行一次 * `onResolve()`:模块解析之前运行 * `onLoad()`:模块加载之前运行 * `onBeforeParse()`:在文件被解析之前,在解析线程中运行零拷贝的本地插件 * `onEnd()`: 打包完成后运行。 ## 参考 类型的概览(完整的类型定义请参阅 Bun 的 `bun.d.ts`): ```ts title="bun.d.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} type PluginBuilder = { onStart(callback: () => void): void; onResolve: ( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string }) => { path: string; namespace?: string; } | void, ) => void; onLoad: ( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; defer: () => Promise }) => { loader?: Loader; contents?: string; exports?: Record; }, ) => void; onEnd(callback: (result: BuildOutput) => void | Promise): void; config: BuildConfig; }; type Loader = | "js" | "jsx" | "ts" | "tsx" | "json" | "jsonc" | "toml" | "yaml" | "file" | "napi" | "wasm" | "text" | "css" | "html"; ``` ## 用法 插件是一个具有 `name` 属性和 `setup` 函数的 JavaScript 对象。 ```ts title="myPlugin.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import type { BunPlugin } from "bun"; const myPlugin: BunPlugin = { name: "Custom loader", setup(build) { // 实现逻辑 }, }; ``` 在调用 `Bun.build` 时,将其传入 `plugins` 数组。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} await Bun.build({ entrypoints: ["./app.ts"], outdir: "./out", plugins: [myPlugin], }); ``` ## 插件生命周期 ### 命名空间 `onLoad` 和 `onResolve` 接受一个可选的 `namespace` 字符串。 每个模块都有一个命名空间。命名空间会为转译代码中的导入添加前缀;例如,一个具有 `filter: /\.yaml$/` 和 `namespace: "yaml:"` 的加载器,会将从 `./myfile.yaml` 的导入转换为 `yaml:./myfile.yaml`。 默认命名空间是 `"file"`,无需指定:`import myModule from "./my-module.ts"` 等同于 `import myModule from "file:./my-module.ts"`。 其他常见命名空间: * `"bun"`:用于 Bun 特有的模块(`"bun:test"`、`"bun:sqlite"`) * `"node"`:用于 Node.js 模块(`"node:fs"`、`"node:path"`) ### onStart ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onStart(callback: () => void): Promise | void; ``` 注册一个回调,在打包器开始新一轮构建时执行。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; plugin({ name: "onStart example", setup(build) { build.onStart(() => { console.log("Bundle started!"); }); }, }); ``` 回调可以返回 Promise。打包流程初始化后,打包器会等待所有 `onStart()` 回调完成才继续。 例如: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} const result = await Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", sourcemap: "external", plugins: [ { name: "Sleep for 10 seconds", setup(build) { build.onStart(async () => { await Bun.sleep(10_000); }); }, }, { name: "Log bundle time to a file", setup(build) { build.onStart(async () => { const now = Date.now(); await Bun.$`echo ${now} > bundle-time.txt`; }); }, }, ], }); ``` 在此示例中,Bun 会等待两个 `onStart()` 回调完成:10 秒的休眠以及向 `bundle-time.txt` 写入内容。 `onStart()` 回调(与其他所有生命周期回调一样)不能修改 `build.config` 对象。要修改 `build.config`,请直接在 `setup()` 函数中进行。 ### onResolve ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onResolve( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string }) => { path: string; namespace?: string; } | void, ): void; ``` 为了打包项目,Bun 会遍历项目中所有模块的依赖树。对于每个导入的模块,Bun 都必须找到并读取该模块。“查找”部分称为模块“解析”。 `onResolve()` 插件生命周期回调配置模块的解析方式。 `onResolve()` 的第一个参数是一个包含 `filter` 和 `namespace` 属性的对象。`filter` 是一个用于匹配导入字符串的正则表达式。两者结合起来,用于选择应用自定义解析逻辑的模块。 `onResolve()` 的第二个参数是一个回调,当 Bun 找到与第一个参数中定义的过滤器和命名空间匹配的模块导入时,该回调会针对每个模块导入执行一次。 回调会接收匹配模块的路径,并可以为该模块返回一个新路径。Bun 会读取新路径的内容,并将其解析为模块。 例如,将所有导入 `images/` 的模块重定向到 `./public/images/`: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; plugin({ name: "onResolve example", setup(build) { build.onResolve({ filter: /.*/, namespace: "file" }, args => { if (args.path.startsWith("images/")) { return { path: args.path.replace("images/", "./public/images/"), }; } }); }, }); ``` ### onLoad ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onLoad( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; namespace: string; loader: Loader; defer: () => Promise }) => { loader?: Loader; contents?: string; exports?: Record; }, ): void; ``` Bun 的打包器解析模块后,会读取并解析模块的内容。 `onLoad()` 插件生命周期回调会在 Bun 读取并解析模块之前修改模块的内容。 与 `onResolve()` 类似,`onLoad()` 的第一个参数用于选择此次 `onLoad()` 调用所应用的模块。 `onLoad()` 的第二个参数是一个回调,在 Bun 将匹配的模块内容加载到内存之前,该回调会针对每个匹配的模块执行一次。 回调会接收匹配模块的路径、其命名空间、默认加载器以及一个 `defer` 函数。 回调可以返回该模块新的 `contents` 字符串和新的 `loader`。 例如: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; const envPlugin: BunPlugin = { name: "env plugin", setup(build) { build.onLoad({ filter: /env/, namespace: "file" }, args => { return { contents: `export default ${JSON.stringify(process.env)}`, loader: "js", }; }); }, }; Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", plugins: [envPlugin], }); // import env from "env" // env.FOO === "bar" ``` 此插件会将所有形式为 `import env from "env"` 的导入转换为一个导出当前环境变量的 JavaScript 模块。 #### .defer() 传递给 `onLoad` 回调的参数之一是一个 `defer` 函数。它会返回一个 Promise,该 Promise 会在所有其他模块加载完成后解析。当模块的内容依赖于其他模块时,请等待该 Promise。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import { plugin } from "bun"; plugin({ name: "track imports", setup(build) { const transpiler = new Bun.Transpiler(); let trackedImports: Record = {}; // 每个经过这个 onLoad 回调的模块 // 都会将其导入记录到 trackedImports 中 build.onLoad({ filter: /\.ts/ }, async ({ path }) => { const contents = await Bun.file(path).arrayBuffer(); const imports = transpiler.scanImports(contents); for (const i of imports) { trackedImports[i.path] = (trackedImports[i.path] || 0) + 1; } return undefined; }); build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => { // 等待所有文件加载完成,确保 // 上面的 onLoad 回调对每个文件都执行且导入被跟踪 await defer(); // 输出包含每个导入统计信息的 JSON return { contents: `export default ${JSON.stringify(trackedImports)}`, loader: "json", }; }); }, }); ``` ` .defer()` 函数在每个 `onLoad` 回调中只能调用一次。 ## 原生插件 Bun 的打包器使用原生代码编写,并使用多个线程并行加载和解析模块。JavaScript 插件运行在单个线程上,因为 JavaScript 本身是单线程的。 原生插件是暴露生命周期钩子(C ABI 函数)的 NAPI 模块。它们可以在多个线程上运行,因此比 JavaScript 插件运行得快得多,并且可以跳过将字符串传递给 JavaScript 所需的 UTF-8 -> UTF-16 转换等工作。 原生插件可以使用以下生命周期钩子: * `onBeforeParse()`:在任何线程上,文件被 Bun 打包器解析前调用。 要创建原生插件,请导出一个与要实现的原生生命周期钩子签名匹配的 C ABI 函数。 ### 用 Rust 创建原生插件 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun add -g @napi-rs/cli napi new ``` 然后安装该 crate: ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} cargo add bun-native-plugin ``` 在 `lib.rs` 中,使用 `bun_native_plugin::bun` 过程宏定义实现原生插件的函数。 下面是一个实现 `onBeforeParse` 钩子的示例: ```rust title="lib.rs" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/rust.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=7ac541ea996ac441aff4b3857ad7431d" theme={"theme":{"light":"github-light","dark":"dracula"}} use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader}; use napi_derive::napi; /// 定义插件及其名称 define_bun_plugin!("replace-foo-with-bar"); /// 实现 `onBeforeParse`,将所有出现的 /// `foo` 替换为 `bar`。 /// /// 我们用 #[bun] 宏生成部分样板代码。 /// /// 函数参数(`handle: &mut OnBeforeParse`)告诉宏该函数实现了 `onBeforeParse` 钩子。 #[bun] pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> { // 获取输入源代码 let input_source_code = handle.input_source_code()?; // 获取该文件的 Loader 类型 let loader = handle.output_loader(); let output_source_code = input_source_code.replace("foo", "bar"); handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX); Ok(()) } ``` 在 `Bun.build()` 中使用它: ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} import myNativeAddon from "./my-native-addon"; Bun.build({ entrypoints: ["./app.tsx"], plugins: [ { name: "my-plugin", setup(build) { build.onBeforeParse( { namespace: "file", filter: /\.tsx$/, }, { napiModule: myNativeAddon, symbol: "replace_foo_with_bar", // external: myNativeAddon.getSharedState() }, ); }, }, ], }); ``` ### onBeforeParse ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onBeforeParse( args: { filter: RegExp; namespace?: string }, callback: { napiModule: NapiModule; symbol: string; external?: unknown }, ): void; ``` `onBeforeParse()` 回调会在 Bun 的打包器解析文件前立即运行。 它接收文件内容,并可以选择返回新的源代码。 Bun 可以从任何线程调用此回调,因此 NAPI 模块的实现必须是线程安全的。 ### onEnd ```ts theme={"theme":{"light":"github-light","dark":"dracula"}} onEnd(callback: (result: BuildOutput) => void | Promise): void; ``` 注册一个回调,在打包完成后运行。回调接收包含构建结果的 [`BuildOutput`](/docs/bundler#outputs) 对象,包括输出文件和任何构建信息。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} const result = await Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", plugins: [ { name: "onEnd example", setup(build) { build.onEnd(result => { console.log(`构建完成,输出了 ${result.outputs.length} 个文件`); for (const log of result.logs) { console.log(log); } }); }, }, ], }); ``` 该回调可以返回一个 `Promise`。在所有 `onEnd()` 回调执行完毕之前,`Bun.build()` 返回的 promise 不会解析。 ```ts title="index.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}} const result = await Bun.build({ entrypoints: ["./app.ts"], outdir: "./dist", plugins: [ { name: "上传到 S3", setup(build) { build.onEnd(async result => { if (!result.success) return; for (const output of result.outputs) { await uploadToS3(output); } }); }, }, ], }); ``` # 独立 HTML Source: https://bun.zhcndoc.com/bundler/standalone-html 将单页应用打包成一个无外部依赖的自包含 `.html` 文件 Bun 可以将整个前端打包成一个 **单个 `.html` 文件**,无需任何外部依赖。JavaScript、TypeScript、JSX、CSS、图片、字体、视频、WASM —— 所有内容均内联到一个文件中。 ```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}} bun build --compile --target=browser ./index.html --outdir=dist ``` 输出的是一个自包含的 HTML 文档:没有相对路径、没有外部文件,也不需要服务器。 ## 单文件。随处上传。 输出是一个单独的 `.html` 文件,你可以放到任何地方: * **上传到 S3** 或任何静态文件托管服务——无需维护目录结构,只需一个文件 * **从桌面双击打开**——它会在浏览器中打开并离线运行,无需本地主机服务器 * **嵌入到 WebView 中**——无需处理相对文件 * **插入 `