运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

打包器

Bun 的快速原生打包器,支持 JavaScript、TypeScript、JSX 等多种语言

通过 bun build CLI 命令或 Bun.build() JavaScript API 使用 Bun 的原生打包器。

一览

  • JS API: await Bun.build({ entrypoints, outdir })
  • CLI: bun build <入口文件> --outdir ./out
  • 监视:--watch 实现增量重建
  • 目标环境:--target browser|bun|node
  • 格式:--format esm|cjs|iife(cjs/iife 为实验性功能)
build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './build',
});

它的速度很快。以下数据来自 esbuild 的 three.js 基准测试。

为什么要打包?

打包器可以解决以下几个问题:

  • Reducing HTTP requests. A single package in node_modules may consist of hundreds of files, and large applications may have dozens of such dependencies. Loading each of these files with a separate HTTP request becomes untenable, so bundlers convert your application source code into a smaller number of self-contained "bundles" that can be loaded with a single request.
  • Code transforms. Modern apps are commonly built with languages or tools like TypeScript, JSX, and CSS modules. All of these must be converted into plain JavaScript and CSS before a browser can consume them. The bundler is the natural place to configure these transformations.
  • Framework features. Frameworks rely on bundler plugins & code transformations to implement common patterns like file-system routing, client-server code co-location (think getServerSideProps or Remix loaders), and server components.
  • Full-stack Applications. Bun's bundler can handle both server and client code in a single command, enabling optimized production builds and single-file executables. With build-time HTML imports, you can bundle your entire application — frontend assets and backend server — into a single deployable unit.

基本示例

构建你的第一个 bundle。你有以下两个文件,它们实现了一个客户端渲染的 React 应用。

index.tsx
import * as ReactDOM from "react-dom/client";
import { Component } from "./Component";

const root = ReactDOM.createRoot(document.getElementById("root")!);
root.render(<Component message="Sup!" />);
Component.tsx
export function Component(props: { message: string }) {
  return <h1>{props.message}</h1>;
}

这里,index.tsx 是应用的“入口点”:也就是打包器开始处理的文件。通常,这是一个会执行某些副作用的脚本,例如启动服务器,或者在本例中初始化一个 React 根节点。由于这些文件使用了 TypeScript 和 JSX,因此必须先进行打包,然后才能发送到浏览器。

要创建 bundle:

build.ts
await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
});
terminal
bun build ./index.tsx --outdir ./out

对于 entrypoints 中指定的每个文件,Bun 都会生成一个新的 bundle,并将其写入 ./out 目录(该路径相对于当前工作目录解析)。运行构建命令后,文件系统如下所示:

文件系统
.
├── index.tsx
├── Component.tsx
└── out
    └── index.js

out/index.js 的内容大致如下:

out/index.js
// out/index.js
// ...
// 约 2 万行代码
// 包含 `react-dom/client` 及所有依赖的内容
// 这里定义了 $jsxDEV 和 $createRoot 函数

// Component.tsx
function Component(props) {
  return $jsxDEV(
    "h1",
    {
      children: props.message,
    },
    undefined,
    false,
    undefined,
    this,
  );
}

// index.tsx
var rootNode = document.getElementById("root");
var root = $createRoot(rootNode);
root.render(
  $jsxDEV(
    Component,
    {
      message: "Sup!",
    },
    undefined,
    false,
    undefined,
    this,
  ),
);

监听模式

和运行时与测试器一样,打包器原生支持监听模式。

terminal
bun build ./index.tsx --outdir ./out --watch

支持的内容类型

与 Bun 运行时一样,打包器默认支持多种文件类型。下表列出了打包器的标准“加载器”。参见 加载器。

扩展名详情
.js .jsx .cjs .mjs .mts .cts .ts .tsxUses Bun's built-in transpiler to parse the file and transpile TypeScript/JSX syntax to vanilla JavaScript. The bundler executes a set of default transforms including dead code elimination and tree shaking. Bun does not down-convert syntax; if you use recent ECMAScript syntax, it appears as-is in the bundled code.
.jsonJSON files are parsed and inlined into the bundle as a JavaScript object.

js<br/>import pkg from "./package.json";<br/>pkg.name; // => "my-package"<br/>
.jsoncJSON with comments. Files are parsed and inlined into the bundle as a JavaScript object.

js<br/>import config from "./config.jsonc";<br/>config.name; // => "my-config"<br/>
.tomlTOML files are parsed and inlined into the bundle as a JavaScript object.

js<br/>import config from "./bunfig.toml";<br/>config.logLevel; // => "debug"<br/>
.yaml .ymlYAML files are parsed and inlined into the bundle as a JavaScript object.

js<br/>import config from "./config.yaml";<br/>config.name; // => "my-app"<br/>
.txtThe contents of the text file are read and inlined into the bundle as a string.

js<br/>import contents from "./file.txt";<br/>console.log(contents); // => "Hello, world!"<br/>
.htmlHTML files are processed and any referenced assets (scripts, stylesheets, images) are bundled.
.cssCSS files are bundled together into a single .css file in the output directory.
.node .wasmThe Bun runtime supports these files, but the bundler treats them as assets.

资源文件

If the bundler encounters an import with an unrecognized extension, it treats the imported file as an external file. The bundler copies the referenced file as-is into outdir and resolves the import as a path to the file.

Input
// bundle入口文件
import logo from "./logo.svg";
console.log(logo);
Output
// 打包输出
var logo = "./logo-a7305bdef.svg";
console.log(logo);

文件加载器的具体行为还取决于 naming 和 publicPath。

插件

插件可以覆盖或扩展本表中所述的行为。参见加载器。

API

入口点

必填

一个与应用程序入口点对应的路径数组。Bun 会为每个入口点生成一个捆绑包。

build.ts
const result = await Bun.build({
  entrypoints: ["./index.ts"],
});
// => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] }

文件

用于内存打包的文件路径到文件内容的映射:打包磁盘上不存在的虚拟文件,或覆盖磁盘上已有文件的内容。此选项仅适用于 JavaScript API。

You can provide file contents as a string, Blob, TypedArray, or ArrayBuffer.

完全内存打包

只需在 files 中提供所有源文件,即可在磁盘上没有任何文件的情况下打包代码:

build.ts
const result = await Bun.build({
  entrypoints: ["/app/index.ts"],
  files: {
    "/app/index.ts": `
      import { greet } from "./greet.ts";
      console.log(greet("World"));
    `,
    "/app/greet.ts": `
      export function greet(name: string) {
        return "Hello, " + name + "!";
      }
    `,
  },
});

const output = await result.outputs[0].text();
console.log(output);

When all entrypoints are in the files map, Bun uses the current working directory as the root.

覆盖磁盘文件

