@xmldom/xmldom vs fast-xml-parser vs libxmljs vs xml2js vs xmlbuilder
JavaScript 生态中的 XML 处理方案选型
@xmldom/xmldomfast-xml-parserlibxmljsxml2jsxmlbuilder类似的npm包:

JavaScript 生态中的 XML 处理方案选型

@xmldom/xmldom、fast-xml-parser、libxmljs、xml2js 和 xmlbuilder 都是用于在 JavaScript 环境中处理 XML 数据的工具库,但它们的侧重点和底层实现截然不同。

@xmldom/xmldom 提供了标准的 W3C DOM API,适合需要操作 DOM 树的场景;fast-xml-parser 和 xml2js 专注于将 XML 快速转换为 JavaScript 对象,前者性能更优,后者更经典;libxmljs 基于原生 C++ 库,支持 XPath 等高级功能但仅限 Node.js;xmlbuilder 则专门用于以编程方式构建和生成 XML 字符串。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
@xmldom/xmldom0474440 kB652 个月前MIT
fast-xml-parser03,1391.34 MB2812 小时前MIT
libxmljs01,05717.7 MB703 年前MIT
xml2js04,9603.44 MB2583 年前MIT
xmlbuilder0928-87 年前MIT

JavaScript XML 处理方案深度对比:解析、性能与架构选型

在 JavaScript 生态中处理 XML 往往是一个被忽视但至关重要的环节。无论是处理遗留的 SOAP 接口、解析 RSS 订阅源,还是生成 Sitemap,开发者都需要在多个库之间做出选择。@xmldom/xmldom、fast-xml-parser、libxmljs、xml2js 和 xmlbuilder 代表了五种不同的设计哲学。本文将从架构师的角度,深入对比它们的核心机制、适用场景及潜在陷阱。

🗂️ 核心解析机制:DOM 树 vs JS 对象 vs 原生绑定

不同的库对 XML 的抽象层次不同,这直接决定了你的代码写法。

@xmldom/xmldom 实现了标准的 W3C DOM 接口。

  • 你将获得一个类似浏览器中 document 的对象。
  • 适合需要遍历节点、使用 querySelector 或操作属性的场景。
// @xmldom/xmldom: 标准 DOM API
const { DOMParser } = require('@xmldom/xmldom');
const doc = new DOMParser().parseFromString('<root><item id="1">Text</item></root>');
const item = doc.getElementById('1'); // 使用熟悉的 DOM 方法

fast-xml-parser 将 XML 直接映射为 JavaScript 对象。

  • 解析结果是一个普通的 JSON 结构,易于访问。
  • 配置灵活,可处理属性与文本的混合。
// fast-xml-parser: 映射为 JS 对象
const { XMLParser } = require("fast-xml-parser");
const parser = new XMLParser();
const result = parser.parse('<root><item id="1">Text</item></root>');
console.log(result.root.item); // 直接访问对象属性

xml2js 同样转换为 JS 对象,但结构更详细。

  • 默认将属性放在 $ 键下,文本放在 _ 键下。
  • 适合需要保留完整 XML 结构信息的场景。
// xml2js: 详细对象映射
const xml2js = require('xml2js');
const parser = new xml2js.Parser();
parser.parseString('<root><item id="1">Text</item></root>', (err, result) => {
  console.log(result.root.item[0].$); // 访问属性
});

libxmljs 绑定到 C++ 的 libxml2 库。

  • 提供自己的 DOM 实现,支持 XPath 查询。
  • 性能最高,但接口与其他纯 JS 库不同。
// libxmljs: 原生 DOM 与 XPath
const libxmljs = require("libxmljs");
const doc = libxmljs.parseXmlString('<root><item id="1">Text</item></root>');
const node = doc.get('//item[@id="1"]'); // 使用 XPath 查询

xmlbuilder 专注于生成,而非解析。

  • 它不解析输入,而是通过链式调用构建输出。
  • 适合创建复杂的 XML 文档结构。
// xmlbuilder: 链式构建
const builder = require('xmlbuilder');
const xml = builder.create('root')
  .ele('item', { id: '1' }).txt('Text')
  .end({ pretty: true });

⚡ 性能与验证能力:纯 JS vs 原生

在处理大量数据时,性能差异会变得明显。同时,验证(Validation)是确保数据完整性的关键。

