-
Notifications
You must be signed in to change notification settings - Fork 1
Performance
All benchmarks run on Node.js, single thread (v0.9.1).
| Operation | modern-xlsx | SheetJS CE | Factor |
|---|---|---|---|
| Read 100K rows | 472 ms | 1,901 ms | 4.0x faster |
| Read 10K rows | 69 ms | 170 ms | 2.5x faster |
Write 100K (batch aoaToSheet) |
232 ms | 1,950 ms | 8.4x faster |
Write 50K (batch aoaToSheet) |
49 ms | 80 ms | 1.6x faster |
| Write 10K (cell-by-cell) | 175 ms | 125 ms | 0.7x |
| sheetToCsv (10K) | 37 ms | 31 ms | ~1.0x |
| sheetToJson (10K) | 36 ms | 22 ms | ~0.6x |
Summary: modern-xlsx is 4-8x faster for bulk read/write — its primary use case. SheetJS is faster for cell-by-cell writes and small utility conversions (sheetToJson, sheetToCsv). For large workbooks (10K+ rows), the WASM-accelerated Rust core delivers significant throughput gains.
The heavy lifting — ZIP decompression, XML parsing, shared string table lookup, and style resolution — runs in compiled WASM at near-native speed. The WASM sandbox also provides memory safety guarantees.
Data crosses the WASM boundary as a JSON string (serialized with serde_json in Rust, parsed with JSON.parse in JS). This is 8-13x faster than serde_wasm_bindgen for large workbooks because:
- JSON serialization in Rust is heavily optimized (itoa, ryu)
-
JSON.parseis one of the fastest built-in JS operations - Avoids thousands of individual WASM boundary crossings
The Rust core uses quick-xml in SAX (streaming) mode rather than building a DOM tree. This keeps memory usage proportional to the current element being processed, not the entire document.
When writing, the shared string table is built and indices are remapped inline during XML generation — avoiding a full worksheet clone that earlier versions required.
Use aoaToSheet / jsonToSheet instead of setting cells one by one:
// Fast — batch API
const ws = aoaToSheet(data);
// Slower — individual cell access
for (const [r, row] of data.entries()) {
for (const [c, val] of row.entries()) {
ws.cell(encodeCellRef(r, c)).value = val;
}
}Use sheetRows to limit how many rows are parsed:
const first100 = sheetToJson(ws, { sheetRows: 100 });Call initWasm() once at startup, not before every operation:
// Good
await initWasm();
// ...later, many operations...
// Bad — redundant (safe but wasteful)
await initWasm();
const wb1 = await readFile('a.xlsx');
await initWasm(); // unnecessary
const wb2 = await readFile('b.xlsx');| Component | Size |
|---|---|
| WASM binary | ~939 KB |
| JS wrapper | ~55 KB |
| Total | ~994 KB |
The WASM binary is loaded asynchronously and can be cached by the browser/runtime. The JS code is tree-shakable — unused utilities are eliminated by bundlers.
The v0.9.1 release focused on eliminating unnecessary allocations in hot paths:
All f64-to-string conversions in worksheet JSON serialization and chart value output now use the ryu crate, which provides 2-6x faster formatting than format!("{}", f) by using the Ryu algorithm (Ulf Adams, PLDI 2018).
The custom JSON string escaper was rewritten to batch-copy safe byte spans via memcpy instead of iterating character-by-character. This significantly reduces overhead when serializing large string values (shared strings, cell formulas).
Relationship fields now use Cow<'static, str>, allowing well-known OOXML namespace URIs and relationship types to be borrowed as static string references with zero heap allocation.
A new make_rid() helper uses itoa::Buffer for stack-based integer formatting, eliminating 21 format!("rId{}", n) heap allocations across the writer pipeline.
modern-xlsx v1.0.0
Getting Started
Guides
- Charts & Visualizations
- Formula Engine
- Table Layout Engine
- Tables & Print Layout
- Encryption
- Feature Comparison
Reference
Migration
Project