内存文件的优先级高于磁盘上的文件,因此你可以在保持代码库其余部分不变的同时覆盖特定文件:

build.ts
// 假设 ./src/config.ts 磁盘文件存在,含开发配置
await Bun.build({
  entrypoints: ["./src/index.ts"],
  files: {
    // 用生产配置覆盖 config.ts
    "./src/config.ts": `
      export const API_URL = "https://api.production.com";
      export const DEBUG = false;
    `,
  },
  outdir: "./dist",
});

混合磁盘与虚拟文件

磁盘文件可导入虚拟文件,虚拟文件也可导入磁盘文件:

build.ts
// ./src/index.ts 磁盘存在,导入 "./generated.ts"
await Bun.build({
  entrypoints: ["./src/index.ts"],
  files: {
    // 提供 index.ts 导入的虚拟文件
    "./src/generated.ts": `
      export const BUILD_ID = "${crypto.randomUUID()}";
      export const BUILD_TIME = ${Date.now()};
    `,
  },
  outdir: "./dist",
});

可将其用于代码生成、注入构建时常量,或使用模拟模块进行测试。

outdir

写入输出文件的目录。

build.ts
const result = await Bun.build({
  entrypoints: ['./index.ts'],
  outdir: './out'
});
// => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] }

If you don't pass outdir to the JavaScript API, Bun does not write bundled code to disk. It returns the bundled files in an array of BuildArtifact objects. These objects are Blobs with extra properties; see Outputs.

build.ts
const result = await Bun.build({
  entrypoints: ["./index.ts"],
});

for (const res of result.outputs) {
  // 可作为 Blob 处理
  await res.text();

  // Bun 设置 Content-Type 和 Etag 标头
  new Response(res);

  // 也可手动写入,但此时推荐使用 `outdir` 选项。
  Bun.write(path.join("out", res.path), res);
}

设置 outdir 后,BuildArtifact 上的 path 属性就是写入文件的绝对路径。

target

指定目标执行环境。

build.ts
await Bun.build({
  entrypoints: ['./index.ts'],
  outdir: './out',
  target: 'browser', // 默认
})

根据目标环境的不同,Bun 会应用不同的模块解析规则和优化策略。

browser

默认值。 用于在浏览器中运行的捆绑包。解析导入时,优先使用 "browser" 导出条件。 导入 node:events 或 node:path 等内置模块可以正常工作,但调用某些函数(例如 fs.readFile)则无法正常工作。

bun

用于在 Bun 运行时中运行的捆绑包。在许多情况下,无需捆绑服务器端代码;你可以直接执行源代码而无需修改。不过,捆绑服务器代码可以缩短启动时间并提高运行性能。对于在构建时导入 HTML、并将服务器端和客户端代码捆绑在一起的全栈应用,应使用此目标。

所有使用 target: "bun" 生成的捆绑包都会标记 // @bun 编译指令,告知 Bun 运行时无需在执行前重新转译文件。

如果任何入口点包含 Bun shebang(#!/usr/bin/env bun),捆绑器会默认使用 target: "bun",而不是 "browser"。

When you use target: "bun" and format: "cjs" together, the bundler adds the // @bun @bun-cjs pragma, and the CommonJS wrapper function is not compatible with Node.js.

node

用于在 Node.js 中运行的捆绑包。解析导入时,优先使用 "node" 导出条件。Bun 不会为 Bun 全局对象或内置的 bun:* 模块提供 polyfill。

format

指定生成 bundle 的模块格式。

Bun 默认为 "esm",并提供 "cjs" 和 "iife" 实验性支持。

格式: "esm" - ES 模块

默认格式。支持 ES 模块语法,包括顶层 await 和 import.meta。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  format: "esm",
})

要在浏览器中使用 ES 模块语法,请将 format 设置为 "esm",并使用 <script type="module"> 标签加载 bundle。

格式: "cjs" - CommonJS

To build a CommonJS module, set format to "cjs". When you choose "cjs", the default target changes from "browser" (esm) to "node" (cjs). CommonJS modules transpiled with format: "cjs", target: "node" run in both Bun and Node.js (assuming both support the APIs in use).

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  format: "cjs",
})

格式: "iife" - IIFE

To build an IIFE bundle, set format to "iife". Bun wraps the bundle in an immediately invoked function expression and does not support exposing its exports under a global name.

jsx

配置 JSX 的编译方式。

Classic 运行时示例(使用 factory 和 fragment):

index.ts
await Bun.build({
  entrypoints: ["./app.tsx"],
  outdir: "./out",
  jsx: {
    factory: "h",
    fragment: "Fragment",
    runtime: "classic",
  },
});
terminal
# JSX 配置可通过 bunfig.toml 或 tsconfig.json 管理
bun build ./app.tsx --outdir ./out

自动运行时示例(使用 importSource):

index.ts
await Bun.build({
  entrypoints: ["./app.tsx"],
  outdir: "./out",
  jsx: {
    importSource: "preact",
    runtime: "automatic",
  },
});
terminal
# JSX 配置可通过 bunfig.toml 或 tsconfig.json 管理
bun build ./app.tsx --outdir ./out

splitting

是否启用代码拆分。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  splitting: false, // 默认关闭
})

当设置为 true 时,打包器会启用代码拆分。当多个入口点导入同一个文件或模块时,打包器可以将这部分共享代码拆分到一个单独的包中,这个包称为 chunk。请考虑以下文件:

entry-a.ts
import { shared } from "./shared.ts";
console.log(shared);
entry-b.ts
import { shared } from "./shared.ts";
console.log(shared);
shared.ts
export const shared = "shared";

启用拆分打包:

build.ts
await Bun.build({
  entrypoints: ['./entry-a.ts', './entry-b.ts'],
  outdir: './out',
  splitting: true,
})

Running this build with the JavaScript API results in the following files:

文件系统
.
├── entry-a.ts
├── entry-b.ts
├── shared.ts
└── out
    ├── entry-a.js
    ├── entry-b.js
    └── chunk-dqmx6gc8.js

The generated chunk-dqmx6gc8.js file contains the shared code. To avoid collisions, the file name includes a content hash by default. The bun build CLI names this chunk entry-a-t268ez5g.js instead of chunk-<hash>.js. Customize this with naming.

Each import() of a bundled JavaScript module also becomes its own chunk. Tree shaking applies to these chunks too: if every import() of a module lives in code that tree shaking removes — for example inside a function that is only called behind a define or features gate that evaluates to false — and nothing else in the live output imports the module, its chunk is not written and the module is absent from the metafile's inputs and outputs. Setting treeShaking: false keeps every import() chunk. This differs from esbuild, which emits a chunk for every reachable import() target.

