S3
Bun 提供了快速的原生绑定,用于与兼容 S3 的对象存储服务交互。
生产服务器通常读取、上传和写入文件到兼容 S3 的对象存储服务,而不是本地文件系统。历史上这意味着开发时使用的本地文件系统 API 无法在生产环境中使用。但使用 Bun,情况则有所不同。
Bun 的 S3 API 很快

Bun 提供了快速的原生绑定,用于与兼容 S3 的对象存储服务交互。其 S3 API 类似于 fetch 的 Response 和 Blob API(就像 Bun 的本地文件系统 API)。
import { s3, write, S3Client } from "bun";
// Bun.s3 会读取环境变量中的凭据
// file() 返回对 S3 上文件的惰性引用
const metadata = s3.file("123.json");
// 从 S3 下载 JSON
const data = await metadata.json();
// 上传到 S3
await write(metadata, JSON.stringify({ name: "John", age: 30 }));
// 预签名 URL(同步 - 无需网络请求)
const url = metadata.presign({
acl: "public-read",
expiresIn: 60 * 60 * 24, // 1 天
});
// 删除文件
await metadata.delete();
S3 是事实上的标准互联网文件系统。Bun 的 S3 API 支持与以下兼容 S3 的存储服务协作:
- AWS S3
- Cloudflare R2
- DigitalOcean Spaces
- MinIO
- Backblaze B2
- …以及其他任何兼容 S3 的存储服务。
基本用法
Bun.S3Client & Bun.s3
Bun.s3 等同于 new Bun.S3Client(),依赖环境变量中的凭据。
若需显式设置凭据,可通过构造函数传递给 Bun.S3Client。
import { S3Client } from "bun";
const client = new S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// sessionToken: "..."
// acl: "public-read",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
// endpoint: "https://<region>.digitaloceanspaces.com", // DigitalOcean Spaces
// endpoint: "http://localhost:9000", // MinIO
});
// Bun.s3 是一个全局单例,等同于 `new Bun.S3Client()`
处理 S3 文件
S3Client 中的 file 方法返回对 S3 上文件的 惰性引用。
// 对 S3 上文件的惰性引用
const s3file: S3File = client.file("123.json");
与 Bun.file(path) 类似,S3Client 的 file 方法是同步的。在调用需要网络请求的方法之前,它不会发起任何网络请求。
从 S3 读取文件
S3File 扩展自 Blob,因此适用于 Blob 的方法同样适用于 S3File。
// 将 S3File 作为文本读取
const text = await s3file.text();
// 将 S3File 作为 JSON 读取
const json = await s3file.json();
// 将 S3File 作为 ArrayBuffer 读取
const buffer = await s3file.arrayBuffer();
// 仅获取前 1024 字节
const partial = await s3file.slice(0, 1024).text();
// 流式读取文件
const stream = s3file.stream();
for await (const chunk of stream) {
console.log(chunk);
}
内存优化
像 text()、json()、bytes() 或 arrayBuffer() 这样的方法会尽量避免在内存中复制字符串或字节。
如果文本恰好是 ASCII,Bun 会直接将字符串传输到 JavaScriptCore(引擎),无需转码,也不会在内存中创建副本。.bytes() 和 .arrayBuffer() 同样会避免在内存中复制字节。
写入与上传文件至 S3
写入到 S3 的方式也一样。
// 写入一个字符串(替换文件)
await s3file.write("Hello World!");
// 写入一个 Buffer(替换文件)
await s3file.write(Buffer.from("Hello World!"));
// 写入一个 Response(替换文件)
await s3file.write(new Response("Hello World!"));
// 带内容类型写入
await s3file.write(JSON.stringify({ name: "John", age: 30 }), {
type: "application/json",
});
// 使用内容编码写入(例如用于预压缩数据)
await s3file.write(compressedData, {
type: "application/json",
contentEncoding: "gzip",
});
// 使用内容处置写入
await s3file.write(pdfData, {
type: "application/pdf",
contentDisposition: 'attachment; filename="report.pdf"',
});
// 使用 writer(流式)
const writer = s3file.writer({ type: "application/json" });
writer.write("Hello");
writer.write(" World!");
await writer.end();
// 使用 Bun.write 写入
await Bun.write(s3file, "Hello World!");
处理大文件(流)
Bun 会自动为大文件处理分段上传,并支持流式传输。适用于本地文件的同一套 API 也适用于 S3 文件。
// 写入大文件
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const writer = s3file.writer({
// 遇到网络错误自动重试最多 3 次
retry: 3,
// 同时排队最多 10 个请求
queueSize: 10,
// 以 5MB 分块上传
partSize: 5 * 1024 * 1024,
});
for (let i = 0; i < 10; i++) {
writer.write(bigFile);
await writer.flush();
}
await writer.end();
预签名 URL
当你的生产服务需要允许用户上传文件时,通常让用户直接上传到 S3 比你的服务器作为中介更可靠。
要让用户直接上传到 S3,请为 S3 文件生成预签名 URL。生成预签名 URL 时会创建一个带签名的 URL,允许用户将指定文件上传到 S3,而不会暴露你的凭据,也不会授予他们对存储桶不必要的访问权限。
默认情况下,Bun 会生成一个有效期为 24 小时的 GET URL。
import { s3 } from "bun";
// 生成一个有效期为 24 小时的预签名 URL(默认)
const download = s3.presign("my-file.txt"); // GET,有效期为 24 小时
const upload = s3.presign("my-file", {
expiresIn: 3600, // 1 小时
method: "PUT",
type: "application/json", // 设置预签名 URL 中的响应内容类型
});
// 使用内容处置进行预签名(例如强制使用特定文件名进行下载)
const downloadUrl = s3.presign("report.pdf", {
expiresIn: 3600,
contentDisposition: 'attachment; filename="quarterly-report.pdf"',
});
// 如果已有文件引用,也可以调用 .presign(),但应避免频繁这样做
// 以减少内存占用。
const myFile = s3.file("my-file.txt");
const presignedFile = myFile.presign({
expiresIn: 3600, // 1 小时
});
设置 ACL
要为预签名 URL 设置 ACL(访问控制列表),传入 acl 选项:
const url = s3file.presign({
acl: "public-read",
expiresIn: 3600,
});
你可以传入以下 ACL:
| ACL | 说明 |
|---|---|
"public-read" | 对象可被公开读取。 |
"private" | 对象仅桶所有者可读取。 |
"public-read-write" | 对象可被公开读取和写入。 |
"authenticated-read" | 桶所有者和已认证用户可读取。 |
"aws-exec-read" | 发送请求的 AWS 账户可读取对象。 |
"bucket-owner-read" | 桶所有者可读取对象。 |
"bucket-owner-full-control" | 桶所有者可读写对象。 |
"log-delivery-write" | AWS 用于日志传递的服务可以写入对象。 |
设置 URL 过期时间
设置预签名 URL 的过期时间,传入 expiresIn 选项。
const url = s3file.presign({
// 秒数
expiresIn: 3600, // 1 小时
// 访问控制列表
acl: "public-read",
// HTTP 方法
method: "PUT",
});
method
设置预签名 URL 的 HTTP 方法,传入 method 选项。
const url = s3file.presign({
method: "PUT",
// method: "DELETE",
// method: "GET",
// method: "HEAD",
// method: "POST",
// method: "PUT",
});
new Response(S3File)
要将用户重定向到 S3 文件的预签名 URL,请将一个 S3File 实例作为 body 传给 Response 对象。
此响应会将用户重定向到 S3 文件的预签名 URL。这样可以避免将文件下载到你的服务器并发送回用户所需的内存、时间和带宽开销。
const response = new Response(s3file);
console.log(response);
Response (0 KB) {
ok: false,
url: "",
status: 302,
statusText: "",
headers: Headers {
"location": "https://<account-id>.r2.cloudflarestorage.com/...",
},
redirected: true,
bodyUsed: false
}
对兼容 S3 服务的支持
Bun 的 S3 实现适用于任何兼容 S3 的存储服务。指定适当的端点:
使用 Bun 的 S3Client 连接 AWS S3
AWS S3 是默认选项。使用 AWS S3 时,可以传递 region 选项,而不是 endpoint 选项。
import { S3Client } from "bun";
// AWS S3
const s3 = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// region: "us-east-1",
});
使用 Bun 的 S3Client 连接 Google Cloud Storage
使用 Bun 的 S3 客户端连接 Google Cloud Storage,在 S3Client 构造器中将 endpoint 设置为 "https://storage.googleapis.com"。
import { S3Client } from "bun";
// Google Cloud Storage
const gcs = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
endpoint: "https://storage.googleapis.com",
});
使用 Bun 的 S3Client 连接 Cloudflare R2
使用 Bun 的 S3 客户端连接 Cloudflare R2,在 S3Client 构造器中将 endpoint 设置为包含你的账号 ID 的 R2 端点。
import { S3Client } from "bun";
// CloudFlare R2
const r2 = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
});
使用 Bun 的 S3Client 连接 DigitalOcean Spaces
使用 Bun 的 S3 客户端连接 DigitalOcean Spaces,在 S3Client 构造器中将 endpoint 设置为对应区域的 DigitalOcean Spaces 端点。
import { S3Client } from "bun";
const spaces = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
// region: "nyc3",
endpoint: "https://<region>.digitaloceanspaces.com",
});
使用 Bun 的 S3Client 连接 MinIO
使用 Bun 的 S3 客户端连接 MinIO,在 S3Client 构造器中将 endpoint 设置为 MinIO 运行的 URL。
import { S3Client } from "bun";
const minio = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
// 确保使用正确的 endpoint URL
// 在生产环境中可能不是 localhost!
endpoint: "http://localhost:9000",
});
使用 Bun 的 S3Client 连接 supabase
使用 Bun 的 S3 客户端连接 Supabase,在 S3Client 构造器中将 endpoint 设置为 Supabase 端点。Supabase 端点包含你的账号 ID 和 /storage/v1/s3 路径。在 Supabase 控制台的 https://supabase.com/dashboard/project/<account-id>/settings/storage 中,开启 Enable connection via S3 protocol,并使用该部分显示的区域。
import { S3Client } from "bun";
const supabase = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
region: "us-west-1",
endpoint: "https://<account-id>.supabase.co/storage/v1/s3",
});
使用 Bun 的 S3Client 连接 S3 虚拟主机式端点
使用虚拟主机式端点时,将 virtualHostedStyle 选项设置为 true。
import { S3Client } from "bun";
// 从 region 和 bucket 推断的 AWS S3 端点
const s3 = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
virtualHostedStyle: true, // [!code ++]
// endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
// region: "us-east-1",
});
// AWS S3 指定 endpoint
const s3WithEndpoint = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
virtualHostedStyle: true, // [!code ++]
});
// Cloudflare R2 指定 endpoint
const r2WithEndpoint = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
virtualHostedStyle: true, // [!code ++]
});
凭据
默认情况下,Bun 会读取以下环境变量作为凭据。
| 选项名称 | 环境变量 |
|---|---|
accessKeyId | S3_ACCESS_KEY_ID |
secretAccessKey | S3_SECRET_ACCESS_KEY |
region | S3_REGION |
endpoint | S3_ENDPOINT |
bucket | S3_BUCKET |
sessionToken | S3_SESSION_TOKEN |
对于每个选项,如果未设置 S3_* 环境变量,Bun 会回退使用对应的 AWS_* 环境变量。
| 选项名称 | 备用环境变量 |
|---|---|
accessKeyId | AWS_ACCESS_KEY_ID |
secretAccessKey | AWS_SECRET_ACCESS_KEY |
region | AWS_REGION |
endpoint | AWS_ENDPOINT |
bucket | AWS_BUCKET |
sessionToken | AWS_SESSION_TOKEN |
Bun 会在初始化时从 .env 文件或进程环境中读取这些环境变量(Bun 不会为此使用 process.env)。
你传递给 s3.file(credentials)、new Bun.S3Client(credentials) 或任何接受凭据的方法的选项会覆盖这些默认值。因此,如果你对不同的存储桶使用相同的凭据,可以在 .env 文件中设置一次凭据,然后只将 bucket: "my-bucket" 传递给 s3.file()。
Bun 会通过 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY 和 NO_PROXY 为 endpoint 选择的代理发送 S3 请求,方式与 fetch 相同。
S3Client 对象
如果你不使用环境变量,或者正在使用多个存储桶,可以创建一个 S3Client 对象来显式设置凭据。
import { S3Client } from "bun";
const client = new S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// sessionToken: "..."
endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
// endpoint: "http://localhost:9000", // MinIO
});
const file = client.file("my-file.txt");
// Write using a Response
await file.write(new Response("Hello World!"));
// 预签名 URL
const url = file.presign({
expiresIn: 60 * 60 * 24, // 1 天
acl: "public-read",
});
// 删除文件
await file.delete();
S3Client.prototype.write
向 S3 上传或写入文件,可调用 S3Client 实例的 write 方法。
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
endpoint: "https://s3.us-east-1.amazonaws.com",
bucket: "my-bucket",
});
await client.write("my-file.txt", "Hello World!");
await client.write("my-file.txt", new Response("Hello World!"));
// 等价于
// await client.file("my-file.txt").write("Hello World!");
S3Client.prototype.delete
删除 S3 文件,调用 S3Client 实例的 delete 方法。
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
});
await client.delete("my-file.txt");
// 等价于
// await client.file("my-file.txt").delete();
S3Client.prototype.exists
检查 S3 文件是否存在,调用 S3Client 实例的 exists 方法。
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
});
const exists = await client.exists("my-file.txt");
// 等价于
// const exists = await client.file("my-file.txt").exists();
S3File
在 S3Client 实例上调用 file() 或调用 s3.file(),会返回一个 S3File。与 Bun.file() 一样,S3File 实例是惰性的:它们在创建时不一定指向实际存在的对象。因此,所有不涉及网络请求的方法都是完全同步的。
interface S3File extends Blob {
slice(start: number, end?: number): S3File;
exists(): Promise<boolean>;
unlink(): Promise<void>;
presign(options: S3Options): string;
text(): Promise<string>;
json(): Promise<any>;
bytes(): Promise<Uint8Array>;
arrayBuffer(): Promise<ArrayBuffer>;
stream(options: S3Options): ReadableStream;
write(
data: string | Uint8Array | ArrayBuffer | Blob | ReadableStream | Response | Request,
options?: BlobPropertyBag,
): Promise<number>;
exists(options?: S3Options): Promise<boolean>;
unlink(options?: S3Options): Promise<void>;
delete(options?: S3Options): Promise<void>;
presign(options?: S3Options): string;
stat(options?: S3Options): Promise<S3Stat>;
/**
* 由于需要网络请求,大小无法同步获取。
*
* @deprecated 请使用 `stat()` 替代。
*/
size: NaN;
// …为简洁起见省略部分内容
}
与 Bun.file() 一样,S3File 继承自 Blob,因此 Blob 上提供的所有方法也都可以在 S3File 上使用。用于从本地文件读取数据的相同 API,也可以用于从 S3 读取数据。
| 方法 | 输出 |
|---|---|
await s3File.text() | string |
await s3File.bytes() | Uint8Array |
await s3File.json() | JSON |
s3File.stream() | ReadableStream |
await s3File.arrayBuffer() | ArrayBuffer |
由于 S3File 继承自 Blob,S3File 实例可与 fetch()、Response 以及其他接受 Blob 实例的 Web API 配合使用。
使用 slice 部分读取
要读取文件的部分范围,请使用 slice 方法。
const partial = s3file.slice(0, 1024);
// 将部分范围以 Uint8Array 读取
const bytes = await partial.bytes();
// 将部分范围以字符串读取
const text = await partial.text();
在内部,Bun 使用 HTTP Range 标头来请求所需的字节。此 slice 方法与 Blob.prototype.slice 相同。
从 S3 删除文件
要从 S3 删除文件,请使用 delete 方法。
await s3file.delete();
// await s3File.unlink();
delete 与 unlink 同义。
错误代码
当 Bun 的 S3 API 抛出错误时,错误对象会带有 code 属性,其值为以下之一:
ERR_S3_MISSING_CREDENTIALSERR_S3_INVALID_METHODERR_S3_INVALID_PATHERR_S3_INVALID_ENDPOINTERR_S3_INVALID_SIGNATUREERR_S3_INVALID_SESSION_TOKEN
当 S3 服务本身返回错误时(也就是说,不是 Bun 返回的错误),该错误是一个 S3Error 实例:一个名称为 "S3Error" 的 Error 实例。
S3Client 静态方法
S3Client 类提供了多个静态方法用于操作 S3。
静态方法 S3Client.write
要直接将数据写入存储桶中的某个路径,请使用 S3Client.write 静态方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
// Write string
await S3Client.write("my-file.txt", "Hello World", credentials);
// 指定类型写入 JSON
await S3Client.write("data.json", JSON.stringify({ hello: "world" }), {
...credentials,
type: "application/json",
});
// fetch 后写入
const res = await fetch("https://example.com/data");
await S3Client.write("data.bin", res, credentials);
// 写入并设置 ACL
await S3Client.write("public.html", html, {
...credentials,
acl: "public-read",
type: "text/html",
});
这等价于调用 new S3Client(credentials).write("my-file.txt", "Hello World")。
静态方法 S3Client.presign
要为 S3 文件生成预签名 URL,请使用 S3Client.presign 静态方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const url = S3Client.presign("my-file.txt", {
...credentials,
expiresIn: 3600,
});
这等价于调用 new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 })。
静态方法 S3Client.list
要列出存储桶中的部分或全部对象(最多 1,000 个),请使用 S3Client.list 静态方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
// 列出最多 1000 个存储桶中的对象
const allObjects = await S3Client.list(null, credentials);
// 列出 `uploads/` 前缀下最多 500 个对象,并获取每个对象的 owner 字段
const uploads = await S3Client.list(
{
prefix: "uploads/",
maxKeys: 500,
fetchOwner: true,
},
credentials,
);
// 检查是否有更多结果
if (uploads.isTruncated) {
// 列出 `uploads/` 前缀下的下一批对象
const moreUploads = await S3Client.list(
{
prefix: "uploads/",
maxKeys: 500,
startAfter: uploads.contents!.at(-1)!.key,
fetchOwner: true,
},
credentials,
);
}
这等价于调用 new S3Client(credentials).list()。
静态方法 S3Client.exists
要检查 S3 文件是否存在,请使用 S3Client.exists 静态方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const exists = await S3Client.exists("my-file.txt", credentials);
同样方法也适用于 S3File 实例。
import { s3 } from "bun";
const s3file = s3.file("my-file.txt", {
// ...credentials,
});
const exists = await s3file.exists();
静态方法 S3Client.size
要在不下载 S3 文件的情况下检查其大小,请使用 S3Client.size 静态方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const bytes = await S3Client.size("my-file.txt", credentials);
这等价于调用 new S3Client(credentials).size("my-file.txt")。
静态方法 S3Client.stat
要获取 S3 文件的大小、etag 及其他元数据,请使用 S3Client.stat 静态方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const stat = await S3Client.stat("my-file.txt", credentials);
{
etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
lastModified: 2025-01-07T00:19:10.000Z,
size: 1024,
type: "text/plain;charset=utf-8",
}
静态方法 S3Client.delete
要删除 S3 文件,请使用 S3Client.delete 静态方法。
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
};
await S3Client.delete("my-file.txt", credentials);
// 等价于
// await new S3Client(credentials).delete("my-file.txt");
// S3Client.unlink 是 S3Client.delete 的别名
await S3Client.unlink("my-file.txt", credentials);
s3:// 协议
fetch 和 Bun.file() 支持 s3:// 协议,因此相同的代码可用于本地文件和 S3 文件。
const response = await fetch("s3://my-bucket/my-file.txt");
const file = Bun.file("s3://my-bucket/my-file.txt");
你还可以向 fetch 和 Bun.file 传入 s3 选项。
const response = await fetch("s3://my-bucket/my-file.txt", {
s3: {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
endpoint: "https://s3.us-east-1.amazonaws.com",
},
headers: {
range: "bytes=0-1023",
},
});
UTF-8、UTF-16 和 BOM(字节顺序标记)
跟 Response 和 Blob 一样,S3File 默认假定为 UTF-8 编码。
调用 S3File 的 text() 或 json() 时:
- 当 Bun 检测到 UTF-16 字节顺序标记(BOM)时,会将数据视为 UTF-16。JavaScriptCore 原生支持 UTF-16,因此 Bun 会跳过 UTF-8 转码步骤(并移除 BOM)。由此产生的一个结果是,UTF-16 字符串中的无效代理项对会直接传递给 JavaScriptCore(与源代码相同)。
- 当 Bun 检测到 UTF-8 BOM 时,会移除 BOM,并在将字符串传递给 JavaScriptCore 之前,将无效的 UTF-8 码点替换为 Unicode 替换字符(
\uFFFD)。 - 不支持 UTF-32。