-
Notifications
You must be signed in to change notification settings - Fork 1
Changelog
Complete formula tokenizer, parser, evaluator, and 54 built-in Excel functions — enabling modern-xlsx to compute cell values without Excel. 1142 TypeScript tests.
- Tokenizer — single-pass lexer producing typed tokens (24 token types)
- Parser — recursive descent with operator precedence (12 AST node types)
- Serializer — AST to formula string with precedence-aware parenthesization
-
Reference Resolver — resolves A1 refs and ranges to cell values via
EvalContext -
Reference Rewriter — adjusts refs on row/column insert/delete, generates
#REF!for deleted - Shared Formula Expansion — derives child formulas from master + row/col offset
- Evaluator — tree-walk interpreter with Excel-compatible type coercion and error propagation
String & Logical (20): IF, AND, OR, NOT, IFERROR, CONCATENATE, LEFT, RIGHT, MID, LEN, TRIM, UPPER, LOWER, TEXT, VALUE, EXACT, SUBSTITUTE, REPT, FIND, SEARCH
Math & Statistical (25): SUM, AVERAGE, MIN, MAX, COUNT, COUNTA, COUNTBLANK, ROUND, ROUNDUP, ROUNDDOWN, ABS, SQRT, MOD, INT, CEILING, FLOOR, POWER, LOG, LN, PI, RAND, SUMIF, COUNTIF, AVERAGEIF, SUMPRODUCT
Lookup (9): VLOOKUP, HLOOKUP, INDEX, MATCH, CHOOSE, ROW, COLUMN, ROWS, COLUMNS
import { evaluateFormula, createDefaultFunctions } from 'modern-xlsx'; const functions = createDefaultFunctions(); const result = evaluateFormula('SUM(A1:A3)*2', { currentSheet: 'Sheet1', functions, getCell: (sheet, col, row) => { /* return cell value */ }, });
Full ECMA-376 encryption support — read and write password-protected XLSX files. Agile Encryption (AES-256-CBC, SHA-512) for modern files, Standard Encryption (AES-128-ECB, SHA-1) for legacy compatibility. OLE2 compound document read/write. 299 Rust tests + 583 TypeScript tests.
- OLE2 compound document detection — magic byte analysis distinguishes encrypted XLSX, legacy XLS, and normal ZIP
- EncryptionInfo parser — Agile (v4.4) and Standard (v2.2/3.2/4.2) encryption method identification
- SHA-512/SHA-256/SHA-1 key derivation — per ECMA-376 SS2.3.6.2 with configurable spin count (100K default)
- AES-256-CBC segment decryption — 4096-byte segment-based decryption with per-segment IV derivation
- AES-128-ECB decryption — Standard Encryption verifier decryption using raw block cipher
- HMAC-SHA-512 integrity — tamper detection via encrypt-then-MAC verification
- Password verification — constant-time comparison for Agile and Standard encryption
-
readBuffer(data, { password })— simple API to read encrypted files -
readFile(path, { password })— file-based encrypted read (Node.js/Bun/Deno)
- OLE2 compound document writer — v3 format (512-byte sectors), FAT chains, directory entries
- Agile Encryption pipeline — random salt/key generation, segment-based AES-256-CBC encryption
- HMAC integrity embedding — compute-and-encrypt HMAC for post-write tamper detection
- EncryptionInfo XML generation — produces spec-compliant Agile encryption descriptor
-
wb.toBuffer({ password })— simple API to write encrypted files -
wb.toFile(path, { password })— file-based encrypted write (Node.js/Bun/Deno)
-
SensitiveKey RAII wrapper —
#[derive(Zeroize, ZeroizeOnDrop)]for automatic key material cleanup -
No key leaks on error paths — all derived keys wrapped in SensitiveKey (immune to
?operator bypass) -
Constant-time comparisons —
constant_time_eqat all 3 secret comparison sites - Error message safety — no key material, hash values, or intermediate state in error strings
-
Cryptographically secure random —
getrandomwithwasm_jsfeature for WASM targets
-
sha10.10 — SHA-1 hash for Standard Encryption -
getrandom0.3 — CSPRNG for salt/key generation (WASM-compatible)
- 42 new Rust tests (OLE2 detection, encryption info, key derivation, AES, HMAC, OLE2 writer, security)
- 30+ new TypeScript tests (encryption read/write, roundtrip, edge cases, 10K-row performance)
Encryption support for the Worker API, OLE2 code deduplication, and crypto helper consolidation.
-
worker.readBuffer(data, { password })— read encrypted files in Web Worker thread -
worker.writeBuffer(data, { password })— write encrypted files in Web Worker thread - Backward compatible — password parameter is optional, existing code unchanged
-
OLE2_MAGIC consolidation — deduplicated constant from
detect.rsandwriter.rsintoole2/mod.rs -
Generic hash dispatcher —
with_hash_algeliminates triplicated SHA-512/SHA-256/SHA-1 match blocks -
HMAC computation helper —
compute_hmacdeduplicates HMAC dispatch between verify and encrypt paths -
DIFAT documentation — enhanced
write_ole2doc comments explaining 109-entry DIFAT limitation
-
digest0.10 (withallocfeature) —DynDigesttrait for generic hash dispatch
Critical bug fixes found during comprehensive codebase audit.
-
Image embedding broken —
generateDrawingXmlrId off-by-one causing images to silently fail -
Frozen panes: wrong
activePane— column-only freeze wrote"bottomLeft"instead of"topRight" -
Invalid JSON for NaN/Infinity —
write_f64_jsonnow writesnullinstead of crashing JSON.parse -
Custom numFmt ID collision —
resolveNumFmtIdnow always assigns IDs >= 164 per ECMA-376
-
HashMap::with_capacityin ZIP reader -
col_index_to_letterdelegates to optimizedcol_to_letters -
TextEncoderhoisted to module-level constant
Closed 7 SheetJS advantage categories + added native Excel Tables, headers/footers, row/column grouping, and print layout. 704 tests (217 Rust + 487 TypeScript).
-
encodeRow()— convert 0-based row index to 1-based string -
decodeRow()— convert 1-based row string to 0-based index -
splitCellRef()— decompose$A1ドルinto{ col, row, absCol, absRow }
-
appVersion— roundtrip<AppVersion>from app.xml -
hyperlinkBase— roundtrip<HyperlinkBase>from app.xml -
revision— roundtrip<cp:revision>from core.xml -
application,company,manager— now fully parsed and written
-
sheetToTxt()— tab-separated text output (reuses CSV engine with\tdelimiter) -
sheetToFormulae()— extract all cell values and formulas as["A1=100", "A3='SUM(A1:A2)"]
-
Conditional sections — parse
[>100]#,##0;[<=100]0.00with condition evaluation -
Bracket color codes — extract
[Red],[Blue],[Color3]–[Color56]from format strings -
formatCellRich()— returns{ text, color }with color metadata -
loadFormat()— register custom format code at runtime -
loadFormatTable()— bulk-register format codes by ID
-
ws.usedRange— computed getter returning cell extent (e.g.,"B2:D5") ornull -
ws.tabColor— getter/setter for sheet tab RGB hex color, roundtrips through WASM
-
cell.numberFormat— resolves style index → numFmtId → format code string -
cell.dateValue— returnsDateif cell contains a date-formatted number -
Stub cell type —
'stub'inCellTypeunion for explicitly empty cells (SheetJS type"z")
-
dynamicArray— roundtripcm="1"attribute on<f>element for SPILL/dynamic array formulas
| Category | v0.4.0 | v0.5.0 |
|---|---|---|
| Worksheet operations | Tie | modern-xlsx wins |
| Cell operations | Tie | modern-xlsx wins |
| Formulas | Tie | modern-xlsx wins |
| Document properties | SheetJS wins | modern-xlsx wins |
| Cell ref utilities | SheetJS wins | Tie |
| Number formatting | SheetJS wins | modern-xlsx wins |
| Sheet conversion | SheetJS wins | SheetJS (gap narrowed) |
-
Full OOXML Table read/write — native
xl/tables/table{n}.xmlsupport with SAX parser and XML writer -
ws.tables— getter returning all tables on a worksheet -
ws.getTable(name)— find table by name -
ws.addTable(opts)— create table with name, ref, columns, style, totalsRow, autoFilter -
ws.removeTable(name)— delete table by name -
60 built-in table styles —
TABLE_STYLES.light(1-21),.medium(1-28),.dark(1-11) -
VALID_TABLE_STYLES— ReadonlySet for style name validation -
TotalsRowFunction— type for totals row calculations (sum, count, average, etc.)
-
ws.headerFooter— getter/setter forHeaderFooterDatawith odd/even/first header/footer -
HeaderFooterBuilder— fluent builder withleft(),center(),right(),build() -
Static formatting helpers —
pageNumber(),totalPages(),date(),time(),fileName(),sheetName(),filePath() -
Text formatting —
bold(),italic(),underline(),strikethrough(),fontSize(),fontName(),color()
-
ws.groupRows(start, end, level?)— set outline level (0-7) on row range -
ws.ungroupRows(start, end)— remove outline level from rows -
ws.collapseRows(start, end)— collapse grouped rows -
ws.expandRows(start, end)— expand collapsed rows -
ws.groupColumns(start, end, level?)— set outline level on column range -
ws.ungroupColumns(start, end)— remove outline level from columns -
ws.outlineProperties— getter/setter for summary row/column position
-
wb.setPrintTitles(sheet, opts)— set repeating rows/columns via_xlnm.Print_Titlesdefined name -
wb.getPrintTitles(sheet)— get current print title configuration -
wb.setPrintArea(sheet, area)— set print area via_xlnm.Print_Areadefined name -
wb.getPrintArea(sheet)— get current print area
Declarative table generation API with styled headers, zebra striping, auto-width, merge cells, and multi-table composition. Critical bug fixes across WASM init, writer performance, and sheet conversion utilities.
-
drawTable(wb, ws, opts)— generate styled XLSX tables from declarative options, no manual cell coordinate math -
drawTableFromData(wb, ws, data, opts?)— create tables from JSON arrays with auto-extracted headers andheaderMapdisplay names -
Header styling — customizable font, background color, and alignment with sensible defaults (bold white on
#4472C4) -
Body styling — zebra striping via
alternateRowColor, configurable borders, font, and alignment - Auto-width — content-aware column width calculation with CJK double-width character support
-
Per-cell style overrides —
cellStylesmap with"row,col"keys for targeted formatting on top of base styles -
Merge cells — row and column spanning via
mergesarray (0-based, relative to data area) -
Column definitions —
TableColumnwith per-column width, alignment, and number format (e.g.'$#,##0.00') -
Freeze header —
freezeHeader: truefreezes the header row for scrolling -
Auto-filter —
autoFilter: trueadds filter dropdown arrows to header cells -
Nested composition — use
TableResult.lastDataRowto stack tables vertically with gap rows -
Side-by-side composition — use
origincolumn offsets to place tables horizontally
-
jsonToSheetstartCol —writeDataRownow respects thestartColparameter instead of ignoring it -
resolveNumFmtIdsort assumption — no longer assumes thenumFmtarray is sorted, uses correct lookup -
initWasmSyncrace condition — concurrentinitWasmcalls no longer conflict with synchronous initialization -
frozenPanefield names — table engine uses correctrows/colsfield names for frozen pane data -
WASM reader HashSet rebuild — eliminated unnecessary
HashSetreconstruction on every read operation -
Comments clone — removed avoidable
.clone()in comments path handling -
Writer insert shift — replaced
Vec::insert(0, ...)O(n) shift with efficient prepend pattern
-
columnToLetter/letterToColumn— corrected examples that used wrong indices -
serialToDate— fixed example showing wrong return type -
addValidation— corrected example using wrong field name - Rich text — fixed example that accessed private fields
Pure TypeScript barcode/QR generation with 9 formats, PNG renderer, and XLSX embedding. Full codebase audit with critical bug fixes. 437 tests (157 Rust + 280 TypeScript).
- 9 barcode formats — Code 39, Code 128, EAN-13, UPC-A, ITF-14, GS1-128, QR Code, Data Matrix, PDF417
-
renderBarcodePNG()— pure TypeScript PNG renderer (no external dependencies) -
generateBarcode()— one-call XLSX embedding with drawing XML and relationships -
generateDrawingXml()/generateDrawingRels()— low-level drawing XML generation - Multi-image accumulation — multiple images per sheet without data loss
-
Unicode panic —
validate.rsbyte-indexing multi-byte characters →chars().take(n).collect() -
Multi-image data loss —
addImage()now accumulates via Maps instead of overwriting drawing XML -
SST insert performance — Entry API replaces double hash lookup in
shared_strings.rs -
Comments author dedup — Entry::Vacant pattern eliminates double lookup in
comments.rs -
DXF diagonal attrs — eliminated redundant UTF-8 decoding in
styles.rs
- GF(256) lookup tables hoisted to module-level constants (Data Matrix encoder)
-
Array.prototype.at(-1)for O(1) sorted-array access (utils.ts,style-builder.ts) -
split_once(':')replacessplit(':').collect::<Vec<>>()in validators - Single
TextEncoderinstance in QR encoder (was creating two)
- All docs updated for v0.3.0 accuracy
- Barcode generation examples in playground
- Migration guides updated with barcode comparison
Browser-first release with IIFE bundle, CDN distribution, Web Worker support, and framework examples. 419 tests (171 Rust + 248 TypeScript).
-
IIFE build — self-contained
modern-xlsx.min.js(~29 KB) exposingwindow.ModernXlsx - CDN distribution — available on jsDelivr and unpkg, no bundler required
-
detectWasmUrl()— auto-detection of WASM binary URL fromdocument.currentScript.src -
initWasmSync(module)— synchronous WASM initialization from pre-loaded buffer -
ensureReady()— lazy auto-initialization on first use (cached Promise pattern) -
Source maps —
.mapfiles generated for all build outputs (ESM, IIFE, Worker)
-
createXlsxWorker()— off-thread XLSX read/write via Web Worker message passing -
modern-xlsx.worker.js— dedicated worker script with auto WASM init -
Transferable buffers — zero-copy transfer of
Uint8Arrayresults back to main thread
- tsdown 3-build config — ESM (primary), IIFE (browser), Worker (off-thread)
-
Package exports —
"."(ESM),"./browser"(IIFE),"./worker"(Worker script) -
browser/unpkg/jsdelivrfields in package.json for CDN auto-resolution
-
Interactive browser playground at
docs/site/playground.html - 6 built-in examples: Hello World, Styled Workbook, Read & Inspect, Batch Data, JSON to Sheet, Formulas & Dates
- Live WASM status indicator, Ctrl+Enter execution, download integration
-
React — hooks-based integration with
useEffectWASM init -
Vue 3 — Composition API with
onMountedWASM init -
Svelte 5 —
$effectrune-based WASM init - Angular — service-based WASM init with standalone components
- Cloudflare Workers — XLSX generation at the edge
- Deno Deploy — server-side XLSX with Deno-native imports
- Service Worker — offline XLSX generation with custom WASM URL
Complete 0.1.x hardening series. 419 tests (171 Rust + 248 TypeScript).
- Style roundtrip: gradient fills, diagonal borders, DXF styles, cell named styles
- Formula metadata: array formulas, shared formulas, formulaRef, sharedIndex
- Feature roundtrip: hyperlinks, comments, data validation, conditional formatting, sheet protection, page setup
- Edge cases: empty workbooks, malformed XML, boundary values
- Error handling: corrupted files, invalid inputs, graceful degradation
- Streaming & benchmarks: 100K row read/write, parallel parsing, buffer scaling
- Utilities: cell refs, dates, format codes, CSV/HTML/JSON conversion
-
push_entity()— zero-allocation XML entity resolution -
Vec::with_capacity()on all XML parse buffers (11 sites) -
from_utf8().unwrap_or_default()replacesfrom_utf8_lossy()(6 sites) -
entries.remove()in StreamingReader — moves data instead of cloning -
drain()incollect_preserved()— moves preserved entries -
#[non_exhaustive]on error enum
- Module-scoped
CELL_REF_REregex (hoisted from hot loop) -
isolatedDeclarations: truein tsconfig - Shared WASM init via
__tests__/setup.ts
- Migration guide: SheetJS
- Migration guide: ExcelJS
- Usage examples cookbook
- Release roadmap
- GitHub Actions pinned to stable versions
- Rust toolchain:
@stable - Node.js engine:
>=24.0.0
First public release.
- Full XLSX read/write with Rust WASM core
- Cell styling — fonts, fills, borders, alignment, number formats via StyleBuilder
- Data validation — list, number, date, text length, custom formula
- Conditional formatting — color scales, data bars, icon sets, formula-based rules
- Frozen panes, hyperlinks, comments, sheet protection, page setup
- Rich text with RichTextBuilder
- Named ranges, document properties, workbook views, calc chain
- Streaming reader/writer for large files
- Parallel sheet parsing (rayon)
- Cell reference utilities, date utilities, format utilities
- Sheet conversion utilities (JSON, CSV, HTML, array-of-arrays)
- Browser Blob download support
- 342 tests (170 Rust + 172 TypeScript)
| Operation | modern-xlsx | SheetJS CE | Factor |
|---|---|---|---|
| Read 100K | 1,155 ms | 4,927 ms | 4.3x faster |
| Write 100K | 5,048 ms | 5,048 ms | 1.0x |
| sheetToJson 10K | 54 ms | 103 ms | 1.9x faster |
modern-xlsx v1.0.0
Getting Started
Guides
- Charts & Visualizations
- Formula Engine
- Table Layout Engine
- Tables & Print Layout
- Encryption
- Feature Comparison
Reference
Migration
Project