With target: "bun", a require() of a bundled ES module becomes a chunk of its own as well. The call is emitted as import.meta.require("./chunk-<hash>.js") and stays synchronous — Bun evaluates the chunk when the call runs — so a require() inside a function that never runs keeps its module out of the startup working set entirely. Tree shaking treats these chunks like import() chunks. require() of a CommonJS module is unaffected and keeps returning module.exports. A require() cycle between such chunks follows Bun's runtime require() of an ES module: a module required again while its chunk is still being loaded sees the partially initialized CommonJS placeholder ({}) rather than the live bindings the in-chunk wrapper provides. For other targets — and with splitRequire: false (--no-split-require) — the required module is inlined into the calling chunk behind a lazy wrapper instead, because the call must return synchronously.

Tree-shaking import() and require() results

When the result of a string-literal import() or require() of an ES module is only ever used to read specific exports, the exports nobody reads are dropped from that module — and whatever only they depended on tree-shakes with them. This applies with and without splitting; the module is still loaded lazily, exactly where the call is written.

entry.ts
const { render } = await import("./markdown"); // keeps `render`
import("./telemetry").then(t => t.init()); // keeps `init`
const { parse } = require("./yaml"); // keeps `parse` (ES module targets only)
await import("./polyfill"); // keeps only side effects

Recognized uses: destructuring (const { a, b: c, ...rest } = await import(x), also let/var and export const { a } = …), property access on the awaited value or on a local holding it ((await import(x)).a, const ns = await import(x); ns.a; const { b } = ns), .then(({ a }) => …) / .then(ns => ns.a) with an arrow function, per-element destructuring of await Promise.all([import(x), import(y)]), the same shapes for require(), and a bare import(x); / await import(x); / require(x); statement (nothing observed).

The module keeps every export as soon as one use can't be followed: the namespace is passed, returned, stored, spread or iterated (() => import(x), fn(ns), { ...ns }, Object.keys(ns)), accessed with a computed key (or ?. directly on the await import(x) expression), handed to a non-arrow .then callback, reached via import * as ns elsewhere, or read under a direct eval. CommonJS targets always keep everything. The imported module itself is always evaluated; only with "sideEffects": false can a module it merely re-exports from be skipped when none of those re-exports are observed.

build.ts
await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
  target: "bun",
  splitting: true,
  splitRequire: false, // keep require()'d modules in the calling chunk
});

minChunkSize

With splitting, also fold small side-effect-free chunks into a chunk that more entrypoints load.

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  splitting: true,
  minChunkSize: 16 * 1024, // default 0 (off)
})

Code splitting gives each distinct set of importers its own chunk, then folds chunks that are always loaded together: a module that index.tsx imports and that a lazily import()ed module also imports lives in the chunk of index.tsx, and the lazy chunk imports it from there, because the lazy module can only load after index.tsx has run. That fold never makes an entrypoint load more code, so it is always on.

Nothing folds into the chunk of an entrypoint whose name has no [hash] (the default naming). Whatever loads that file can add a query string to its URL (index.js?v=123), and an import "./index.js" in another chunk would then run the file a second time. The shared code goes into one hashed chunk next to the entrypoint. Entrypoints that an HTML file loads, and entrypoints with [hash] in naming.entry, still take the fold.

minChunkSize goes further for chunks whose source files add up to fewer than this many bytes and whose modules run nothing at the top level: only declarations, "sideEffects": false in their package.json, a lazily initialized CommonJS/ESM wrapper, or an import of a CommonJS module (such as react) that is already initialized wherever the chunk's code ends up. Such a chunk folds into a chunk loaded by a superset of its importers. Everything it imports must already be loaded whenever that target is, or be side-effect free as well. The extra entrypoints then carry some unused definitions, capped at about 1.5% of what each entrypoint loaded to begin with. No side effect runs earlier than before and nothing lazy becomes eager. Because these rules bound what an entrypoint can end up loading, a large minChunkSize is reasonable.

For target: "browser", where every chunk is a request, 16 KiB is a good starting point.

A chunk that absorbs other chunks exports the symbols those chunks' importers need. An entrypoint that has exports of its own never absorbs one, so its module namespace stays as written; an entrypoint without exports can. With --compile, nothing folds into the entrypoint's own chunk.

modulePreload

With splitting and target: "browser", the browser would otherwise discover a chunk's own imports only after downloading and parsing it, one level per round trip. Bun writes a <link rel="modulepreload"> into HTML entrypoints for every chunk the page's script statically imports, and every import() first inserts one for each chunk its target statically imports (transitively), so the whole dependency chain downloads in parallel. Outside a document (workers, server-side) the import() helper does nothing. Inserted links copy the nonce from a <meta property="csp-nonce" nonce="..."> tag if the page has one. Enabled by default; set modulePreload: false (--no-module-preload) to emit plain import() calls and no links.

插件

打包时应用的插件列表。

build.ts
await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
  plugins: [
    /* ... */
  ],
});

The runtime and the bundler share Bun's plugin system. See plugins.

env

Controls how environment variables are handled during bundling. Internally, this option uses define to inject environment variables into the bundle; env is a shorthand for specifying which ones.

env: "inline"

将 process.env.FOO 替换为包含环境变量实际值的字符串字面量,内联进 bundle。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  env: "inline",
})

示例输入:

input.js
// input.js
console.log(process.env.FOO);
console.log(process.env.BAZ);

生成的 bundle 包含以下代码:

output.js
// output.js
console.log("bar");
console.log("123");

env: "PUBLIC_*"(前缀)

内联匹配给定前缀的环境变量(即 * 字符之前的部分),将 process.env.FOO 替换为实际的环境变量值。使用前缀可以内联公共值(例如面向公众的 URL 或客户端令牌),而不会将私有凭据注入输出 bundle。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',

  // 内联所有以 "ACME_PUBLIC_" 开头的环境变量
  env: "ACME_PUBLIC_*",
})

设环境变量如下:

terminal
FOO=bar BAZ=123 ACME_PUBLIC_URL=https://acme.com

源码:

index.tsx
console.log(process.env.FOO);
console.log(process.env.ACME_PUBLIC_URL);
console.log(process.env.BAZ);

生成的 bundle 包含以下代码:

output.js
console.log(process.env.FOO);
console.log("https://acme.com");
console.log(process.env.BAZ);

env: "disable"

完全禁用环境变量注入。

sourcemap

指定生成哪种类型的 sourcemap。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  sourcemap: 'linked', // 默认 'none'
})
ValueDescription
"none"Default. No sourcemap is generated.
"linked"A separate *.js.map file is created alongside each *.js bundle using a //# sourceMappingURL comment to link the two. Requires --outdir to be set. You can customize the base URL in this comment with --public-path.

