StAX-XML: JavaScript/TypeScript向け高性能ストリーミングXMLパーサー
(github.com/Clickin)職場ではJavaで作られたレガシーサービスとXMLベースで通信しているのですが、そのレガシーサービスをバックエンドに据えたまま、JSベースで新しいWebサービスを作ろうとしたところ、ちょうど気に入るXMLパーサーがなかったので自分で作ってしまいました。
StAXベースのpull方式でXMLをパースし、非同期実装を提供しているため、Streamベースで大容量XMLファイルも10MB程度のメモリだけでパースできます。
ECMAScript標準ではstringの最大長が 2^53 - 1 であるため、1GBを超えるXMLはSAXパーサーを使うしかありませんでしたが、このライブラリはよい代替になると思います。
普段の仕事では主にJavaを使っていて、Node界隈でライブラリを作るのは初めてなので、足りない部分があれば提案していただければ、できる限り反映していきたいと思います。
歴史
最初はJavaのwoodstoxライブラリをWASMにバインドして使うことも考えましたが、
当時はまだWASMにGC実装がなかったため、JavaをWASMにコンパイルするのはまだ時期尚早だと考えてやめました。
次にRustのquick-xmlをWASMにバインドして試してみましたが、streamをWASMに渡して処理するコストが大きすぎて、既存のJavaScript XMLパーサーとの性能差が大きく出てしまったため断念しました。
最終的には純粋なTypeScriptで書くことに決め、複数のAIの助けも借りながら、V8エンジンをターゲットにさまざまな最適化も適用しました。
🚀 主な特徴
完全非同期ストリームベースパース
- 大容量XMLファイル(数百MB〜GB)をメモリ効率よく処理
- ReadableStream ベースでメインスレッドをブロックせずリアルタイムにパース
- pull方式で必要な分だけデータを処理
// 970MBファイルも10MB未満のメモリで処理可能
const parser = new StaxXmlParser(largeXmlStream);
for await (const event of parser) {
// ストリーミングでイベントを処理
}
StAXスタイルのイベントベースパース
Java開発者になじみのあるプルパーサーパターンを提供し、XML構造を細かく制御できます。
import { StaxXmlParser, isStartElement, isCharacters } from 'stax-xml';
for await (const event of parser) {
if (isStartElement(event)) {
console.log(`要素: ${event.name}`, event.attributes);
} else if (isCharacters(event)) {
console.log(`テキスト: ${event.value}`);
}
}
🛠️ 4つの主要コンポーネント
1. StaxXmlParser(非同期パーサー)
- 大容量ファイル専用: ストリームベースでメモリ効率の高いパース
- リアルタイム処理: fetch APIと連携してリモートXMLをリアルタイムにパース
- TypeScript型ガード: ランタイムの型安全性を保証
2. StaxXmlParserSync(同期パーサー)
- 小規模ファイル向け最適化: インメモリのXML文字列を高速パース
- Web APIレスポンス: 同期ワークフローで即時処理
3. StaxXmlWriter(非同期ライター)
- ストリーミング生成: WritableStreamに直接XMLを出力
- リアルタイム応答: APIサーバーで大容量XMLレスポンスを生成
- メモリ効率が高い: XML全体をメモリに保持しない
4. StaxXmlWriterSync(同期ライター)
- 即時生成: メモリ上でXML文字列を構築
- Webサーバー統合: Express、Honoなどと完全連携
📊 性能比較(ベンチマーク)
ベンチマーク環境
- CPU: 13th Gen Intel(R) Core(TM) i5-13600K (~4.70-4.80 GHz)
- Runtime: Node.js 22.17.0 (x64-win32) with --expose-gc
- Tool: Mitata
97MB大容量ファイルのパース:
- stax-xml: 1.05s, メモリ 8.89MB
- fast-xml-parser: 4.41s, メモリ 886.33MB
- txml: 1.02s, メモリ 897.50MB
🌐 汎用互換性
Web標準APIのみを使用しているため、すべてのJavaScriptランタイムで動作:
- Node.js (v18+)
- Bun, Deno
- Webブラウザ
- Edge Runtime (Vercel, Cloudflare Workers)
📦 インストールと開始
npm install stax-xml
import { StaxXmlParser, XmlEventType } from 'stax-xml';
// XML文字列からストリームを生成
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(xmlContent));
controller.close();
}
});
// 非同期パース
const parser = new StaxXmlParser(stream);
for await (const event of parser) {
if (event.type === XmlEventType.START_ELEMENT) {
console.log(`要素: ${event.name}`, event.attributes);
}
}
📄 プロジェクト情報
- GitHub: https://github.com/clickin/stax-xml
- ドキュメント: https://clickin.github.io/stax-xml
- NPM: https://www.npmjs.com/package/stax-xml
- ライセンス: MIT
※ ライセンス関連の参考事項: このライブラリは JSR 173: Streaming API for XML で提案されたStAXの概念に着想を得ていますが、JSR自体のライセンス条件を明確に把握できていませんでした。 StAX 名称のライセンス条項をご存じの方がいれば、アドバイスをいただけるとありがたいです。
1件のコメント
記事から丁寧さと真心が感じられてよかったです。
楽しく読ませていただきました!