加载器与资源
本教程共 34 篇 · 第 17 篇 · 更新于 2026-08-06
本节目标:
- 理解 loader(加载器)在打包过程中扮演的角色
- 掌握内置 loader 清单,以及用导入属性/配置覆盖默认行为
- 学会处理 CSS、图片、字体等静态资源
- 学会在代码中直接导入 JSON、TOML、YAML 等数据文件
打包器最核心的能力之一,就是把”非 JavaScript 的东西”变成 JavaScript 能消费的东西。比如你在代码里写了 import "./style.css",打包器需要知道”CSS 该怎么办”;你写了 import logo from "./logo.svg",打包器得决定”这个图片是复制出去、还是内联成字符串”。Bun 用 loader 机制统一管理这类转换规则。
17.1 什么是 loader
loader(加载器)决定了”遇到某种文件类型时,打包器怎么做”。当打包器在依赖分析阶段遇到一个导入,它会根据文件扩展名(或你显式指定的规则)选择一个 loader,由该 loader 把文件内容转换成可打包的形式——可能是内联成字符串、解析成 JS 对象,也可能是复制成独立文件并替换为最终 URL。
Bun 内置了丰富的 loader,覆盖常见场景:
- 代码类:
js/jsx/ts/tsx,直接作为代码参与打包; - 数据类:
json/jsonc(带注释的 JSON)/toml/yaml/text,在打包时内联为对应对象或字符串; - 资源类:
css走内置 CSS 处理,file把文件复制到输出目录并返回路径,dataurl把文件内联成data:URI,wasm处理 WebAssembly; - 特殊类:
html用于 HTML 入口与资源重写(见第 20 章),sqlite把 SQLite 数据库嵌入产物(仅 target=bun),napi对应.node原生插件。
默认按扩展名匹配的文件类型是这一串:.js .cjs .mjs .mts .cts .ts .tsx .jsx .css .json .jsonc .toml .yaml .yml .txt .wasm .node .html。
Note对于 Bun 不认识、又没有显式配置 loader 的文件类型,默认会回退到
fileloader:把它当作静态资源复制到输出目录,导入值变成最终的资源 URL 字符串。这让”未知文件也不会报错”。
17.2 用导入属性指定 loader
最推荐的做法,是用 ECMAScript 的”导入属性”(import attributes)在 import 语句里就地声明文件类型。语法是在导入路径后加 with { type: "..." }:
// 把 TOML 文件作为对象直接导入
import config from "./config.toml" with { type: "toml" };
console.log(config.server.port);
// 把任意文件作为纯文本字符串导入
import readme from "./README.md" with { type: "text" };
console.log(readme.length);
这种方式的好处是:loader 的选择和导入语句写在一起,可读性高,也避免了全局配置”影响所有同名扩展名文件”的副作用。常见的 type 取值包括 toml、yaml、json、text、file、sqlite 等,与内置 loader 一一对应。
Tip当你只想在某个特定导入上覆盖默认行为时,优先用
with { type }而不是全局loader配置。范围越小,越不容易”误伤”其他文件。
17.3 用配置/CLI 覆盖 loader
如果你希望”所有 .conf 文件都按 TOML 解析”,或者命令行快速试一下,可以用 loader 选项(JS API)或 --loader 参数全局映射扩展名到 loader:
await Bun.build({
entrypoints: ["./index.ts"],
outdir: "./dist",
loader: {
// 把 .conf 扩展名映射成 toml loader
".conf": "toml",
// 把 .data 映射成 text loader
".data": "text",
},
});
# CLI 方式:扩展名和 loader 名之间用冒号分隔,可以重复传多次
bun build ./index.ts --outdir ./dist --loader .conf:toml --loader .data:text
注意 CLI 的分隔符是冒号(.conf:toml),别写成等号。JS API 里的 loader 则是一个”扩展名 → loader 名”的映射表,键要带点号(如 ".conf")。
映射表适合做项目级的统一约定,with { type } 适合做局部、一次性覆盖。两者可以共存,导入属性优先级更高。
17.4 处理 CSS
CSS 在 Bun 里有”一等公民”待遇。它的 CSS 解析器与打包器是 LightningCSS 的直接移植,打包思路参考了 esbuild,默认就做转译与厂商前缀补全。你在入口里 import "./style.css",打包器会把它收集起来,最终输出成一个 .css 文件(或在 HTML 场景下自动关联)。
// 在 JS/TS 入口中导入 CSS
import "./styles/global.css";
document.body.textContent = "Hello Bun";
/* styles/global.css */
:root {
--brand: oklch(70% 0.2 250);
}
.box {
background: var(--brand);
padding: 1rem;
}
现代 CSS 特性如嵌套(nesting)、color-mix()、@import 合并等都被支持;Bun 还会对标目标浏览器做语法降级与前缀补全。关于 CSS Modules(.module.css)、组合(composes)等更深入的话题,属于 CSS 子主题的进阶内容,这里先建立”CSS 可被直接 import”的认识。
Warning如果你在浏览器 target 下导入 CSS,产物是一个独立的
.css文件,需要你自己用<link>引用,或在 HTML 入口中让它自动关联(见第 20 章)。不要假设 CSS 会被自动塞进 JS。
17.5 处理图片、字体等静态资源(file loader)
图片、字体、音视频这类二进制资源,默认走 file loader:打包时复制到输出目录,导入值变成指向产物的路径字符串。产物名按 --asset-naming 生成,默认模板是 [name]-[hash].[ext],内容哈希天然避开了缓存冲突。
import logo from "./logo.svg";
const img = document.createElement("img");
img.src = logo; // logo 是打包后的 URL,例如 "./logo-a7305bdef.svg"
document.body.appendChild(img);
注意上面的 logo 打包后并不是文件对象,而是一个字符串路径。这就是 file loader 的标准行为:把”文件”抽象成”可引用的地址”。想强调这层意图,可以显式写 with { type: "file" }。
字体、PDF、压缩包等也是同理:凡是”应该原样复制到输出目录、代码里只引用其路径”的,基本都交给 file loader 处理。
import resume from "./assets/resume.pdf" with { type: "file" };
console.log("简历下载地址:", resume);
17.6 导入数据文件(json / toml / yaml / text)
数据文件是 loader 的”高光场景”。Bun 在打包期就把结构化数据内联进产物,运行时不依赖外部文件:
// JSON(含 jsonc,支持注释)
import pkg from "./package.json" with { type: "json" };
console.log(pkg.name);
// TOML
import dbConfig from "./db.toml" with { type: "toml" };
console.log(dbConfig.database.url);
// YAML
import specs from "./openapi.yaml" with { type: "yaml" };
console.log(specs.info.title);
// 纯文本
import banner from "./banner.txt" with { type: "text" };
console.log(banner);
json/jsonc 导入为 JS 对象,toml/yaml 同样解析为对象,text 则保留为字符串。这类内联意味着:构建完成后,这些配置文件已经”烧录”进产物,部署时无需随包携带源数据文件(除非你刻意用 file loader 保留为外部引用)。
Tip对于需要在运行时动态切换的配置(比如不同环境的不同密钥),不要用
type: "json"内联,而应走环境变量(--env)或define/运行时读取,避免把敏感或易变数据”固化”进构建产物。
17.7 其他 loader 速览
napi:.node原生插件的默认 loader,让 Bun 运行时能加载 C/C++ 编译的 Node 插件。要留意一个差异——在打包器里.node文件走的是fileloader,也就是被当成静态资源复制出去,而不是就地链接。wasm:导入.wasm时返回 WebAssembly 模块,配合WebAssembly.instantiate使用。sqlite:通过import db from "./app.db" with { type: "sqlite" }把 SQLite 数据库嵌入产物,仅当 target=bun 时可用,适合做带内嵌数据的工具或可执行文件。dataurl:把文件内容编码成data:URI 直接内联,适合小图标这类”不值得单独发一次请求”的资源,常配合--loader .png:dataurl使用。sh:Bun Shell 脚本只在用bun直接执行该.sh文件时生效,打包器的依赖分析与运行时的import都用不了,别指望能import "./deploy.sh"。
覆盖面已经相当广了。但”自定义处理规则”这类需求——比如把 .md 转成 HTML、给图片加自定义压缩——超出了内置 loader 的能力,得靠第 18 章的插件系统。
17.8 小结
本章我们掌握了 Bun 打包器的资源处理主干:
- loader 决定”遇到某类文件时打包器怎么做”,未知类型默认回退到
file; - 局部覆盖用
with { type: "..." }导入属性,全局约定用loader映射或--loader .ext:name; - CSS 是一等公民,可直接 import,由 LightningCSS 移植而来的管线转译与补全前缀;
- 图片、字体等走
fileloader,导入值是指向产物的路径字符串; json/jsonc/toml/yaml/text会在打包期内联为对象或字符串;dataurl/wasm/sqlite/napi各有分工,sh只在 Bun 直接执行脚本时生效。
Tiploader 选择冲突时,导入属性
with { type }优先级最高,其次是Bun.build的loader映射,最后才是按扩展名推断。记住这个顺序,排查”为什么某个文件没按预期处理”会快很多。
有了 loader 打底,下章用插件系统对任意文件做完全自定义的转换。