fast-xml-parser 在纯 JS 库中性能领先。

  • 它支持在解析时进行简单的验证。
  • 无需编译,启动速度快。
// fast-xml-parser: 高性能解析与验证
const { XMLParser, XMLValidator } = require("fast-xml-parser");
const isValid = XMLValidator.validate(xmlString); 
if(isValid === true) { /* 解析 */ }

libxmljs 提供工业级的验证能力。

  • 支持 XSD 和 RelaxNG 验证。
  • 适合对数据规范性要求极高的企业级应用。
// libxmljs: XSD 验证
const xsd = libxmljs.parseXmlString(xsdSchema);
const is_valid = doc.validate(xsd); // 返回布尔值或错误数组

@xmldom/xmldom 和 xml2js 性能适中。

  • 它们主要关注兼容性而非极致速度。
  • 验证通常需要额外的库或手动检查。
// xml2js: 基础解析,无内置 XSD 验证
const parser = new xml2js.Parser({ explicitCharkey: true });
// 验证需手动实现或配合其他库

xmlbuilder 不涉及解析性能。

  • 它的重点在于生成字符串的效率。
  • 内存占用低,适合流式生成。
// xmlbuilder: 高效生成
const root = builder.create('root');
// 逐步添加节点,最后一次性输出
const str = root.end();

🌍 运行环境兼容性:浏览器 vs Node.js

这是架构决策中最容易踩坑的地方。并非所有库都能在所有环境中运行。

@xmldom/xmldom、fast-xml-parser 和 xml2js 是纯 JavaScript 实现。

  • 它们可以在浏览器、Node.js、Deno 或 Web Workers 中运行。
  • 打包时无需特殊配置。
// 通用示例 (fast-xml-parser)
// 在 Webpack/Vite 中可直接使用,无原生依赖
import { XMLParser } from "fast-xml-parser";

libxmljs 依赖原生 C++ 扩展。

  • 仅限 Node.js 环境。
  • 无法在浏览器中使用,且在 Serverless 环境(如 Vercel、Cloudflare Workers)中可能因无法编译而失败。
// libxmljs: Node.js 专用
// 在浏览器中运行会报错:Module did not self-register
require("libxmljs"); 

xmlbuilder 是纯 JavaScript。

  • 全平台兼容。
  • 常用于前端生成需要提交给后端的 XML 负载。
// xmlbuilder: 全平台兼容
// 可在前端直接生成 SOAP 请求体
const xml = builder.create('Envelope').end();

🛠️ 生成与序列化:谁更擅长写 XML?

解析很重要,但生成 XML 同样常见。不同库的“写”体验差异巨大。

xmlbuilder 是专门为此设计的。

  • API 直观,支持链式调用。
  • 自动处理转义和格式。
// xmlbuilder: 专为生成设计
builder.create('book').att('id', '123').ele('title').txt('JS Guide').end();

fast-xml-parser 也包含构建器。

  • 适合解析和生成使用同一个库的场景。
  • API 基于对象转换。
// fast-xml-parser: 对象转 XML
const { XMLBuilder } = require("fast-xml-parser");
const builder = new XMLBuilder();
const output = builder.build({ root: { item: "Text" } });

xml2js 提供 Builder 类。

  • 与 Parser 配置对应。
  • 略显繁琐,但功能完整。
// xml2js: 对象转 XML
const builder = new xml2js.Builder();
const xml = builder.buildObject({ root: { item: "Text" } });

@xmldom/xmldom 使用 XMLSerializer。

  • 符合 W3C 标准。
  • 适合操作完 DOM 树后导出字符串。
// @xmldom/xmldom: DOM 转字符串
const { XMLSerializer } = require('@xmldom/xmldom');
const str = new XMLSerializer().serializeToString(doc);

libxmljs 提供 toString 方法。

  • 快速序列化原生节点。
  • 支持格式化选项。
// libxmljs: 原生节点序列化
const xmlString = doc.toString(true); // true for pretty print

📊 总结对比表

特性@xmldom/xmldomfast-xml-parserlibxmljsxml2jsxmlbuilder
核心类型W3C DOMJS 对象原生 DOMJS 对象构建器
运行环境全平台全平台仅 Node.js全平台全平台
解析性能中等极高极高中等N/A
XPath 支持有限无完整无无
主要用途DOM 操作快速数据转换高级验证/查询传统转换生成 XML
安装依赖无无需编译 (node-gyp)无无

