eccellente
v0.1.0
Published
Excel (xlsx/xlsm) files for JavaScript — fast, streaming, with eccellente DX
Maintainers
Readme
eccellente
Excel (xlsx/xlsm) files for JavaScript — fast, streaming, with eccellente DX.
- Fast: reads 1M cells in ~0.6s and writes them in ~1.3s
- Streaming: read and write row by row with O(row) memory — process huge files in a few MB of RAM
- One dependency (fflate); own XML parser, DOCTYPE rejected by design
- TypeScript-first: plain-object styles with full inference, no class ceremony
- Runs on Node ≥20, Bun and browsers — the core never touches the filesystem (Deno should work too, but isn't part of the test matrix yet)
Status: 0.x. The format layer is tested with round-trips against genuine Excel files and was validated cell-by-cell against openpyxl during development, but the public API may still shift before 1.0.
Install
npm i eccellenteShips as ESM. require('eccellente') works natively on Node ≥20.19 / ≥22.12.
Quickstart
import { Workbook, readWorkbook } from 'eccellente';
// Write
const wb = new Workbook();
const ws = wb.addSheet('Report');
ws.cell('A1').value = 'Total';
ws.cell('A1').style = { font: { bold: true, size: 14, color: '#FFFFFF' }, fill: '#4472C4' };
ws.cell('B1').value = 1234.56;
ws.cell('B2').value = 2765.44;
ws.cell('B3').formula = '=SUM(B1:B2)';
ws.cell('B1').style = { numberFormat: '$ #,##0.00' };
ws.cell('C1').value = new Date(2026, 6, 15);
await wb.save('out.xlsx');
// Read
const loaded = await readWorkbook('out.xlsx');
loaded.sheet('Report').cell('B1').value; // 1234.56
for (const row of loaded.sheet(0).rows()) {
console.log(row.number, row.cells.map((c) => c.value));
}Worksheet features
ws.mergeCells('A1:C1');
ws.setColumnWidth('A', 22);
ws.setRowHeight(1, 30);
ws.freezePanes = 'A2';
ws.autoFilter = 'A1:C10';
ws.tabColor = '#FF6600';
ws.cell('D1').hyperlink = 'https://example.com';
ws.cell('B2').note = 'Double-check this number';
ws.addDataValidation({ range: 'C2:C10', type: 'list', formula1: '"Yes,No"' });
ws.addConditionalFormatting({
range: 'B2:B100',
rules: [{ type: 'cellIs', operator: 'lessThan', formulas: ['0'],
style: { font: { color: '#9C0006' }, fill: '#FFC7CE' } }],
});
ws.addTable({ name: 'Sales', ref: 'A1:C10' });
ws.protect({ password: 'secret' });
wb.definedNames.set('Prices', 'Report!$B$2:$B$10');Streaming (large files)
Memory stays proportional to one row, not the file:
import { openWorkbookStream, StreamingWorkbookWriter } from 'eccellente';
// Read row by row — the sheet XML is never fully materialized
const stream = await openWorkbookStream('big-file.xlsx');
for await (const row of stream.rows('Data')) {
handleRow(row.number, row.values);
}
// Write row by row — deflates straight to disk
const writer = await StreamingWorkbookWriter.createFile('out.xlsx');
const sheet = writer.addSheet('Data');
for (const item of millionsOfItems) {
sheet.addRow([item.id, item.name, item.value]);
}
await writer.finish();Try it from a clone
git clone https://github.com/menaguilherme/eccellente && cd eccellente
npm install
npm run example # builds a styled workbook and reads it back
npm run example:streaming # 100k rows written and read with O(row) memoryThe generated files land in example/output/ — open them in Excel.
Benchmarks
1M cells (100k rows × 10 cols), Apple M4, Node 24, best of 3, isolated
processes (npm run bench; ROWS=n to scale). The streaming modes trade a
little wall time for flat memory, and both readers are checksum-verified to
see exactly the same data:
| Mode | Time | Peak RSS | |---|---|---| | read | 0.62s | 437 MB | | read (streaming) | 0.65s | 289 MB | | write | 1.31s | 590 MB | | write (streaming) | 0.96s | 191 MB |
xlsm
Macro-enabled files round-trip safely: vbaProject.bin is preserved as an
opaque blob (macros are never parsed or executed).
Current limitations
Known scope limitations in 0.x — read before using in production:
- Charts, images and pivot tables are not modelled yet. Reading a file that contains them and saving it back keeps the underlying parts but breaks their link to the sheet — the visuals are lost. If your workflow edits files with charts/images, wait for that support or keep the original file.
- Rich text is flattened: per-run formatting inside a cell (one bold word in a sentence) is read as plain text.
- Theme/indexed colors are not resolved to RGB in
cell.style(they come back asundefined); gradient fills are not modelled. - The streaming writer covers values, styles and row heights only — no
merges, hyperlinks or notes in that mode (the regular
Workbookhas them all). - ESM-only package:
require('eccellente')works natively on Node ≥20.19 / ≥22.12; older CommonJS consumers need a dynamicimport(). - Minor fidelity notes: duration-formatted cells (
[h]:mm:ss) read as numbers (JS has no duration type); rare worksheet sections (row/column page breaks, phonetic data,extLst) are dropped on resave.
Acknowledgements
eccellente is an independent implementation, but its understanding of the xlsx format owes a lot to openpyxl — the Python library whose battle-tested handling of OOXML quirks (the 1900 leap-year bug, date-format detection, the legacy protection hash) served as the reference during development. Generated files were validated against it cell-by-cell while each feature was built.
