@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 字符串。
在 JavaScript 生态中处理 XML 往往是一个被忽视但至关重要的环节。无论是处理遗留的 SOAP 接口、解析 RSS 订阅源,还是生成 Sitemap,开发者都需要在多个库之间做出选择。@xmldom/xmldom、fast-xml-parser、libxmljs、xml2js 和 xmlbuilder 代表了五种不同的设计哲学。本文将从架构师的角度,深入对比它们的核心机制、适用场景及潜在陷阱。
不同的库对 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 对象。
// 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 对象,但结构更详细。
$ 键下,文本放在 _ 键下。// 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 库。
// 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 专注于生成,而非解析。
// xmlbuilder: 链式构建
const builder = require('xmlbuilder');
const xml = builder.create('root')
.ele('item', { id: '1' }).txt('Text')
.end({ pretty: true });
在处理大量数据时,性能差异会变得明显。同时,验证(Validation)是确保数据完整性的关键。
fast-xml-parser 在纯 JS 库中性能领先。
// fast-xml-parser: 高性能解析与验证
const { XMLParser, XMLValidator } = require("fast-xml-parser");
const isValid = XMLValidator.validate(xmlString);
if(isValid === true) { /* 解析 */ }
libxmljs 提供工业级的验证能力。
// 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();
这是架构决策中最容易踩坑的地方。并非所有库都能在所有环境中运行。
@xmldom/xmldom、fast-xml-parser 和 xml2js 是纯 JavaScript 实现。
// 通用示例 (fast-xml-parser)
// 在 Webpack/Vite 中可直接使用,无原生依赖
import { XMLParser } from "fast-xml-parser";
libxmljs 依赖原生 C++ 扩展。
// libxmljs: Node.js 专用
// 在浏览器中运行会报错:Module did not self-register
require("libxmljs");
xmlbuilder 是纯 JavaScript。
// xmlbuilder: 全平台兼容
// 可在前端直接生成 SOAP 请求体
const xml = builder.create('Envelope').end();
解析很重要,但生成 XML 同样常见。不同库的“写”体验差异巨大。
xmlbuilder 是专门为此设计的。
// xmlbuilder: 专为生成设计
builder.create('book').att('id', '123').ele('title').txt('JS Guide').end();
fast-xml-parser 也包含构建器。
// fast-xml-parser: 对象转 XML
const { XMLBuilder } = require("fast-xml-parser");
const builder = new XMLBuilder();
const output = builder.build({ root: { item: "Text" } });
xml2js 提供 Builder 类。
// xml2js: 对象转 XML
const builder = new xml2js.Builder();
const xml = builder.buildObject({ root: { item: "Text" } });
@xmldom/xmldom 使用 XMLSerializer。
// @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/xmldom | fast-xml-parser | libxmljs | xml2js | xmlbuilder |
|---|---|---|---|---|---|
| 核心类型 | W3C DOM | JS 对象 | 原生 DOM | JS 对象 | 构建器 |
| 运行环境 | 全平台 | 全平台 | 仅 Node.js | 全平台 | 全平台 |
| 解析性能 | 中等 | 极高 | 极高 | 中等 | N/A |
| XPath 支持 | 有限 | 无 | 完整 | 无 | 无 |
| 主要用途 | DOM 操作 | 快速数据转换 | 高级验证/查询 | 传统转换 | 生成 XML |
| 安装依赖 | 无 | 无 | 需编译 (node-gyp) | 无 | 无 |
在选择 XML 库时,不要只看功能列表,要看它如何融入你的技术栈。
首选现代纯 JS 方案:对于大多数前端和 Node.js 项目,fast-xml-parser 是最佳平衡点。它速度快、无需编译、同时支持解析和生成。除非你有特殊理由,否则新项目应优先考虑它。
避免原生依赖陷阱:除非你明确需要 XPath 或 XSD 验证,否则不要在生产环境的前端项目或 Serverless 函数中使用 libxmljs。编译问题会显著增加部署复杂度和失败风险。
DOM 操作的唯一选择:如果你需要像操作 HTML 一样操作 XML(例如使用 querySelectorAll),@xmldom/xmldom 是唯一标准的纯 JS 选择。它是 xmldom 的维护版本,修复了安全漏洞。
生成专用:如果你的主要任务是构建复杂的 XML 文档(如配置文件生成),xmlbuilder 的链式 API 会让代码更整洁。虽然 fast-xml-parser 也能做,但 xmlbuilder 的语义更清晰。
遗留系统维护:如果你正在维护旧代码,xml2js 依然可靠。但在重构时,建议逐步迁移到 fast-xml-parser 以获得更好的性能和更少的依赖问题。
最终结论:XML 处理在 JavaScript 中已不再是难题。fast-xml-parser 凭借其性能和易用性成为了新的默认选择,而 @xmldom/xmldom 则稳固地占据了标准 DOM 操作的生态位。理解它们的边界,能让你的架构更加稳健。
如果你需要在 Node.js 或浏览器中使用标准的 W3C DOM API(如 getElementById)来处理 XML,选择 @xmldom/xmldom。它是 xmldom 的活跃维护分支,适合需要与现有 DOM 代码兼容的项目,纯 JavaScript 实现使其具有良好的跨平台兼容性。
如果你追求极致的解析性能且希望直接将 XML 映射为简单的 JavaScript 对象,选择 fast-xml-parser。它支持验证和构建功能,纯 JS 实现无需编译,非常适合高并发的后端服务或需要快速处理大量 XML 数据的前端应用。
如果你仅在 Node.js 环境下开发,且需要强大的 XPath 查询、XSD 验证或处理超大文件,选择 libxmljs。它基于 libxml2 原生库,性能强劲,但需要编译环境,无法在浏览器或无编译环境的 Serverless 平台中运行。
如果你维护旧项目或偏好传统的回调/Promise 风格,且不需要极致的性能,选择 xml2js。它是一个成熟稳定的库,专注于 XML 与 JS 对象的双向转换,社区资源丰富,但在新项目中通常建议优先考虑 fast-xml-parser。
如果你主要需求是生成 XML 而不是解析,选择 xmlbuilder。它提供流畅的链式调用 API 来构建 XML 结构,代码可读性高,常与 xml2js 搭配使用,适合生成配置文件、SOAP 请求或 Sitemap 等场景。
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".
xmldom is a javascript ponyfill to provide the following APIs that are present in modern browsers to other runtimes:
new DOMParser().parseFromString(xml, mimeType) => Document
new DOMImplementation().createDocument(...) => Document
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.
npm install @xmldom/xmldom
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'
parseFromString(xmlsource, mimeType)
// the options argument can be used to modify behavior
// for more details check the documentation on the code or type definition
new DOMParser(options)
serializeToString(node)
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.
readonly class properties (aka NodeType),
these can be accessed from any Node instance node:
if (node.nodeType === node.ELEMENT_NODE) {...
ELEMENT_NODE (1)ATTRIBUTE_NODE (2)TEXT_NODE (3)CDATA_SECTION_NODE (4)ENTITY_REFERENCE_NODE (5)ENTITY_NODE (6)PROCESSING_INSTRUCTION_NODE (7)COMMENT_NODE (8)DOCUMENT_NODE (9)DOCUMENT_TYPE_NODE (10)DOCUMENT_FRAGMENT_NODE (11)NOTATION_NODE (12)attribute:
nodeValue | prefix | textContentreadonly attribute:
nodeName | nodeType | parentNode | parentElement | childNodes | firstChild | lastChild | previousSibling | nextSibling | attributes | ownerDocument | namespaceURI | localName | isConnected | baseURImethod:
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()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.method:
hasFeature(feature, version) (deprecated)createDocumentType(qualifiedName, publicId, systemId)createDocument(namespaceURI, qualifiedName, doctype)Document : Node
readonly attribute:
doctype | implementation | documentElementmethod:
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:
tagNamemethod:
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:
valuereadonly attribute:
name | specified | ownerElementreadonly attribute:
lengthmethod:
item(index)readonly attribute:
lengthmethod:
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)Comment : CharacterData
readonly attribute:
name | entities | notations | publicId | systemId | internalSubsetNotation : Node
readonly attribute:
publicId | systemIdEntity : Node
readonly attribute:
publicId | systemId | notationNameEntityReference : Node
ProcessingInstruction : Node
attribute:
data
readonly attribute:targetattribute:
textContentmethod:
isDefaultNamespace(namespaceURI)lookupNamespaceURI(prefix)ParentNode mixin (on Document, DocumentFragment, Element)
readonly attribute:
children[Node] Source position extension;
attribute:
lineNumber //number starting from 1columnNumber //number starting from 1The implementation is based on several specifications:
From the W3C DOM Parsing and Serialization (WD 2016) xmldom provides an implementation for the interfaces:
DOMParserXMLSerializerNote 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
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:
AttrCDATASectionCharacterDataCommentDocumentDocumentFragmentDocumentTypeDOMExceptionDOMImplementationElementEntityEntityReferenceLiveNodeListNamedNodeMapNodeNodeListNotationProcessingInstructionTextmore details are available in the (incomplete) API Reference section.
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).
xmldom has an own SAX parser implementation to do the actual parsing, which implements some interfaces in alignment with the Java interfaces SAX defines:
XMLReaderDOMHandlerThere is an idea/proposal to make it possible to replace it with something else in https://github.com/xmldom/xmldom/issues/55