js<br/>// <bundled code here><br/><br/>//# sourceMappingURL=bundle.js.map<br/>
"external"A separate *.js.map file is created alongside each *.js bundle without inserting a //# sourceMappingURL comment.

Generated bundles contain a debug id that can be used to associate a bundle with its corresponding sourcemap. This debugId is added as a comment at the bottom of the file.

js<br/>// <generated bundle code><br/><br/>//# debugId=<DEBUG ID><br/>
"inline"A sourcemap is generated and appended to the end of the generated bundle as a base64 payload.

js<br/>// <bundled code here><br/><br/>//# sourceMappingURL=data:application/json;base64,<encoded sourcemap here><br/>

关联的 *.js.map sourcemap 是一个包含等效 debugId 属性的 JSON 文件。

压缩

是否启用代码压缩,默认 false。

启用所有压缩选项:

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  minify: true, // 默认 false
})

细粒度开启压缩:

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  minify: {
    whitespace: true,
    identifiers: true,
    syntax: true,
  },
})

外部依赖

外部依赖列表,默认空数组 []。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  external: ["lodash", "react"], // 默认 []
})

An external import is not included in the final bundle. Instead, the bundler leaves the import statement as-is, to be resolved at runtime.

示例入口:

index.tsx
import _ from "lodash";
import { z } from "zod";

const value = z.string().parse("Hello world!");
console.log(_.upperCase(value));

通常,构建 index.tsx 会生成一个包含整个 "zod" 包源代码的构建产物。若要保持导入语句原样,则将其标记为外部依赖:

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  external: ['zod'],
})

生成的构建产物大致如下:

out/index.js
import { z } from "zod";

// ...
// lodash 源码
// 包括 `_.upperCase` 函数

var value = z.string().parse("Hello world!");
console.log(_.upperCase(value));

可使用通配符 * 标记所有导入为外部:

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  external: ['*'],
})

包

控制是否将包依赖项包含在构建包中。可能的值:bundle(默认)、external。Bun 将导入路径不以 ., .. 或 / 开头的任何导入视为包。

build.ts
await Bun.build({
  entrypoints: ['./index.ts'],
  packages: 'external',
})

命名

Customizes the generated file names. Defaults to [dir]/[name].[ext].

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  naming: "[dir]/[name].[ext]", // 默认
})

默认情况下,生成文件名基于入口文件名。

file system
.
├── index.tsx
└── out
    └── index.js

当存在多个入口点时,生成的文件层级结构会反映入口点的目录结构。

file system
.
├── index.tsx
└── nested
    └── index.tsx
└── out
    ├── index.js
    └── nested
        └── index.js

The naming field customizes the names and locations of the generated files. It accepts a template string. Bun uses the template for all bundles that correspond to entrypoints and replaces the following tokens with their values:

  • [name] - The name of the entrypoint file, without the extension.
  • [ext] - The extension of the generated bundle.
  • [hash] - A hash of the bundle contents: 8 lowercase alphanumeric characters. If two outputs with different contents would print the same characters, both get enough extra characters to differ (as do the outputs that reference them). [hash9] through [hash13] set a wider minimum; [hash13] is the full 64-bit hash.
  • [dir] - The relative path from the project root to the parent directory of the source file.

举例:

示例文件[name][ext][hash][dir]
./index.tsxindexjsa1b2c3d4""(空字符串)
./nested/entry.tsentryjsc3d4e5f6"nested"

组合这些标记以创建模板字符串。例如,要在生成的捆绑文件名中包含哈希值:

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  naming: 'files/[dir]/[name]-[hash].[ext]',
})

示例输出结构:

file system
.
├── index.tsx
└── out
    └── files
        └── index-a1b2c3d4.js

When you provide a string for the naming field, Bun uses it only for bundles that correspond to entrypoints. The names of chunks and copied assets are not affected. In the JavaScript API, you can specify a separate template string for each type of generated file.

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  naming: {
    // 默认值
    entry: '[dir]/[name].[ext]',
    chunk: '[name]-[hash].[ext]',
    asset: '[name]-[hash].[ext]',
  },
})

根目录

项目根目录。

build.ts
await Bun.build({
  entrypoints: ['./pages/a.tsx', './pages/b.tsx'],
  outdir: './out',
  root: '.',
})

If unspecified, Bun uses the first common ancestor of all entrypoint files as the root. Consider the following file structure:

文件系统
.
└── pages
  └── index.tsx
  └── settings.tsx

在 pages 目录中构建两个入口文件:

JavaScript
await Bun.build({
  entrypoints: ['./pages/index.tsx', './pages/settings.tsx'],
  outdir: './out',
})

结果:

文件系统
.
└── pages
  └── index.tsx
  └── settings.tsx
└── out
  └── index.js
  └── settings.js

The pages directory is the first common ancestor of the entrypoint files, so Bun treats it as the project root. As a result, the generated bundles live at the top level of the out directory; there is no out/pages directory.

通过指定 root 选项可以覆盖此行为:

JavaScript
await Bun.build({
  entrypoints: ['./pages/index.tsx', './pages/settings.tsx'],
  outdir: './out',
  root: '.',
})

当 root 为 . 时,生成的文件结构如下:

Code
.
└── pages
  └── index.tsx
  └── settings.tsx
└── out
  └── pages
    └── index.js
    └── settings.js

publicPath

添加到打包代码中所有导入路径前的前缀。

在许多情况下,生成的 bundle 不包含任何导入语句;打包的目标就是将所有代码合并到单个文件中。不过在少数情况下,生成的 bundle 会包含导入语句:

  • Asset imports — When importing an unrecognized file type like *.svg, the bundler defers to the file loader, which copies the file into outdir as is. The import is converted into a variable.
  • External modules — Files and modules marked as external are not included in the bundle. Instead, the bundler leaves the import statement in the final bundle.
  • Chunking. When splitting is enabled, the bundler may generate separate "chunk" files that represent code that is shared among multiple entrypoints.

在任何这些情况下,最终 bundle 可能包含对其他文件的路径。默认这些导入是相对的。下面是一个资源导入的示例:

Input
import logo from "./logo.svg";
console.log(logo);
Output
var logo = "./logo-a7305bdef.svg";
console.log(logo);

设置 publicPath 会使用指定的值作为所有文件路径的前缀。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  publicPath: 'https://cdn.example.com/', // 默认为 undefined
})

生成文件示例:

out/index.js
var logo = "https://cdn.example.com/logo-a7305bdef.svg";

定义

A map of global identifiers to be replaced at build time. Keys of this object are identifiers or dotted property paths such as process.env.NODE_ENV, and values are JSON strings, identifiers, or property paths that are inlined.

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  define: {
    STRING: JSON.stringify("value"),
    "nested.boolean": "true",
  },
})

加载器

