11 Punkte von click 2025-09-22 | 1 Kommentare | Auf WhatsApp teilen

Da ich auf der Arbeit mit einem in Java geschriebenen Legacy-Service arbeite, der XML-basiert kommuniziert, und wir bei einem neuen Webservice den Legacy-Service als Backend beibehalten, aber auf JS-Basis neu entwickeln wollten, gab es keinen XML-Parser, der mir wirklich gefiel — also habe ich selbst einen gebaut.

Er parst XML mit einem StAX-basierten Pull-Ansatz und bietet eine asynchrone Implementierung, sodass sich auch große XML-Dateien auf Stream-Basis mit nur etwa 10 MB Speicher parsen lassen.
Da die maximale Länge von string im ECMAScript-Standard bei 2^53 - 1 liegt, blieb man bei XML-Dateien über 1 GB bisher praktisch auf SAX-Parser angewiesen — ich denke, diese Bibliothek kann dafür eine gute Alternative sein.

Da ich beruflich hauptsächlich Java nutze und dies mein erstes Mal ist, eine Bibliothek für das Node-Ökosystem zu entwickeln, freue ich mich über Vorschläge zu Schwachstellen und werde sie nach Möglichkeit einarbeiten.

Geschichte

Zuerst habe ich überlegt, die Java-Bibliothek woodstox per WASM-Binding zu verwenden,
aber damals gab es in WASM noch keine GC-Implementierung, daher hielt ich das Kompilieren von Java nach WASM noch für verfrüht und habe es gelassen.
Als Zweites habe ich versucht, Rusts quick-xml per WASM-Binding einzusetzen, aber die Kosten dafür, Streams an WASM zu übergeben und dort zu verarbeiten, waren zu hoch, sodass der Performance-Unterschied zu bestehenden JavaScript-XML-Parsern viel zu groß war — deshalb habe ich auch das verworfen.
Letztlich habe ich mich entschieden, alles in reinem TypeScript zu schreiben, und mit Unterstützung mehrerer AIs verschiedene Optimierungen speziell für die V8-Engine angewendet.

🚀 Hauptmerkmale

Vollständig asynchrones, streambasiertes Parsing

  • Große XML-Dateien (mehrere hundert MB bis GB) speichereffizient verarbeiten
  • Auf Basis von ReadableStream für Echtzeit-Parsing ohne Blockieren des Main Threads
  • Pull-Ansatz zur Verarbeitung nur der tatsächlich benötigten Daten
// Selbst eine 970MB-Datei lässt sich mit unter 10MB Speicher verarbeiten  
const parser = new StaxXmlParser(largeXmlStream);  
for await (const event of parser) {  
  // Ereignisse per Streaming verarbeiten  
}  

Ereignisbasiertes Parsing im StAX-Stil

Bietet Java-Entwicklern ein vertrautes Pull-Parser-Muster, mit dem sich die XML-Struktur fein granular steuern lässt.

import { StaxXmlParser, isStartElement, isCharacters } from 'stax-xml';  
  
for await (const event of parser) {  
  if (isStartElement(event)) {  
    console.log(`Element: ${event.name}`, event.attributes);  
  } else if (isCharacters(event)) {  
    console.log(`Text: ${event.value}`);  
  }  
}  

🛠️ Vier Kernkomponenten

1. StaxXmlParser (asynchroner Parser)

  • Für große Dateien: streambasiertes, speichereffizientes Parsing
  • Echtzeitverarbeitung: Remote-XML in Echtzeit über die Fetch API parsen
  • TypeScript-Type-Guards: garantierte Typensicherheit zur Laufzeit

2. StaxXmlParserSync (synchroner Parser)

  • Für kleine Dateien optimiert: schnelles Parsing von XML-Strings im Speicher
  • Web-API-Antworten: sofortige Verarbeitung in synchronen Workflows

3. StaxXmlWriter (asynchroner Writer)

  • Streaming-Erzeugung: XML direkt in einen WritableStream ausgeben
  • Echtzeitantworten: große XML-Antworten auf API-Servern erzeugen
  • Speichereffizient: speichert nicht das gesamte XML im Speicher

4. StaxXmlWriterSync (synchroner Writer)

  • Sofortige Erzeugung: XML-Strings im Speicher aufbauen
  • Webserver-Integration: perfekte Anbindung an Express, Hono usw.

📊 Leistungsvergleich (Benchmark)

Benchmark-Umgebung

  • 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

Parsing einer großen 97MB-Datei:

  • stax-xml: 1.05s, Speicher 8.89MB
  • fast-xml-parser: 4.41s, Speicher 886.33MB
  • txml: 1.02s, Speicher 897.50MB

🌐 Universelle Kompatibilität

Verwendet nur Web-Standard-APIs und läuft dadurch in allen JavaScript-Runtimes:

  • Node.js (v18+)
  • Bun, Deno
  • Webbrowser
  • Edge Runtime (Vercel, Cloudflare Workers)

📦 Installation und Einstieg

npm install stax-xml  
import { StaxXmlParser, XmlEventType } from 'stax-xml';  
  
// Stream aus einem XML-String erzeugen  
const stream = new ReadableStream({  
  start(controller) {  
    controller.enqueue(new TextEncoder().encode(xmlContent));  
    controller.close();  
  }  
});  
  
// Asynchron parsen  
const parser = new StaxXmlParser(stream);  
for await (const event of parser) {  
  if (event.type === XmlEventType.START_ELEMENT) {  
    console.log(`Element: ${event.name}`, event.attributes);  
  }  
}  

📄 Projektinformationen


※ Hinweis zur Lizenz: Diese Bibliothek ist von dem in JSR 173: Streaming API for XML vorgeschlagenen StAX-Konzept inspiriert, die Lizenzbedingungen des JSR selbst konnte ich jedoch nicht eindeutig nachvollziehen. Falls jemand die Lizenzklauseln zur Bezeichnung StAX kennt, wäre ich für Hinweise dankbar.

1 Kommentare

 
honglu 2025-09-22

Man spürt die Sorgfalt und Hingabe im Artikel, das fand ich sehr schön.

Ich habe ihn gerne gelesen!