💡 架构师建议

在选择 XML 库时,不要只看功能列表,要看它如何融入你的技术栈。

  1. 首选现代纯 JS 方案:对于大多数前端和 Node.js 项目,fast-xml-parser 是最佳平衡点。它速度快、无需编译、同时支持解析和生成。除非你有特殊理由,否则新项目应优先考虑它。

  2. 避免原生依赖陷阱:除非你明确需要 XPath 或 XSD 验证,否则不要在生产环境的前端项目或 Serverless 函数中使用 libxmljs。编译问题会显著增加部署复杂度和失败风险。

  3. DOM 操作的唯一选择:如果你需要像操作 HTML 一样操作 XML(例如使用 querySelectorAll),@xmldom/xmldom 是唯一标准的纯 JS 选择。它是 xmldom 的维护版本,修复了安全漏洞。

  4. 生成专用:如果你的主要任务是构建复杂的 XML 文档(如配置文件生成),xmlbuilder 的链式 API 会让代码更整洁。虽然 fast-xml-parser 也能做,但 xmlbuilder 的语义更清晰。

  5. 遗留系统维护:如果你正在维护旧代码,xml2js 依然可靠。但在重构时,建议逐步迁移到 fast-xml-parser 以获得更好的性能和更少的依赖问题。

最终结论:XML 处理在 JavaScript 中已不再是难题。fast-xml-parser 凭借其性能和易用性成为了新的默认选择,而 @xmldom/xmldom 则稳固地占据了标准 DOM 操作的生态位。理解它们的边界,能让你的架构更加稳健。

如何选择: @xmldom/xmldom vs fast-xml-parser vs libxmljs vs xml2js vs xmlbuilder

  • @xmldom/xmldom:

    如果你需要在 Node.js 或浏览器中使用标准的 W3C DOM API(如 getElementById)来处理 XML,选择 @xmldom/xmldom。它是 xmldom 的活跃维护分支,适合需要与现有 DOM 代码兼容的项目,纯 JavaScript 实现使其具有良好的跨平台兼容性。

  • fast-xml-parser:

    如果你追求极致的解析性能且希望直接将 XML 映射为简单的 JavaScript 对象,选择 fast-xml-parser。它支持验证和构建功能,纯 JS 实现无需编译,非常适合高并发的后端服务或需要快速处理大量 XML 数据的前端应用。

  • libxmljs:

    如果你仅在 Node.js 环境下开发,且需要强大的 XPath 查询、XSD 验证或处理超大文件,选择 libxmljs。它基于 libxml2 原生库,性能强劲,但需要编译环境,无法在浏览器或无编译环境的 Serverless 平台中运行。

  • xml2js:

    如果你维护旧项目或偏好传统的回调/Promise 风格,且不需要极致的性能,选择 xml2js。它是一个成熟稳定的库,专注于 XML 与 JS 对象的双向转换,社区资源丰富,但在新项目中通常建议优先考虑 fast-xml-parser。

  • xmlbuilder:

    如果你主要需求是生成 XML 而不是解析,选择 xmlbuilder。它提供流畅的链式调用 API 来构建 XML 结构,代码可读性高,常与 xml2js 搭配使用,适合生成配置文件、SOAP 请求或 Sitemap 等场景。

@xmldom/xmldom的README

@xmldom/xmldom

Since version 0.7.0 this package is published to npm as @xmldom/xmldom and no longer as xmldom, because we are no longer able to publish xmldom.
For better readability in the docs, we will continue to talk about this library as "xmldom".

license(MIT) no dependencies codecov install size

OpenSSF Best Practices OpenSSF Scorecard Socket Badge

npm:latest npm:next npm:lts

bug issues help-wanted issues

xmldom is a javascript ponyfill to provide the following APIs that are present in modern browsers to other runtimes:

  • convert an XML string into a DOM tree
    new DOMParser().parseFromString(xml, mimeType) => Document
    
  • create, access and modify a DOM tree
    new DOMImplementation().createDocument(...) => Document
    
  • serialize a DOM tree back into an XML string
    new XMLSerializer().serializeToString(node) => string
    

The target runtimes xmldom supports are currently Node >= v14.6 (and very likely any other ES5 compatible runtime).

When deciding how to fix bugs or implement features, xmldom tries to stay as close as possible to the various related specifications/standards.
As indicated by the version starting with 0., this implementation is not feature complete and some implemented features differ from what the specifications describe.
Issues and PRs for such differences are always welcome, even when they only provide a failing test case.