将文件扩展名映射到内置加载器名称的表。使用它自定义某些文件的加载方式。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  loader: {
    ".png": "dataurl",
    ".txt": "file",
  },
})

横幅

添加到最终 bundle 中的横幅内容。它可以是 React 使用的 "use client" 指令,也可以是许可证等注释块。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  banner: '"use client";'
})

页脚

添加到最终构建包的页脚。可以是许可证的注释块,也可以是一个有趣的彩蛋。

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  footer: '// 在旧金山用爱构建'
})

drop

Removes function calls from a bundle. For example, --drop=console removes all calls to console.log. Bun also removes the arguments to dropped calls, even if they have side effects. Dropping debugger removes all debugger statements.

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  drop: ["console", "debugger", "anyIdentifier.or.propertyAccess"],
})

功能

Enable compile-time feature flags for dead code elimination: conditionally include or exclude code paths at bundle time using import { feature } from "bun:bundle".

app.ts
import { feature } from "bun:bundle";

if (feature("PREMIUM")) {
  // 仅 PREMIUM 标记启用时包含
  initPremiumFeatures();
}

if (feature("DEBUG")) {
  // 仅 DEBUG 标记启用时包含
  console.log("Debug mode");
}
build.ts
await Bun.build({
  entrypoints: ['./app.ts'],
  outdir: './out',
  features: ["PREMIUM"],  // PREMIUM=true, DEBUG=false
})

Bun replaces the feature() function with true or false at bundle time. When minification is also enabled, Bun eliminates the unreachable code:

输入
import { feature } from "bun:bundle";
const mode = feature("PREMIUM") ? "premium" : "free";
输出(启用 --feature PREMIUM --minify)
var mode = "premium";
输出(不启用 --feature PREMIUM,启用 --minify)
var mode = "free";

主要注意事项:

  • feature() requires a string literal argument — dynamic values are not supported
  • Bun completely removes the bun:bundle import from the output
  • Works with bun build, bun run, and bun test
  • You can enable multiple flags: --feature FLAG_A --feature FLAG_B
  • For type safety, augment the Registry interface to restrict feature() to known flags

应用场景:

  • 平台差异代码(feature("SERVER") vs feature("CLIENT"))
  • 环境变量控制功能(feature("DEVELOPMENT"))
  • 渐进式功能发布
  • A/B 测试
  • 付费版功能区分

类型安全: 默认 feature() 可传任意字符串。想实现自动提示防错,可新建 env.d.ts 文件(或补充到已有 .d.ts)并声明扩展:

env.d.ts
declare module "bun:bundle" {
  interface Registry {
    features: "DEBUG" | "PREMIUM" | "BETA_FEATURES";
  }
}

确保该文件已包含在你的 tsconfig.json 中(例如 "include": ["src", "env.d.ts"])。现在,feature() 只接受这些标记,像 feature("TYPO") 这样的无效字符串会产生类型错误。

optimizeImports

Skip parsing unused submodules of barrel files (re-export index files). When you import only a few named exports from a large library, normally the bundler parses every file the barrel re-exports. With optimizeImports, the bundler parses only the submodules you use.

build.ts
await Bun.build({
  entrypoints: ["./app.ts"],
  outdir: "./out",
  optimizeImports: ["antd", "@mui/material", "lodash-es"],
});

For example, import { Button } from 'antd' normally parses all ~3000 modules that antd/index.js re-exports. With optimizeImports: ['antd'], the bundler parses only the Button submodule.

This works for pure barrel files — files where every named export is a re-export (export { X } from './x'). If a barrel file has any local exports (export const foo = ...), or if any importer uses import *, the bundler loads all submodules.

The bundler always loads export * re-exports (it never defers them) to avoid circular resolution issues. It defers only named re-exports (export { X } from './x') that no importer uses.

自动模式: 在 package.json 中带有 "sideEffects": false 的包会自动启用桶优化——无需配置 optimizeImports。对于没有该字段的包,可以使用 optimizeImports。

插件: Resolve 和 load 插件可与桶优化配合使用。延迟加载的子模块最终加载时,会经过插件管道。

Re-exported namespaces

Property reads on an import that turns out to be a module namespace are linked straight to the export they name, the same as a named import. This covers namespaces reached indirectly:

Each of these shapes of lib.ts qualifies:

TypeScript
// import { z } from "./lib";  z.object()
import * as z from "./external";
export { z };
TypeScript
// import z from "./lib";  z.object()
import * as z from "./external";
export default z;
TypeScript
// import { ns } from "./lib";  ns.object()
export * as ns from "./external";
TypeScript
// import lib from "./lib";  lib.object()
export function object() {}
export * as default from "./lib";

In each case the member access compiles to a direct reference to object, the exports object for ./external is not created unless something else uses the namespace as a value (Object.keys(z), { ...z }, passing z around), and the exports you don't touch are tree-shaken. One level is resolved (ns.a.b binds a). Calling a function this way passes undefined as this, as with import * as ns; ns.fn().

Assignments (z.x = 1), optional chains, and non-literal computed keys (z[key]) are left as property accesses; z["object"] is treated like z.object. export default someImport is followed only when it ends at a namespace; a default that snapshots a let export keeps snapshot semantics.

The same applies to the default import of a CommonJS module whose exports.x = ... assignments the bundler lifted to ES module exports, such as react and scheduler. The default import of a CommonJS module is its module.exports, which is that module's namespace, so import React from "react"; React.useState() compiles to a direct call of the lifted useState binding. This holds while every use of React reads, calls or assigns one of its properties: React.useState, React[key], const { useState } = React. A write, React.useLayoutEffect = React.useEffect, goes through a namespace object whose setters assign the lifted binding, so every importer sees the new value, the same as a write to module.exports. Code that holds React itself as a value (fn(React), Object.keys(React), const R = React, a re-export) or deletes one of its properties can change the object in ways a namespace object cannot pass on to the lifted bindings (Object.defineProperty, delete, Object.freeze). The module then keeps its CommonJS wrapper and the default import is the real module.exports object. This rule looks at import React from only. ns.default on import * as ns, the default of an import() and the this of a call like React.fn() are still the namespace object of a module that stays lifted. React.default, and ns.default on import * as ns, is the lifted default export when the module has one, and otherwise the namespace itself, as module.exports is in Node. A module that sets both exports.__esModule and exports.default keeps its CommonJS wrapper when the importer is not an ES module by type (.mjs, .mts, or "type": "module"), because the default import then depends on that flag at run time. An import() of a module that does not set both resolves to that same namespace object as default, with or without code splitting.

deprecatedNamespaceObjectSetters

