Bun v1.4 中新增
- 使用
Bun.XML.parse和Bun.XML.stringify解析和序列化 XML - 在运行时将 XML 文件
import和require为模块(包括热重载和 watch 模式支持) - 在使用 Bun 打包器的前端应用中
import和requireXML 文件
运行时 API
Bun.XML.parse()
将 XML 文档解析为普通 JavaScript 对象。
@attr/#text 约定。其工作方式如下:
- 结果只有一个键,即根元素的名称。
- 没有属性和子元素的元素会变成其字符数据:一个字符串;如果为空,则为
""。在 XML 中,<paid/>和<paid></paid>是同一回事。 - 其他元素都会变成对象。每个属性对应一个
"@name"键,然后按照各个子元素名称首次出现的顺序,为每个不同的子元素名称添加一个键,并为元素自身的文本添加一个"#text"键。 - 如果某个子元素名称在元素中出现多次,其键对应的值就是按文档顺序排列的数组;否则对应单个值。请参阅一个或多个。
compact用于选择结构,不会改变值。Bun 会按原样返回文本:包括前导、尾随和内部空白字符,展开 CDATA 段和实体引用,并将行尾规范化为\n——这与该元素对应的树结构所给出的文本相同。由于一个元素只有一个"#text",Bun 会串联该元素的文本片段,并忽略位于子元素之间的纯空白片段(即文档的排版空白)。如果文档经过手动排版(<name>\n value\n</name>),请在读取时进行修剪。- 所有值都是字符串。不会将任何值转换为数字、布尔值或
null。 - 名称保持原样,包括命名空间前缀(
"soap:Body");xmlns声明是普通属性。 - 注释、处理指令、
<?xml …?>声明和<!DOCTYPE …>都不会被表示。
@ 和 # 不能作为 XML 名称的开头,因此属性键和文本键不会与子元素键冲突。
紧凑结构用于 数据。它不会保留名称不同的兄弟元素之间的相对顺序,也不会保留文本相对于子元素的位置:
{ compact: false },以获取根元素的树结构,并保留元素内容在文档中的顺序:
{ name, attributes, children };即使为空,这两个键也都会存在。children 按顺序保存元素内容:文本以字符串形式保存(保持原样,包括纯空白片段,相邻文本会合并)、子元素、以 { comment } 表示的注释,以及以 { target, data } 表示的处理指令。可以根据对象所包含的键来区分对象子项。与紧凑结构一样,树结构不会表示声明、DOCTYPE,或根元素之前和之后的任何内容。
一个或多个
在紧凑结构中,一个元素的列表和两个元素的列表具有不同的类型(entry: {…} 与 entry: [{…}, {…}])。通常是字符串的元素,如果带有属性,也会变成对象(<title> 与 <title type="html">)。对于需要遍历或可能带有属性的值,请采取稳妥的处理方式:
children 是数组,每个元素都是一个对象。
输入类型和编码
XML.parse 接受字符串,或以 Buffer、TypedArray、DataView、ArrayBuffer 或 Blob 表示的字节数据。
字符串已经是解码后的文本,因此会检查其中 encoding 声明的语法,但除此之外会忽略它。字节数据会按照 XML 规则解码:字节顺序标记或 <?xml version="1.0" encoding="..."?> 中的 encoding 会指定使用 UTF-8(默认值)、UTF-16(任一字节序)或 ISO-8859-1。其他编码会抛出错误。
错误处理
当文档格式不正确时,Bun.XML.parse() 会抛出 SyntaxError(没有宽松模式);对于深度异常的嵌套,会抛出 RangeError:
Bun.XML.stringify()
将任一结构中的一个元素序列化为 XML。
name,并且具有 children 或 attributes 属性时,Bun 会将其作为树节点写入。在 children 中,包含 name 的对象是元素,包含 comment 的对象是注释,包含 target 的对象是处理指令。其他所有值都会被视为紧凑对象,其中一个键用于命名根元素。Bun 会按顺序写入键,并将 @ 键作为属性。字符串、数字、布尔值和 bigint 会通过 String() 转换为文本;Date 会转换为其 ISO 字符串。null 会变成空元素。与 JSON.stringify 一样,Bun 会跳过 undefined、函数和符号。数组会为每个项生成一个元素。
输出是格式正确的 XML,否则 stringify 会抛出错误。Bun 会转义 &、< 和 >。属性值中的 "、制表符和换行符,以及任意位置的回车符,都会以字符引用的形式写入,以便解析后保持不变。XML 无法容纳的值会导致错误,而不是生成损坏的文档:不是 XML 名称的元素和属性名称("first name"、"0")、XML 字符集之外的字符(U+0000 和其他控制字符、未配对的代理项——XML 1.0 没有可用于这些字符的转义形式)、注释中的 --、处理指令中的 ?>、根节点处的数组或其他数组中的数组,以及循环结构。
结果只有元素本身,不包含 <?xml …?> 声明和 DOCTYPE,因此你可以将结果串联到外层元素中。要写入文件,请自行添加序言:
美化打印
传入space 参数(空格数或缩进字符串,与 JSON.stringify 一样),即可缩进仅包含元素的内容。对于包含文本的元素,Bun 会将其写在同一行,因此缩进不会改变字符数据:
null 或 undefined。
对于 XML.parse 生成的值,无论采用哪种结构,XML.parse(XML.stringify(value)) 都会返回相等的值。
模块导入
ES Modules
你可以直接导入 XML 文件。Bun 会像处理传递给XML.parse 的字节数据一样解码文件(根据字节顺序标记或声明使用 UTF-8、UTF-16 或 ISO-8859-1)。模块的值是上文所述的紧凑对象:
config.xml
默认导入
app.ts
命名导入
根元素也可以作为命名导入使用:app.ts
CommonJS
app.ts
导入属性
使用with { type: "xml" } 将其他扩展名的文件作为 XML 解析:
XML 热重载
使用bun --hot 运行应用程序时,Bun 会在 XML 文件发生变化时重新加载它们:
server.ts
terminal
打包器集成
使用 Bun 打包时,打包器会在构建时解析导入的 XML 文件,并将它们内联为 JavaScript 对象:terminal
- 生产环境中零运行时 XML 解析开销
- 更小的打包体积
- 对未使用的属性进行 Tree Shaking
动态导入
你可以动态导入 XML 文件:一致性
Bun 的 XML 解析器使用 Rust 编写,并作为非验证处理器实现了 XML 1.0(第五版),不会读取外部实体:- 整个文档(包括内部 DTD 子集)都必须格式正确,否则会抛出
SyntaxError。 - 解析器会展开文档中声明的内部实体,并设置展开限制,因此“十亿笑声”攻击载荷会失败,而不是耗尽内存。它会规范化属性值,并应用内部子集中声明的属性默认值。
- 解析器不会获取或读取外部 DTD 或外部实体,因此不存在 XXE 攻击面。对于没有 DTD 的文档,引用未声明的实体会导致错误。如果 DOCTYPE 指向外部子集(或使用参数实体),而该子集可能声明了此实体,解析器就会保留原样的引用(
仍为 ),除非文档指定了standalone="yes"。 - 解析器不会根据 DTD 进行验证。它不会解析命名空间,并会原样保留带前缀的名称。
性能
与 Bun 的 JSON 解析器一样,解析器分两个阶段工作。SIMD 扫描阶段(运行时分派 AVX2/AVX-512/NEON/SVE 内核)会查找可能影响解析的字节,因此解析器无需逐字节扫描字符数据、属性值、注释和 CDATA 段。元素名称和属性名称会像JSON.parse 一样复用 JavaScriptCore 的原子字符串缓存。
bench/xml/xml.mjs 会在相同文档上比较 Bun.XML.parse 与常用的 npm 解析器(越低越好;Linux x64,单核):