This project was forked from it's original source in 2019, more details about that transition can be found in the CHANGELOG.

Usage

Install:

npm install @xmldom/xmldom

Example:

In NodeJS

const { DOMParser, XMLSerializer } = require('@xmldom/xmldom')

const source = `<xml xmlns="a">
	<child>test</child>
	<child/>
</xml>`

const doc = new DOMParser().parseFromString(source, 'text/xml')

const serialized = new XMLSerializer().serializeToString(doc)

Note: in Typescript and ES6 (see #316) you can use the import approach, as follows:

import { DOMParser } from '@xmldom/xmldom'

API Reference

  • DOMParser:

    parseFromString(xmlsource, mimeType)
    
    • options extension by xmldom (not DOM standard!!)
    // the options argument can be used to modify behavior
    // for more details check the documentation on the code or type definition  
    new DOMParser(options)
    
  • XMLSerializer

    serializeToString(node)
    

DOM level2 method and attribute:

All DOM node types listed below are exported for use with instanceof (e.g. node instanceof Text). They cannot be constructed directly — use the Document factory methods (createTextNode, createComment, createDocumentFragment, …) instead.

  • Node

    readonly class properties (aka NodeType),
    these can be accessed from any Node instance node:
    if (node.nodeType === node.ELEMENT_NODE) {...

    1. ELEMENT_NODE (1)
    2. ATTRIBUTE_NODE (2)
    3. TEXT_NODE (3)
    4. CDATA_SECTION_NODE (4)
    5. ENTITY_REFERENCE_NODE (5)
    6. ENTITY_NODE (6)
    7. PROCESSING_INSTRUCTION_NODE (7)
    8. COMMENT_NODE (8)
    9. DOCUMENT_NODE (9)
    10. DOCUMENT_TYPE_NODE (10)
    11. DOCUMENT_FRAGMENT_NODE (11)
    12. NOTATION_NODE (12)

    attribute:

    • nodeValue | prefix | textContent

    readonly attribute:

    • nodeName | nodeType | parentNode | parentElement | childNodes | firstChild | lastChild | previousSibling | nextSibling | attributes | ownerDocument | namespaceURI | localName | isConnected | baseURI

    method:

    • insertBefore(newChild, refChild)
    • replaceChild(newChild, oldChild)
    • removeChild(oldChild)
    • appendChild(newChild)
    • hasChildNodes()
    • cloneNode(deep)
    • normalize()
    • contains(otherNode)
    • getRootNode()
    • isEqualNode(otherNode)
    • isSameNode(otherNode)
    • isSupported(feature, version)
    • hasAttributes()
  • DOMException

    extends the Error type thrown as part of DOM API.

    readonly class properties:

    • INDEX_SIZE_ERR (1)
    • DOMSTRING_SIZE_ERR (2)
    • HIERARCHY_REQUEST_ERR (3)
    • WRONG_DOCUMENT_ERR (4)
    • INVALID_CHARACTER_ERR (5)
    • NO_DATA_ALLOWED_ERR (6)
    • NO_MODIFICATION_ALLOWED_ERR (7)
    • NOT_FOUND_ERR (8)
    • NOT_SUPPORTED_ERR (9)
    • INUSE_ATTRIBUTE_ERR (10)
    • INVALID_STATE_ERR (11)
    • SYNTAX_ERR (12)
    • INVALID_MODIFICATION_ERR (13)
    • NAMESPACE_ERR (14)
    • INVALID_ACCESS_ERR (15)

    attributes:

    • code with a value matching one of the above constants.
  • DOMImplementation

    method:

    • hasFeature(feature, version) (deprecated)
    • createDocumentType(qualifiedName, publicId, systemId)
    • createDocument(namespaceURI, qualifiedName, doctype)
  • Document : Node

    readonly attribute:

    • doctype | implementation | documentElement

    method:

    • createElement(tagName)
    • createDocumentFragment()
    • createTextNode(data)
    • createComment(data)
    • createCDATASection(data)
    • createProcessingInstruction(target, data)
    • createAttribute(name)
    • createEntityReference(name)
    • getElementsByTagName(tagname)
    • importNode(importedNode, deep)
    • createElementNS(namespaceURI, qualifiedName)
    • createAttributeNS(namespaceURI, qualifiedName)
    • getElementsByTagNameNS(namespaceURI, localName)
    • getElementById(elementId)
  • DocumentFragment : Node

  • Element : Node

    readonly attribute:

    • tagName

    method:

    • getAttribute(name)
    • setAttribute(name, value)
    • removeAttribute(name)
    • getAttributeNode(name)
    • setAttributeNode(newAttr)
    • removeAttributeNode(oldAttr)
    • getElementsByTagName(name)
    • getAttributeNS(namespaceURI, localName)
    • setAttributeNS(namespaceURI, qualifiedName, value)
    • removeAttributeNS(namespaceURI, localName)
    • getAttributeNodeNS(namespaceURI, localName)
    • setAttributeNodeNS(newAttr)
    • getElementsByTagNameNS(namespaceURI, localName)
    • hasAttribute(name)
    • hasAttributeNS(namespaceURI, localName)
  • Attr : Node

    attribute:

    • value

    readonly attribute:

    • name | specified | ownerElement
  • NodeList

    readonly attribute:

    • length

    method:

    • item(index)
  • NamedNodeMap

    readonly attribute:

    • length

    method:

    • getNamedItem(name)
    • setNamedItem(arg)
    • removeNamedItem(name)
    • item(index)
    • getNamedItemNS(namespaceURI, localName)
    • setNamedItemNS(arg)
    • removeNamedItemNS(namespaceURI, localName)
  • CharacterData : Node

    method:

    • substringData(offset, count)
    • appendData(arg)
    • insertData(offset, arg)
    • deleteData(offset, count)
    • replaceData(offset, count, arg)
  • Text : CharacterData

    method:

    • splitText(offset)
  • CDATASection

  • Comment : CharacterData

  • DocumentType

    readonly attribute:

    • name | entities | notations | publicId | systemId | internalSubset
  • Notation : Node

    readonly attribute:

    • publicId | systemId
  • Entity : Node

    readonly attribute:

    • publicId | systemId | notationName
  • EntityReference : Node

  • ProcessingInstruction : Node

    attribute:

    • data readonly attribute:
    • target

DOM level 3 support:

  • Node

    attribute:

    • textContent

    method:

    • isDefaultNamespace(namespaceURI)
    • lookupNamespaceURI(prefix)

DOM Living Standard support:

  • ParentNode mixin (on Document, DocumentFragment, Element)

    readonly attribute:

    • children

DOM extension by xmldom

  • [Node] Source position extension;

    attribute:

    • lineNumber //number starting from 1
    • columnNumber //number starting from 1

Specs

The implementation is based on several specifications:

Overview of related specifications and their relations

DOM Parsing and Serialization

From the W3C DOM Parsing and Serialization (WD 2016) xmldom provides an implementation for the interfaces:

  • DOMParser
  • XMLSerializer

Note that there are some known deviations between this implementation and the W3 specifications.

Note: The latest version of this spec has the status "Editors Draft", since it is under active development. One major change is that the definition of the DOMParser interface has been moved to the HTML spec

DOM

The original author claims that xmldom implements [DOM Level 2] in a "fully compatible" way and some parts of [DOM Level 3], but there are not enough tests to prove this. Both Specifications are now superseded by the [DOM Level 4 aka Living standard] wich has a much broader scope than xmldom. In the past, there have been multiple (even breaking) changes to align xmldom with the living standard, so if you find a difference that is not documented, any contribution to resolve the difference is very welcome (even just reporting it as an issue).

xmldom implements the following interfaces:

  • Attr
  • CDATASection
  • CharacterData
  • Comment
  • Document
  • DocumentFragment
  • DocumentType
  • DOMException
  • DOMImplementation
  • Element
  • Entity
  • EntityReference
  • LiveNodeList
  • NamedNodeMap
  • Node
  • NodeList
  • Notation
  • ProcessingInstruction
  • Text

more details are available in the (incomplete) API Reference section.

HTML

xmldom does not have any goal of supporting the full spec, but it has some capability to parse, report and serialize things differently when it is told to parse HTML (by passing the HTML namespace).

SAX, XML, XMLNS

xmldom has an own SAX parser implementation to do the actual parsing, which implements some interfaces in alignment with the Java interfaces SAX defines:

  • XMLReader
  • DOMHandler

There is an idea/proposal to make it possible to replace it with something else in https://github.com/xmldom/xmldom/issues/55