Default true. When a namespace object does have to be created, each property currently gets a getter and a setter; the setter accepts ns.foo = value without throwing (reads still return the module's binding). Set this to false to emit getter-only namespace objects, which is what a future Bun release will do unconditionally. The namespace of a lifted CommonJS module is not affected: it stands in for module.exports, so its setters assign the lifted bindings either way.

build.ts
await Bun.build({
  entrypoints: ['./index.tsx'],
  outdir: './out',
  deprecatedNamespaceObjectSetters: false,
})

metafile

生成结构化格式的构建元数据。元数据文件描述每个输入和输出文件,包括大小、导入和导出。可用于:

  • Bundle 分析:了解哪些内容影响 Bundle 大小
  • 可视化:提供给 esbuild 的 Bundle 分析器 等工具
  • 依赖跟踪:查看应用程序的完整导入图
  • CI 集成:跟踪 Bundle 大小随时间的变化
build.ts
const result = await Bun.build({
  entrypoints: ['./src/index.ts'],
  outdir: './dist',
  metafile: true,
});

if (result.metafile) {
  // 分析输入文件
  for (const [path, meta] of Object.entries(result.metafile.inputs)) {
    console.log(`${path}: ${meta.bytes} 字节`);
  }

  // 分析输出文件
  for (const [path, meta] of Object.entries(result.metafile.outputs)) {
    console.log(`${path}: ${meta.bytes} 字节`);
  }

  // 保存供外部分析工具使用
  await Bun.write('./dist/meta.json', JSON.stringify(result.metafile));
}

Markdown 元数据文件

使用 --metafile-md 生成一个 Markdown 元数据文件,它对 LLM 友好,并且在终端中可读:

terminal
bun build ./src/index.ts --outdir ./dist --metafile-md=./dist/meta.md

You can use both --metafile and --metafile-md together:

terminal
bun build ./src/index.ts --outdir ./dist --metafile=./dist/meta.json --metafile-md=./dist/meta.md

metafile 选项格式

在 JavaScript API 中,metafile 支持以下几种形式:

build.ts
// 布尔值 - 在结果对象中包含 metafile 字段
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  metafile: true,
});

// 字符串 - 将 JSON metafile 写入指定路径
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  metafile: "./dist/meta.json",
});

// 对象 - 指定 JSON 和 Markdown 输出路径
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  metafile: {
    json: "./dist/meta.json",
    markdown: "./dist/meta.md",
  },
});

元数据文件结构包含:

TypeScript
interface BuildMetafile {
  inputs: {
    [path: string]: {
      bytes: number;
      imports: Array<{
        path: string;
        kind: ImportKind;
        original?: string; // Original specifier before resolution
        entryPoint?: string; // import() / require() split into another output file: the input that file was built from
        external?: boolean;
      }>;
      format?: "esm" | "cjs" | "json" | "css";
    };
  };
  outputs: {
    [path: string]: {
      bytes: number;
      inputs: {
        [path: string]: { bytesInOutput: number };
      };
      imports: Array<{ path: string; kind: ImportKind }>;
      exports: string[];
      entryPoint?: string;
      cssBundle?: string; // JS 入口点对应的 CSS 文件
    };
  };
}

输出

Bun.build 返回一个 Promise<BuildOutput>,定义如下:

build.ts
interface BuildOutput {
  outputs: BuildArtifact[];
  success: boolean;
  logs: Array<object>; // 详见文档
  metafile?: BuildMetafile; // 仅当 metafile: true 时存在
}

interface BuildArtifact extends Blob {
  kind: "entry-point" | "chunk" | "asset" | "sourcemap" | "bytecode";
  path: string;
  loader: Loader;
  hash: string | null;
  sourcemap: BuildArtifact | null;
}

outputs 数组包含构建生成的所有文件。每个 artifact 都实现了 Blob 接口。

build.ts
const build = await Bun.build({
  /* */
});

for (const output of build.outputs) {
  await output.arrayBuffer(); // => ArrayBuffer
  await output.bytes(); // => Uint8Array
  await output.text(); // string
}

每个 Artifact 还拥有:

PropertyDescription
kind此文件所属的构建输出类型。构建会生成打包后的入口点、代码分割的“块”、源映射、字节码以及复制的资源(如图像)。
path磁盘上文件的绝对路径
loader用于解释文件的加载器。有关 Bun 如何将文件扩展名映射到内置加载器的信息,请参阅 加载器。
hash文件内容的哈希值。对于资源始终存在。
sourcemap与此文件对应的源映射文件(如果已生成)。仅入口点和块具有此属性。

BuildArtifact 对象可以直接传给 new Response()。

build.ts
const build = await Bun.build({
  /* */
});

const artifact = build.outputs[0];

// 会自动设置 Content-Type 头
return new Response(artifact);

The Bun runtime pretty-prints BuildArtifact objects to help with debugging.

build.ts
// build.ts
const build = await Bun.build({
  /* */
});

const artifact = build.outputs[0];
console.log(artifact);
Shell
bun run build.ts

BuildArtifact (entry-point) {
  path: "./index.js",
  loader: "tsx",
  kind: "entry-point",
  hash: "824a039620219640",
  Blob (74756 bytes) {
    type: "text/javascript;charset=utf-8"
  },
  sourcemap: BuildArtifact (sourcemap) {
    path: "./index.js.map",
    loader: "file",
    kind: "sourcemap",
    hash: "e7178cda3e72e301",
    Blob (24765 bytes) {
      type: "application/json;charset=utf-8"
    },
    sourcemap: null
  }
}

字节码

bytecode: boolean 选项会为任何 JavaScript/TypeScript 入口点生成字节码,这可以显著提升大型应用的启动速度。需要 "target": "bun" 以及匹配版本的 Bun。

  • CommonJS: Works with or without compile: true. Generates a .jsc file alongside each entrypoint.
  • ESM: Requires compile: true. Bun embeds the bytecode and module metadata in the standalone executable.

如果未显式指定 format,字节码默认为 CommonJS。

The bytecodeDepth: number option (a non-negative integer) limits how many levels of nested functions are compiled ahead of time (0 = only each module's top-level code). Functions past the limit are compiled from source when first called. Defaults to all.

build.ts
// CommonJS 字节码(生成 .jsc 文件)
await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
  bytecode: true,
})

// ESM 字节码(需要 compile)
await Bun.build({
  entrypoints: ["./index.tsx"],
  outfile: "./mycli",
  bytecode: true,
  format: "esm",
  compile: true,
})

可执行文件

Bun 支持将 JavaScript/TypeScript 入口“编译”为独立可执行文件,其中包含 Bun 二进制文件。

terminal
bun build ./cli.tsx --outfile mycli --compile
./mycli

请参阅独立可执行文件。

日志和错误

失败时,Bun.build 会返回一个被拒绝的、包含 AggregateError 的 Promise。将其记录到控制台可以美化打印错误列表,也可以通过 try/catch 代码块以编程方式读取。

