11 ポイント 投稿者 click 2025-09-22 | 1件のコメント | WhatsAppで共有

職場では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);  
  }  
}  

📄 プロジェクト情報


※ ライセンス関連の参考事項: このライブラリは JSR 173: Streaming API for XML で提案されたStAXの概念に着想を得ていますが、JSR自体のライセンス条件を明確に把握できていませんでした。 StAX 名称のライセンス条項をご存じの方がいれば、アドバイスをいただけるとありがたいです。

1件のコメント

 
honglu 2025-09-22

記事から丁寧さと真心が感じられてよかったです。

楽しく読ませていただきました!