@levu304/excelrs
v2.9.1
Published
Native XLSX spreadsheet library for Node.js (Rust port of exceljs)
Readme
excelrs
Native XLSX spreadsheet library for Node.js — a Rust port of exceljs via napi-rs.
10–100× faster than exceljs for read/write, with a drop-in compatible API.
Install
npm install @levu304/excelrsQuick Start
import { Workbook } from '@levu304/excelrs';
// Read
const wb = new Workbook();
await wb.xlsx.readFile('input.xlsx');
const ws = wb.getWorksheet('Sheet1');
console.log(ws.getCell('B2').value);
// Write
const wb2 = new Workbook();
const ws2 = wb2.addWorksheet('Data');
ws2.addRow(['Name', 'Age', 'Active']);
ws2.addRow(['Alice', 30, true]);
const buf = await wb2.xlsx.write();
require('fs').writeFileSync('output.xlsx', buf);Async contract:
wb.xlsx.read(buffer)/wb.xlsx.readFile(path)andwb.xlsx.write()/wb.xlsx.writeFile(path)are async — the workbook state is only swapped once the returned Promise resolves. Accessing worksheets before awaiting the Promise will see stale state.
v1.0.0 — Drop-in ExcelJS compatibility milestone
v1.0.0 release closes remaining medium-effort ExcelJS parity gaps. All five areas below are read/write round-trippable verified against ExcelJS 4.4.0:
- Headers & footers —
ws.headerFooterread/write (<headerFooter>&C/&L/&Rformat codes). - Page setup / print —
ws.pageSetupread/write (pageMargins,paperSize,orientation,printArea,printTitlesvia defined names). - Workbook views & calc properties —
workbook.views/workbook.calcProperties(<bookViews>,<calcPr>). - Comments —
Cell.note/Cell.commentread/write (xl/commentsN.xml+ relationship, authors list). - Images / drawings —
ws.addImageread/write (xl/drawings/,xl/media/, anchors, relationship resolution).
See ROADMAP.md for full parity matrix and docs/spec.md for complete API specification.
Feature parity snapshot
| Area | Status | | --- | --- | | XLSX read / write | shipped (v0.1.0) | | CSV read / write | shipped (v0.9.0) | | Styles (font / fill / border / alignment / numFmt) | shipped (v0.2.0+) | | Merged cells, data validation, hyperlinks, freeze panes, sheet protection, auto filter | shipped (v0.5.0 / v0.8.0 / v0.11.0) | | Theme / indexed color refs, JS Date bridge | shipped (v0.6.0 / v0.13.0) | | Headers & footers, page setup, workbook views & calc, comments, images | shipped (v1.0.0) | | Formula evaluation, tables, charts, conditional formatting, pivots | planned (post-v1) |
Style System (v0.2.0)
Write-only support for cell and column styling. Font, Fill, Border, and Alignment properties with inline number formats — full-replace semantics.
const wb = new Workbook();
const ws = wb.addWorksheet('Sales');
// Column-level default style
ws.setColumns([
{ header: 'Name', key: 'name', width: 20, style: { font: { bold: true } } },
{ header: 'Amount', key: 'amount', width: 12 },
]);
ws.addRow(['Widget', 1250]);
ws.addRow(['Gadget', 990]);
// Cell-level override (full-replace — see spec §6.9)
ws.setCellStyle(2, 2, {
font: { color: 'FF00FF00', bold: true },
fill: { kind: 'solid', foreground: 'FFFFFF00' },
numFmt: '"$"#,##0.00',
});
const buf = await wb.xlsx.write();API Surface
Workbook → Worksheet → Row → Cell — mirrors exceljs exactly.
- Workbook:
constructor(),addWorksheet(),getWorksheet(),views,calcProperties,.xlsxI/O handle - Worksheet:
getCell(),getRow(),addRow(),removeRow(),setColumns()(use this to set columns),setCellStyle(),headerFooter,pageSetup,addImage(),rowCount,columnCount,columns(getter-only),rows - Row:
getCell(),values,height,hidden - Cell:
value(Number | String | Boolean | Formula | Null),address,formula,style(getter/setter, full-replace),note/comment - Column:
header,key,width,hidden,style(getter/setter, column default)
See docs/spec.md for the full API specification.
ExcelJS compat note — Images: ExcelJS places
addImageon theWorkbook(two-step:workbook.addImage(buffer) → imageIdthenworksheet.addImage(imageId, range)). excelrs places it onWorksheetas a single call.The anchor shape also differs: ExcelJS uses
{ tl: {col,row}, br: {col,row} }while excelrs uses the same ExcelJS shape viaImageAnchorInput(withtl,brorext). The anchor type (one-cell vs two-cell) is inferred from whetherbr(two-cell) orext(one-cell with explicit size) is provided — noanchorTypefield is needed.col/rowsupport fractional values (e.g.5.5) for sub-cell positioning.const ws = wb.addWorksheet('Sheet1'); // Two-cell anchor (fractional col/row allowed) ws.addImage({ extension: 'png', buffer, anchor: { tl: { col: 0, row: 0 }, br: { col: 5.5, row: 2.2 } } }); // One-cell anchor with explicit size ws.addImage({ extension: 'png', buffer, anchor: { tl: { col: 1, row: 1 }, ext: { width: 120, height: 60 } } });There is no
Workbook.addImage,Workbook.getImage,Worksheet.addBackgroundImage, orWorksheet.getBackgroundImageId— the ExcelJS global image registry is not replicated.
ExcelJS compat note — Column setter: In ExcelJS
worksheet.columnsis a read-write property (getter + setter). In excelrs it is getter-only — direct assignment (worksheet.columns = [...]) is not supported. Callworksheet.setColumns([...])instead. This matches the method-call pattern used elsewhere (setCellStyle,setColumns). The getterworksheet.columnsreturns the current column definitions.
v0.2.0 — Style System (write only)
Read and write .xlsx files with correct data fidelity. Cell and column
styling for Font, Fill, Border, Alignment, and number formats (write only).
Limitations (see spec §9.2.1 for full deferred list):
- Style read round-trip shipped in v0.3.0 (styled
.xlsxpreserves styles). - Cell-level interior mutability shipped in v0.4.0 —
ws.getCell('A1').style = {...}andws.getCell('A1').value = xnow persist into the worksheet automatically (viaArc<Mutex<CellInner>>) - Alignment emission shipped in v0.3.0 (accepted in
StyleJS object, emitted on write). - CSV via
wb.csv— single-sheet only on write (CSV cannot represent multiple worksheets); numbers are inferred on read, all other CSV values are strings; no formula evaluation (cached value is emitted when available) - Formula evaluation available via the
formula-evalCargo feature (built into release binaries since v2.7.0). ProvidesFormulaEvaluatorwith 20 built-in functions (SUM, AVERAGE, MIN, MAX, etc.), Excel-spec error propagation, andWorksheet::recalculate()(Rust-only for now — JS exposure deferred).Cell.cachedValueJS getter returns cached computed values from formula cells. - No XLS / XLSB support (merged cells, data validation, freeze panes, CSV, headers/footers, page setup, comments, images: shipped).
- Theme color references are preserved on write (v0.13.0):
<color theme="N"/>(+tint) is emitted instead of a flattened ARGB; the publiccolorvalue remains the resolved ARGB string - Date cell values are preserved as JS
Date(v0.13.0):Cell.valuereturnsDate | CellValuefrom Date cells; the setter accepts a JSDate, storing it as the Excel serial number and injecting an appropriate datenumFmt(if none is set) so the value survives read→write round-trip as a true Date
Streaming XLSX (v2.1.0+)
Streaming XLSX for large .xlsx files.
- Read is constant-memory:
StreamReadermaterializes one sheet at a time, so peak memory stays bounded by a single sheet regardless of workbook size. - Write buffers every sheet in memory and builds the full archive at
finalize()— it is not constant-memory. Use it when the whole output fits in RAM; for very large outputs prefer writing to disk or generating in parts.
import { StreamReader, StreamWriter } from '@levu304/excelrs'
// Read: yields sheets one at a time via for-await-of
const reader = new StreamReader(buffer)
for await (const sheet of reader) {
console.log(sheet.name, sheet.rows.length)
// Each sheet's rows are yielded here — only one in memory at a time
}
// Write: accepts sheets incrementally
const writer = new StreamWriter()
writer.writeSheet(sheet1)
writer.writeSheet(sheet2)
const output = writer.finalize() // BufferHand-written bridge functions (Node Readable / Writable / AsyncIterable adapters):
import { read, write, readAsReadable, writeToWritable } from '@levu304/excelrs/stream-bridge'
// AsyncIterable
for await (const sheet of read(buffer)) { ... }
// Node Readable
Readable.from(read(buffer))
// Node Writable
await writeToWritable(read(buffer), writable)Development
pnpm build # Build Rust → native addon
cargo test # Rust unit tests
pnpm test # JS integration tests
cargo clippy -- -D warnings
cargo fmt -- --checkLicense
Dual-licensed under MIT or Apache-2.0 — see LICENSE-MIT and LICENSE-APACHE.
Bridging Features
This repo includes two streaming XLSX capabilities:
streaming-node-bridge (archived)
Previously delivered: async iterable wb.stream.xlsx.read() and write(sheets) that preserve values only (no styles). Original constant-memory intent for Node.
streaming-safety (active)
Newly delivered via streaming-hardening: zip‑bomb rejection, stream termination, and per‑sheet constant‑memory reading. Read path is now constant‑memory; write path remains buffered.
Both are additive; the legacy v2.0.0 streaming APIs remain available.