build.ts
try {
  const result = await Bun.build({
    entrypoints: ["./index.tsx"],
    outdir: "./out",
  });
} catch (e) {
  // TS 不允许在 catch 加类型注解,故用断言
  const error = e as AggregateError;
  console.error("构建失败");

  // 示例:内置格式化打印
  console.error(error);

  // 示例:序列化成 JSON 字符串打印
  console.error(JSON.stringify(error, null, 2));
}

大多数情况下,不需要显式使用 try/catch,因为 Bun 会打印未捕获的异常。你可以直接在 Bun.build 调用前使用顶层 await。

错误列表中每条均为 BuildMessage 或 ResolveMessage 实例(继承自 Error),含详细信息:

build.ts
class BuildMessage {
  name: string;
  position?: Position;
  message: string;
  level: "error" | "warning" | "info" | "debug" | "verbose";
}

class ResolveMessage extends BuildMessage {
  code: string;
  referrer: string;
  specifier: string;
  importKind: ImportKind;
}

构建成功时,返回对象有 logs 字段,包含打包警告和信息。

build.ts
const result = await Bun.build({
  entrypoints: ["./index.tsx"],
  outdir: "./out",
});

if (result.logs.length > 0) {
  console.warn("构建成功但有警告:");
  for (const message of result.logs) {
    // Bun 会美化打印消息对象
    console.warn(message);
  }
}

参考

TypeScript
interface Bun {
  build(options: BuildOptions): Promise<BuildOutput>;
}

interface BuildConfig {
  entrypoints: string[]; // 入口文件数组
  outdir?: string; // 输出目录
  target?: Target; // 默认 "browser"
  /**
   * 输出模块格式,顶层 await 仅支持 "esm"
   *
   * 可选:
   * - "esm"
   * - "cjs" (**实验性**)
   * - "iife" (**实验性**)
   *
   * @default "esm"
   */
  format?: "esm" | "cjs" | "iife";
  /**
   * JSX 配置对象,控制 JSX 转换行为
   */
  jsx?: {
    runtime?: "automatic" | "classic";
    importSource?: string;
    factory?: string;
    fragment?: string;
    sideEffects?: boolean;
    development?: boolean;
  };
  naming?:
    | string
    | {
        chunk?: string;
        entry?: string;
        asset?: string;
      };
  root?: string; // project root
  splitting?: boolean; // default false, enable code splitting
  splitRequire?: boolean; // default true, with splitting and target "bun": require() of an ES module is a chunk too
  minChunkSize?: number; // default 0, also fold side-effect-free chunks smaller than this many source bytes
  modulePreload?: boolean; // default true, with splitting and target "browser": <link rel=modulepreload> the chunks an entrypoint or import() depends on
  plugins?: BunPlugin[];
  external?: string[];
  packages?: "bundle" | "external";
  publicPath?: string;
  define?: Record<string, string>;
  loader?: { [k in string]: Loader };
  sourcemap?: "none" | "linked" | "inline" | "external" | boolean; // 默认 "none",true 相当于 "inline"
  /**
   * package.json 的 `exports` 条件,用于解析导入
   *
   * 等同于 CLI 中 `--conditions` 参数
   *
   * https://nodejs.org/api/packages.html#exports
   */
  conditions?: Array<string> | string;

  /**
   * 打包时环境变量处理方式
   *
   * 可选:
   * - "inline": 将 `process.env.FOO` 替换为实际值字符串字面量
   * - "disable": 禁用环境变量注入
   * - 以 "*" 结尾的字符串: 内联匹配前缀的环境变量,如 "MY_PUBLIC_*"
   */
  env?: "inline" | "disable" | `${string}*`;
  minify?:
    | boolean
    | {
        whitespace?: boolean;
        syntax?: boolean;
        identifiers?: boolean;
      };
  /**
   * 忽略无用代码消除相关注释和 package.json 中 "sideEffects" 设置,仅作为库注释错误的临时解决方案
   */
  ignoreDCEAnnotations?: boolean;
  /**
   * 即使 minify.whitespace 为 true,也强制保留 @__PURE__ 注释
   */
  emitDCEAnnotations?: boolean;
  /**
   * Emit a setter per property on bundled module namespace objects (default).
   * @deprecated set to `false` for getter-only namespace objects; this becomes
   * the only behavior in a future release.
   */
  deprecatedNamespaceObjectSetters?: boolean;

  /**
   * 生成字节码,可显著减少冷启动时间,但会增加体积和内存占用。
   *
   * 仅支持 CommonJS (`format: "cjs"`),必须配合 `target: "bun"` 使用
   *
   * @default false
   */
  bytecode?: boolean;
  /**
   * How many levels of nested functions to compile to bytecode ahead of time
   * (a non-negative integer; `0` = only each module's top-level code).
   * Only used when `bytecode: true`.
   * @default undefined (all nested functions)
   */
  bytecodeDepth?: number;
  /**
   * Build-time optimizations for `bytecode` builds.
   */
  optimize?: {
    /**
     * Run JavaScriptCore's build-time optimization passes over the generated bytecode.
     * @default true
     */
    bytecode?: boolean;
  };
  /**
   * Add a banner to the bundled code such as "use client";
   */
  banner?: string;
  /**
   * 添加代码底部内容,如版权注释
   */
  footer?: string;

  /**
   * 移除匹配的函数调用
   */
  drop?: string[];

  /**
   * - true 时构建失败时拒绝并抛出 AggregateError;
   * - false 时返回 `{success: false}` 的 BuildOutput
   *
   * @default true
   */
  throw?: boolean;

  /**
   * 指定自定义 tsconfig.json 路径,用于路径解析
   * 等同于 CLI 的 `--tsconfig-override`
   */
  tsconfig?: string;

  outdir?: string;
}

interface BuildOutput {
  outputs: BuildArtifact[];
  success: boolean;
  logs: Array<BuildMessage | ResolveMessage>;
}

interface BuildArtifact extends Blob {
  path: string;
  loader: Loader;
  hash: string | null;
  kind: "entry-point" | "chunk" | "asset" | "sourcemap" | "bytecode";
  sourcemap: BuildArtifact | null;
}

type Loader =
  | "js"
  | "jsx"
  | "ts"
  | "tsx"
  | "css"
  | "json"
  | "jsonc"
  | "toml"
  | "yaml"
  | "text"
  | "file"
  | "napi"
  | "wasm"
  | "html";

interface BuildOutput {
  outputs: BuildArtifact[];
  success: boolean;
  logs: Array<BuildMessage | ResolveMessage>;
}

