11 คะแนน โดย click 2025-09-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

ที่ทำงานของผมใช้ระบบเลกาซีที่เขียนด้วย Java และสื่อสารกันบนพื้นฐาน XML พอจะสร้างเว็บเซอร์วิสใหม่ด้วย JavaScript โดยยังคงใช้ระบบเลกาซีเป็นแบ็กเอนด์ ก็หา XML parser ที่ถูกใจไม่ได้พอดี เลยลงมือทำขึ้นมาเองเลย

มันพาร์ส XML ด้วยแนวทาง pull แบบอิง StAX และมี implementation แบบ async ให้ จึงสามารถพาร์สไฟล์ XML ขนาดใหญ่บนพื้นฐาน Stream ได้โดยใช้หน่วยความจำเพียงราว 10MB เท่านั้น
ตามมาตรฐาน ecmascript ความยาวสูงสุดของ string คือ 2^53 - 1 ทำให้ XML ที่ใหญ่เกิน 1GB ก่อนหน้านี้แทบต้องใช้ sax parser อย่างเดียว แต่ผมคิดว่าไลบรารีนี้จะเป็นอีกทางเลือกที่ดี

เนื่องจากที่ทำงานผมใช้ Java เป็นหลัก และนี่ก็เป็นครั้งแรกที่ได้ทำไลบรารีในฝั่ง Node หากมีจุดที่ยังขาดตกบกพร่องก็ยินดีรับข้อเสนอแนะและจะพยายามนำไปปรับใช้ให้มากที่สุด

ประวัติ

ตอนแรกเคยคิดว่าจะ bind ไลบรารี woodstox ของ Java มาใช้ผ่าน wasm
แต่ตอนนั้น wasm ยังไม่มีการทำ gc จึงคิดว่าการคอมไพล์ Java ไปเป็น wasm ยังเร็วเกินไปสำหรับช่วงเวลานั้นเลยเลิกไป
จากนั้นลอง bind quick-xml ของ Rust ผ่าน wasm ดูอีกครั้ง แต่ต้นทุนในการส่ง stream เข้าไปให้ wasm จัดการสูงเกินไป จนประสิทธิภาพต่างจากพาร์เซอร์ XML ของ JavaScript เดิมมากเกินไป เลยตัดสินใจยกเลิกเช่นกัน
สุดท้ายจึงตัดสินใจเขียนด้วย TypeScript ล้วน และอาศัยความช่วยเหลือจาก AI หลายตัว พร้อมทั้งใส่การปรับแต่งหลายอย่างโดยเล็งเป้าหมายไปที่เอนจิน V8

🚀 จุดเด่นหลัก

การพาร์สแบบสตรีมที่เป็น async อย่างสมบูรณ์

  • ไฟล์ XML ขนาดใหญ่ (หลายร้อย MB~GB) ประมวลผลได้อย่างคุ้มค่าหน่วยความจำ
  • อิง ReadableStream จึงพาร์สได้แบบเรียลไทม์โดยไม่บล็อก main thread
  • ใช้แนวทาง pull เพื่อประมวลผลข้อมูลเท่าที่ต้องการ
// ไฟล์ 970MB ก็ประมวลผลได้ด้วยหน่วยความจำน้อยกว่า 10MB  
const parser = new StaxXmlParser(largeXmlStream);  
for await (const event of parser) {  
  // จัดการอีเวนต์แบบสตรีมมิง  
}  

การพาร์สแบบ event-based สไตล์ StAX

มอบ รูปแบบ pull parser ที่คุ้นเคยสำหรับนักพัฒนา 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 (พาร์เซอร์แบบ async)

  • สำหรับไฟล์ขนาดใหญ่โดยเฉพาะ: พาร์สแบบอิง stream ที่ประหยัดหน่วยความจำ
  • ประมวลผลแบบเรียลไทม์: ทำงานร่วมกับ fetch API เพื่อพาร์ส XML ระยะไกลแบบเรียลไทม์
  • TypeScript type guard: รับประกันความปลอดภัยของชนิดข้อมูลขณะรันไทม์

2. StaxXmlParserSync (พาร์เซอร์แบบ sync)

  • เหมาะกับไฟล์ขนาดเล็ก: พาร์สสตริง XML ในหน่วยความจำได้อย่างรวดเร็ว
  • การตอบกลับจาก Web API: จัดการได้ทันทีในเวิร์กโฟลว์แบบซิงโครนัส

3. StaxXmlWriter (ไรเตอร์แบบ async)

  • สร้างแบบสตรีมมิง: เขียน XML ออกไปยัง WritableStream ได้โดยตรง
  • ตอบสนองแบบเรียลไทม์: สร้าง XML response ขนาดใหญ่จาก API server
  • ประหยัดหน่วยความจำ: ไม่ต้องเก็บ XML ทั้งหมดไว้ในหน่วยความจำ

4. StaxXmlWriterSync (ไรเตอร์แบบ sync)

  • สร้างได้ทันที: ประกอบสตริง 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

🌐 ความเข้ากันได้แบบครอบจักรวาล

ใช้เฉพาะ Web Standard API จึงทำงานได้บนทุก JavaScript runtime:

  • 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();  
  }  
});  
  
// พาร์สแบบ async  
const parser = new StaxXmlParser(stream);  
for await (const event of parser) {  
  if (event.type === XmlEventType.START_ELEMENT) {  
    console.log(`요소: ${event.name}`, event.attributes);  
  }  
}  

📄 ข้อมูลโปรเจกต์


※ หมายเหตุเกี่ยวกับไลเซนส์: ไลบรารีนี้ได้รับแรงบันดาลใจจากแนวคิดของ StAX ที่เสนอไว้ใน JSR 173: Streaming API for XML แต่ยังไม่สามารถตรวจสอบเงื่อนไขไลเซนส์ของ JSR เองได้อย่างชัดเจน หากมีใครทราบข้อกำหนดไลเซนส์ของชื่อ StAX ก็ขอคำแนะนำด้วยจะขอบคุณมาก

1 ความคิดเห็น

 
honglu 2025-09-22

รู้สึกได้ถึงความตั้งใจและความใส่ใจในเอกสาร เลยอ่านได้อย่างเพลิดเพลินครับ

อ่านสนุกมากเลย!