11 điểm bởi click 2025-09-22 | 1 bình luận | Chia sẻ qua WhatsApp

Vì ở chỗ làm tôi đang làm việc với một dịch vụ legacy viết bằng Java và giao tiếp dựa trên XML, nên khi muốn xây mới một web service dựa trên JS nhưng vẫn dùng dịch vụ legacy đó làm backend, tôi không tìm được trình phân tích XML nào thật sự vừa ý nên đã tự làm luôn.

Nó phân tích XML theo kiểu pull dựa trên StAX và cung cấp cả implementation bất đồng bộ, nên có thể phân tích cả các tệp XML dung lượng lớn theo kiểu stream chỉ với khoảng 10MB bộ nhớ.
Theo chuẩn ecmascript, độ dài tối đa của string là 2^53 - 1, nên với các XML lớn hơn 1GB thì gần như chỉ còn cách dùng trình phân tích SAX; tôi nghĩ thư viện này sẽ là một phương án thay thế tốt.

Vì ở công ty tôi chủ yếu dùng Java và đây cũng là lần đầu tôi thử làm thư viện cho hệ Node, nên nếu có điểm nào còn thiếu sót thì mong mọi người góp ý, tôi sẽ cố gắng phản ánh tối đa.

Lịch sử

Ban đầu tôi từng cân nhắc bind thư viện woodstox của Java sang wasm để dùng,
nhưng lúc đó wasm vẫn chưa có triển khai gc, nên tôi thấy việc biên dịch Java sang wasm vẫn còn quá sớm và đã dừng lại.
Lần thứ hai, tôi thử bind quick-xml của Rust sang wasm, nhưng chi phí chuyển stream vào wasm để xử lý quá lớn, khiến chênh lệch hiệu năng so với các trình phân tích XML JavaScript hiện có quá tệ nên tôi bỏ.
Cuối cùng, tôi quyết định viết hoàn toàn bằng TypeScript thuần, đồng thời nhờ sự hỗ trợ của nhiều AI để áp dụng thêm nhiều tối ưu hóa nhắm tới engine V8.

🚀 Tính năng chính

Phân tích hoàn toàn bất đồng bộ dựa trên stream

  • Tệp XML dung lượng lớn (hàng trăm MB~GB) được xử lý hiệu quả về bộ nhớ
  • Phân tích theo thời gian thực dựa trên ReadableStream mà không chặn main thread
  • Xử lý dữ liệu theo kiểu pull, chỉ lấy đúng lượng cần thiết
// Ngay cả tệp 970MB cũng có thể xử lý với dưới 10MB bộ nhớ  
const parser = new StaxXmlParser(largeXmlStream);  
for await (const event of parser) {  
  // Xử lý event theo kiểu streaming  
}  

Phân tích dựa trên event theo phong cách StAX

Cung cấp mẫu pull parser quen thuộc với các lập trình viên Java, cho phép kiểm soát chi tiết cấu trúc XML.

import { StaxXmlParser, isStartElement, isCharacters } from 'stax-xml';  
  
for await (const event of parser) {  
  if (isStartElement(event)) {  
    console.log(`Phần tử: ${event.name}`, event.attributes);  
  } else if (isCharacters(event)) {  
    console.log(`Văn bản: ${event.value}`);  
  }  
}  

🛠️ 4 thành phần cốt lõi

1. StaxXmlParser (parser bất đồng bộ)

  • Chuyên cho tệp lớn: phân tích hiệu quả về bộ nhớ dựa trên stream
  • Xử lý thời gian thực: kết hợp với fetch API để phân tích XML từ xa theo thời gian thực
  • Type guard của TypeScript: đảm bảo an toàn kiểu ở runtime

2. StaxXmlParserSync (parser đồng bộ)

  • Tối ưu cho tệp nhỏ: phân tích nhanh chuỗi XML trong bộ nhớ
  • Phản hồi web API: xử lý ngay trong workflow đồng bộ

3. StaxXmlWriter (writer bất đồng bộ)

  • Tạo theo streaming: xuất XML trực tiếp ra WritableStream
  • Phản hồi thời gian thực: tạo phản hồi XML dung lượng lớn trên API server
  • Hiệu quả bộ nhớ: không lưu toàn bộ XML trong bộ nhớ

4. StaxXmlWriterSync (writer đồng bộ)

  • Tạo ngay lập tức: build chuỗi XML trong bộ nhớ
  • Tích hợp web server: tương thích hoàn hảo với Express, Hono, v.v.

📊 So sánh hiệu năng (benchmark)

Môi trường benchmark

  • 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

Phân tích tệp lớn 97MB:

  • stax-xml: 1.05s, bộ nhớ 8.89MB
  • fast-xml-parser: 4.41s, bộ nhớ 886.33MB
  • txml: 1.02s, bộ nhớ 897.50MB

🌐 Khả năng tương thích phổ quát

Chỉ sử dụng web standard API nên chạy được trên mọi JavaScript runtime:

  • Node.js (v18+)
  • Bun, Deno
  • Trình duyệt web
  • Edge Runtime (Vercel, Cloudflare Workers)

📦 Cài đặt và bắt đầu

npm install stax-xml  
import { StaxXmlParser, XmlEventType } from 'stax-xml';  
  
// Tạo stream từ chuỗi XML  
const stream = new ReadableStream({  
  start(controller) {  
    controller.enqueue(new TextEncoder().encode(xmlContent));  
    controller.close();  
  }  
});  
  
// Phân tích bất đồng bộ  
const parser = new StaxXmlParser(stream);  
for await (const event of parser) {  
  if (event.type === XmlEventType.START_ELEMENT) {  
    console.log(`Phần tử: ${event.name}`, event.attributes);  
  }  
}  

📄 Thông tin dự án


※ Lưu ý liên quan đến giấy phép: Thư viện này lấy cảm hứng từ khái niệm StAX được đề xuất trong JSR 173: Streaming API for XML, nhưng tôi chưa thể xác định rõ các điều khoản giấy phép của chính JSR. Nếu ai biết về điều khoản cấp phép của tên gọi StAX, tôi sẽ rất cảm kích nếu được tư vấn.

1 bình luận

 
honglu 2025-09-22

Mình rất thích vì có thể cảm nhận được sự chỉn chu và tâm huyết trong bài viết.

Mình đã đọc rất thích!