declare class ResolveMessage {
  readonly name: "ResolveMessage";
  readonly position: Position | null;
  readonly code: string;
  readonly message: string;
  readonly referrer: string;
  readonly specifier: string;
  readonly importKind:
    | "entry_point"
    | "stmt"
    | "require"
    | "import"
    | "dynamic"
    | "require_resolve"
    | "at"
    | "at_conditional"
    | "url"
    | "internal";
  readonly level: "error" | "warning" | "info" | "debug" | "verbose";

  toString(): string;
}

CLI 用法

bash
bun build <入口点>

通用配置

--productionboolean

设置 NODE_ENV=production 并启用压缩

--bytecodeboolean

编译时使用字节码缓存

--bytecode-depthnumber

预先编译为字节码的嵌套函数层数(非负整数)。默认编译所有层级

--no-optimize-bytecodeboolean

使用 --bytecode 时:跳过构建时的字节码优化过程

--targetstringdefault browser

打包的预期执行环境。可选 browser、bun 或 node

--conditionsstring

传递自定义解析条件

--envstringdefault disable

将环境变量内联到包中,形式为 process.env.${name}。要内联匹配某个前缀的变量,可以使用类似 FOO_PUBLIC_* 的通配符

输出与文件处理

--outdirstringdefault dist

输出目录(用于构建多个入口点时)

--outfilestring

输出到指定文件

--metafilestring

写入包含构建元数据的 JSON 文件

--metafile-mdstring

写入以可视化模块图的 Markdown 文件(便于 LLM 使用)

--sourcemapstringdefault none

生成源码映射。可选 linked、inline、external 或 none

--bannerstring

在输出文件前添加标头(例如 React 服务器组件的 "use client")

--footerstring

在输出文件尾部添加注释(例如 // built with bun!)

--formatstringdefault esm

输出包的模块格式。可选 esm、cjs 或 iife。当使用 --bytecode 时,默认值为 cjs。

文件命名

--entry-namingstringdefault [dir]/[name].[ext]

自定义入口点的文件名格式

--chunk-namingstringdefault [name]-[hash].[ext]

自定义代码块文件名格式

--asset-namingstringdefault [name]-[hash].[ext]

自定义资源文件名格式

打包选项

--rootstring

打包多个入口点时使用的根目录

--splittingboolean

启用共享模块的代码分割

--min-chunk-sizenumber

使用 --splitting 时,也会将小于此源字节数且无副作用的代码块,合并到由其所有导入方都能加载的代码块中

--no-module-preloadboolean

同时使用 --splitting 和 --target browser 时,不为入口点或 import() 所依赖的代码块生成 <link rel="modulepreload">

--public-pathstring

打包器添加到打包代码中导入路径前的前缀

--externalstring

从包中排除模块(支持通配符)。别名:-e

--allow-unresolvedstringdefault *

允许未解析且符合这些 glob 模式的动态 import()/require() 标识符。传入 '' 可允许不透明标识符

--reject-unresolvedboolean

如果任何动态 import()/require() 标识符无法在构建时解析,则构建失败

--packagesstringdefault bundle

依赖处理方式:external 或 bundle

--no-bundleboolean

只进行转译,不打包

--css-chunkingboolean

合并 CSS 文件以减少重复(仅当多个入口点引入 CSS 时生效)

压缩与优化

--emit-dce-annotationsbooleandefault true

重新输出死代码消除注释。在使用 --minify-whitespace 时禁用

--no-deprecated-namespace-object-settersboolean

输出仅包含 getter 的模块命名空间对象(赋值给属性会抛出错误,与真正的命名空间对象相同)。此选项将在未来版本中成为默认设置

--minifyboolean

启用所有压缩选项

--minify-syntaxboolean

压缩语法并内联常量

--minify-whitespaceboolean

压缩空白字符

--minify-identifiersboolean

压缩变量和函数标识符

--keep-namesboolean

压缩时保留原始的函数和类名称

开发功能

--watchboolean

文件变化时自动重建

--no-clear-screenboolean

使用 --watch 时不清屏

--react-fast-refreshboolean

启用 React 快速刷新转换(用于开发测试)

--react-compilerboolean

对 .jsx/.tsx 文件运行 React Compiler,自动对组件和钩子进行记忆化。打包器根据 --target 推导输出模式(browser → 客户端,bun/node → ssr)。实验性功能。

独立可执行文件

--compileboolean

生成一个包含该包的独立 Bun 可执行文件

--compile-exec-argvstring

向独立可执行文件的 execArgv 前置参数

--compile-jit-policynumberdefault 1

可执行文件启动时使用的 JIT 层级提升阈值倍率;1 表示正常的 JIT 策略。请参阅 Bun.unsafe.setJITPolicy

--compile-autoload-dotenvbooleandefault true

在独立可执行文件中启用 .env 文件的自动加载

--no-compile-autoload-dotenvboolean

在独立可执行文件中禁用 .env 文件的自动加载

--compile-autoload-bunfigbooleandefault true

在独立可执行文件中启用 bunfig.toml 的自动加载

--no-compile-autoload-bunfigboolean

在独立可执行文件中禁用 bunfig.toml 的自动加载

--compile-autoload-tsconfigbooleandefault false

在独立可执行文件运行时启用 tsconfig.json 的自动加载

--no-compile-autoload-tsconfigboolean

在独立可执行文件运行时禁用 tsconfig.json 的自动加载

--compile-autoload-package-jsonbooleandefault false

在独立可执行文件运行时启用 package.json 的自动加载

--no-compile-autoload-package-jsonboolean

在独立可执行文件运行时禁用 package.json 的自动加载

--compile-executable-pathstring

指定用于交叉编译的 Bun 可执行文件路径,以替代下载

--assetstring

将文件或目录嵌入编译后的可执行文件,并以其基本名称作为路径;目录会保留其内部树结构,因此 --asset ./static/public 会嵌入 public/...(需要 --compile)

Windows 可执行文件详情

--windows-hide-consoleboolean

运行编译后的 Windows 可执行文件时防止打开控制台窗口

--windows-iconstring

设置 Windows 可执行文件图标

--windows-titlestring

设置 Windows 可执行文件产品名称

--windows-publisherstring

设置 Windows 可执行文件公司名称

--windows-versionstring

设置 Windows 可执行文件版本(例如 1.2.3.4)

--windows-descriptionstring

设置 Windows 可执行文件描述

--windows-copyrightstring

设置 Windows 可执行文件版权声明

实验性功能及应用构建

--appboolean

(实验性) 使用 Bun Bake 构建生产环境的 Web 应用

--server-componentsboolean

(实验性) 启用 React 服务器组件

--debug-dump-server-filesboolean

当设置了 --app 时,即使是静态构建也将所有服务器文件导出到磁盘

--debug-no-minifyboolean

当设置了 --app 时,禁用所有压缩