Show GN: StAX-XML: JavaScript/TypeScript용 고성능 스트리밍 XML 파서
(github.com/Clickin)직장이 자바로 된 레거시 서비스+XML 기반으로 통신하는데 레거시 서비스를 백엔드로 두면서 js 기반으로 웹서비스를 새로 만드려다보니 딱 마음에 드는 xml 파서가 없어서 직접 만들어버렸습니다.
StAX 기반 pull 방식으로 xml을 파싱하고 비동기 구현체를 제공해 Stream 기반으로 대용량 xml 파일도 10MB 정도의 메모리만으로 파싱이 가능합니다.
ecmascript 표준에서 string의 최대 길이는 2^53 - 1이다보니 1GB가 넘는 xml들은 sax 파서를 사용할 수밖에 없었는데 이 라이브러리가 좋은 대안이 될 거라고 생각합니다.
직장에서 자바를 주로 쓰고 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 문자열 고속 파싱
- 웹 API 응답: 동기식 워크플로우에서 즉시 처리
3. StaxXmlWriter (비동기 라이터)
- 스트리밍 생성: WritableStream에 직접 XML 출력
- 실시간 응답: API 서버에서 대용량 XML 응답 생성
- 메모리 효율적: 전체 XML을 메모리에 저장하지 않음
4. StaxXmlWriterSync (동기 라이터)
- 즉시 생성: 메모리 내 XML 문자열 빌드
- 웹 서버 통합: 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
🌐 범용 호환성
웹 표준 API만 사용하여 모든 JavaScript 런타임에서 동작:
- Node.js (v18+)
- Bun, Deno
- 웹 브라우저
- 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 명칭의 라이센스 조항을 아시는 분은 조언을 주시면 감사하겠습니다.