Documentation Index
Fetch the complete documentation index at: https://bun.zhcndoc.com/llms.txt
Use this file to discover all available pages before exploring further.
Bun 通过 Bun.Archive 提供了一个快速的本地实现,用于操作 tar 归档文件。它支持从内存数据创建归档,解压归档到磁盘,以及读取归档内容而不进行解压。
快速开始
从文件创建归档:
const archive = new Bun.Archive({
"hello.txt": "Hello, World!",
"data.json": JSON.stringify({ foo: "bar" }),
"nested/file.txt": "嵌套内容",
});
// 写入磁盘
await Bun.write("bundle.tar", archive);
解压归档:
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const entryCount = await archive.extract("./output");
console.log(`解压了 ${entryCount} 个条目`);
读取归档内容而不解压:
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const files = await archive.files();
for (const [path, file] of files) {
console.log(`${path}: ${await file.text()}`);
}
创建归档
使用 new Bun.Archive() 从一个对象创建归档,该对象的键是文件路径,值是文件内容。默认情况下,归档是不压缩的:
// 创建未压缩的 tar 归档(默认)
const archive = new Bun.Archive({
"README.md": "# 我的项目",
"src/index.ts": "console.log('Hello');",
"package.json": JSON.stringify({ name: "my-project" }),
});
文件内容可以是:
- 字符串 - 文本内容
- Blob - 二进制数据
- ArrayBufferView(例如
Uint8Array)- 原始字节
- ArrayBuffer - 原始二进制数据
const data = "二进制数据";
const arrayBuffer = new ArrayBuffer(8);
const archive = new Bun.Archive({
"text.txt": "纯文本",
"blob.bin": new Blob([data]),
"bytes.bin": new Uint8Array([1, 2, 3, 4]),
"buffer.bin": arrayBuffer,
});
写入归档到磁盘
使用 Bun.write() 将归档写入磁盘:
// 写入未压缩的 tar(默认)
const archive = new Bun.Archive({
"file1.txt": "内容1",
"file2.txt": "内容2",
});
await Bun.write("output.tar", archive);
// 写入 gzip 压缩的 tar
const compressed = new Bun.Archive({ "src/index.ts": "console.log('Hello');" }, { compress: "gzip" });
await Bun.write("output.tar.gz", compressed);
获取归档字节数据
获取归档数据为字节或 Blob:
const archive = new Bun.Archive({ "hello.txt": "Hello, World!" });
// Uint8Array 格式
const bytes = await archive.bytes();
// Blob 格式
const blob = await archive.blob();
// 通过构造时设置 gzip 压缩
const gzipped = new Bun.Archive({ "hello.txt": "Hello, World!" }, { compress: "gzip" });
const gzippedBytes = await gzipped.bytes();
const gzippedBlob = await gzipped.blob();
解压归档
从现有归档数据创建
从现有的 tar/tar.gz 数据创建归档:
// 从文件
const tarball = await Bun.file("package.tar.gz").bytes();
const archiveFromFile = new Bun.Archive(tarball);
// 从 fetch 响应
const response = await fetch("https://example.com/archive.tar.gz");
const archiveFromFetch = new Bun.Archive(await response.blob());
解压到磁盘
使用 .extract() 将所有文件写入目录:
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const count = await archive.extract("./extracted");
console.log(`解压了 ${count} 个条目`);
目标目录如果不存在会自动创建。已有文件会被覆盖。返回的计数包括文件、目录和符号链接(在 POSIX 系统上)。
注意:在 Windows 上,归档中的符号链接始终会被跳过,Bun 不会尝试创建它们,无论权限如何。Linux 和 macOS 上,符号链接会正常解压。
安全提示:Bun.Archive 在解压时会验证路径,拒绝绝对路径(POSIX 的 /、Windows 的驱动器字母如 C:\ 或 C:/,以及 UNC 路径如 \\server\share)。路径遍历组件 (..) 会被规范化移除(例如 dir/sub/../file 会变成 dir/file),以防止目录逃逸攻击。
过滤解压的文件
使用 glob 模式只解压特定文件。模式针对归档条目路径,规范为使用正斜杠 (/)。正向模式指定包含,负向模式(以 ! 开头)指定排除。负向模式在正向模式后应用,因此仅使用负向模式将匹配不到任何文件(必须先包含一个正向模式如 **):
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
// 只解压 TypeScript 文件
const tsCount = await archive.extract("./extracted", { glob: "**/*.ts" });
// 解压多个目录中的文件
const multiCount = await archive.extract("./extracted", {
glob: ["src/**", "lib/**"],
});
使用负向模式(以 ! 开头)排除文件。当正负模式混用时,条目必须匹配至少一个正向模式且不匹配任何负向模式:
// 解压所有文件,除了 node_modules
const distCount = await archive.extract("./extracted", {
glob: ["**", "!node_modules/**"],
});
// 解压源码文件,但排除测试
const srcCount = await archive.extract("./extracted", {
glob: ["src/**", "!**/*.test.ts", "!**/__tests__/**"],
});
读取归档内容
获取所有文件
使用 .files() 获取归档内容作为 File 对象的 Map,并不会解压到磁盘。与 extract() 处理所有条目类型不同,files() 仅返回常规文件(无目录):
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const files = await archive.files();
for (const [path, file] of files) {
console.log(`${path}: ${file.size} 字节`);
console.log(await file.text());
}
每个 File 对象包含:
name - 归档内的文件路径(始终使用 / 作为分隔符)
size - 文件大小,单位字节
lastModified - 修改时间戳
- 标准的
Blob 方法:text()、arrayBuffer()、stream() 等
注意:files() 会将文件内容读取到内存中。对于大型归档,建议直接使用 extract() 解压到磁盘。
错误处理
归档操作可能因数据损坏、I/O 错误或无效路径失败。使用 try/catch 处理这些情况:
try {
const tarball = await Bun.file("package.tar.gz").bytes();
const archive = new Bun.Archive(tarball);
const count = await archive.extract("./output");
console.log(`解压了 ${count} 个条目`);
} catch (e: unknown) {
if (e instanceof Error) {
const error = e as Error & { code?: string };
if (error.code === "EACCES") {
console.error("权限被拒绝");
} else if (error.code === "ENOSPC") {
console.error("磁盘空间不足");
} else {
console.error("归档错误:", error.message);
}
} else {
console.error("归档错误:", String(e));
}
}
常见错误场景:
- 归档损坏/截断 -
new Archive() 会加载归档数据;错误可能延迟到读取/解压时出现
- 权限不足 - 目标目录不可写时,
extract() 会抛错
- 磁盘空间不足 - 空间不够时,
extract() 会抛错
- 路径无效 - 路径格式错误时,操作会抛错
extract() 返回的计数包含所有成功写入的条目(文件、目录及 POSIX 系统上的符号链接)。
安全提示:Bun.Archive 在解压时自动验证路径。拒绝绝对路径(POSIX /、Windows 驱动器字母、UNC 路径)和不安全的符号链接目标。路径中的遍历组件 (..) 会被规范化移除,防止目录逃逸。
对于不可信的归档,可以先枚举并验证路径:
const archive = new Bun.Archive(untrustedData);
const files = await archive.files();
// 可选:自定义验证做额外检查
for (const [path] of files) {
// 示例:拒绝隐藏文件
if (path.startsWith(".") || path.includes("/.")) {
throw new Error(`拒绝隐藏文件: ${path}`);
}
// 示例:只允许特定目录
if (!path.startsWith("src/") && !path.startsWith("lib/")) {
throw new Error(`路径不允许: ${path}`);
}
}
// 解压到受控目录
await archive.extract("./safe-output");
使用带 glob 模式的 files(),如果没有匹配,返回空的 Map:
const matches = await archive.files("*.nonexistent");
if (matches.size === 0) {
console.log("未找到匹配文件");
}
使用 Glob 模式过滤
传入 glob 模式来筛选要返回的文件:
// 只获取 TypeScript 文件
const tsFiles = await archive.files("**/*.ts");
// 获取 src 目录下文件
const srcFiles = await archive.files("src/*");
// 递归获取所有 JSON 文件
const jsonFiles = await archive.files("**/*.json");
// 用数组传入多种文件类型
const codeFiles = await archive.files(["**/*.ts", "**/*.js"]);
支持的 glob 模式(基于 Bun.Glob 语法的子集):
* - 匹配除 / 外的任意字符
** - 匹配包括 / 在内的任意字符
? - 匹配单个字符
[abc] - 匹配字符集
{a,b} - 匹配多个选项
!pattern - 排除匹配的文件(取反),必须配合正向模式使用;只用负向模式匹配不到任何文件
请参阅 Bun.Glob 了解完整的 glob 语法及转义和高级模式。
Bun.Archive 默认创建未压缩的 tar 归档。可通过 { compress: "gzip" } 启用 gzip 压缩:
// 默认:未压缩 tar
const archive = new Bun.Archive({ "hello.txt": "Hello, World!" });
// 读取:自动检测 gzip
const gzippedTarball = await Bun.file("archive.tar.gz").bytes();
const readArchive = new Bun.Archive(gzippedTarball);
// 启用 gzip 压缩
const compressed = new Bun.Archive({ "hello.txt": "Hello, World!" }, { compress: "gzip" });
// 自定义 gzip 压缩等级(1-12)
const maxCompression = new Bun.Archive({ "hello.txt": "Hello, World!" }, { compress: "gzip", level: 12 });
选项支持:
- 不传或
undefined - 默认未压缩 tar
{ compress: "gzip" } - 启用 gzip 压缩,默认等级 6
{ compress: "gzip", level: number } - gzip 压缩,自定义等级 1-12(1 为最快,12 为最小)
打包项目文件
import { Glob } from "bun";
// 收集源码文件
const files: Record<string, string> = {};
const glob = new Glob("src/**/*.ts");
for await (const path of glob.scan(".")) {
// 规范化路径分隔符为正斜杠,跨平台兼容
const archivePath = path.replaceAll("\\", "/");
files[archivePath] = await Bun.file(path).text();
}
// 添加 package.json
files["package.json"] = await Bun.file("package.json").text();
// 创建压缩归档并写入磁盘
const archive = new Bun.Archive(files, { compress: "gzip" });
await Bun.write("bundle.tar.gz", archive);
解压并处理 npm 包
const response = await fetch("https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz");
const archive = new Bun.Archive(await response.blob());
// 获取 package.json
const files = await archive.files("package/package.json");
const packageJson = files.get("package/package.json");
if (packageJson) {
const pkg = JSON.parse(await packageJson.text());
console.log(`包名: ${pkg.name}@${pkg.version}`);
}
从目录创建归档
import { readdir } from "node:fs/promises";
import { join } from "node:path";
async function archiveDirectory(dir: string, compress = false): Promise<Bun.Archive> {
const files: Record<string, Blob> = {};
async function walk(currentDir: string, prefix: string = "") {
const entries = await readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(currentDir, entry.name);
const archivePath = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
await walk(fullPath, archivePath);
} else {
files[archivePath] = Bun.file(fullPath);
}
}
}
await walk(dir);
return new Bun.Archive(files, compress ? { compress: "gzip" } : undefined);
}
const archive = await archiveDirectory("./my-project", true);
await Bun.write("my-project.tar.gz", archive);
注意:以下类型签名为文档简化版。完整类型定义请查看 packages/bun-types/bun.d.ts。
type ArchiveInput =
| Record<string, string | Blob | Bun.ArrayBufferView | ArrayBufferLike>
| Blob
| Bun.ArrayBufferView
| ArrayBufferLike;
type ArchiveOptions = {
/** 压缩算法,当前仅支持 "gzip"。 */
compress?: "gzip";
/** 压缩等级 1-12(启用 gzip 时默认 6)。 */
level?: number;
};
interface ArchiveExtractOptions {
/** 用于过滤解压文件的 glob 模式,支持以 "!" 开头的排除模式。 */
glob?: string | readonly string[];
}
class Archive {
/**
* 从输入数据创建归档
* @param data - 要归档的文件(对象形式)或已有归档数据(字节/Blob)
* @param options - 压缩选项。默认不压缩。
* 传入 { compress: "gzip" } 以启用压缩。
*/
constructor(data: ArchiveInput, options?: ArchiveOptions);
/**
* 解压归档到目录
* @returns 解压的条目数(包含文件、目录和符号链接)
*/
extract(path: string, options?: ArchiveExtractOptions): Promise<number>;
/**
* 获取归档 Blob(使用构造时的压缩设置)
*/
blob(): Promise<Blob>;
/**
* 获取归档 Uint8Array 字节数据(使用构造时的压缩设置)
*/
bytes(): Promise<Uint8Array<ArrayBuffer>>;
/**
* 以 File 对象形式获取归档内容(仅常规文件,无目录)
*/
files(glob?: string | readonly string[]): Promise<Map<string, File>>;
}