@xlsxflow/core 1.1.5 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +19 -7
- package/dist/index.cjs +10 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +10 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.6
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `SheetWriter`, `SheetEditor` and `OdsWriter` keep up to four chunks queued for compression instead of one, so building the next chunk overlaps compressing the last. Writing 10M cells takes about 10% less time; the files are the same, and the writer still waits while nobody reads its output.
|
|
8
|
+
|
|
9
|
+
### Testing
|
|
10
|
+
|
|
11
|
+
- Public CI on every push and pull request: the tests on Node 20, 22 and 24 with coverage; the README's claims in Node, Bun, Deno, Chromium, Firefox, WebKit and Cloudflare Workers; and written and edited files validated with Microsoft's Open XML SDK and opened in LibreOffice. OpenSSF Scorecard and CodeQL check the repository.
|
|
12
|
+
|
|
3
13
|
## 1.1.5
|
|
4
14
|
|
|
5
15
|
Fixes found while testing every claim in the README on Node 20.12 and 25, Bun, Deno, Chrome and Cloudflare Workers.
|
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
[](https://www.npmjs.com/package/@xlsxflow/core)
|
|
9
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://github.com/xlsxflow/xlsxflow/actions/workflows/ci.yml)
|
|
11
|
+
[](https://scorecard.dev/viewer/?uri=github.com/xlsxflow/xlsxflow)
|
|
10
12
|
|
|
11
13
|
<p>
|
|
12
14
|
<a href="#features">Features</a> •
|
|
@@ -28,8 +30,8 @@ Rows are read and written one at a time instead of loading the whole workbook, s
|
|
|
28
30
|
## Features
|
|
29
31
|
|
|
30
32
|
- **No dependencies**: TypeScript on Web APIs (`ReadableStream`, `CompressionStream`, `Blob`).
|
|
31
|
-
- **Streaming**: rows are read and written one at a time, so memory stays flat as files grow (10M cells written with about
|
|
32
|
-
- **Runs anywhere with Web APIs**: tested on Node 20.12 and later, Bun, Deno, Chrome and Cloudflare Workers (without `nodejs_compat`).
|
|
33
|
+
- **Streaming**: rows are read and written one at a time, so memory stays flat as files grow (10M cells written with about 2 MB of extra heap; see [Benchmarks](#benchmarks)).
|
|
34
|
+
- **Runs anywhere with Web APIs**: tested on Node 20.12 and later, Bun, Deno, Chrome, Firefox, Safari (WebKit) and Cloudflare Workers (without `nodejs_compat`).
|
|
33
35
|
- **Read, write and edit**: stream rows out of a file, generate one on the fly, or change cells, rows, columns and sheets of an existing file while keeping everything else in it.
|
|
34
36
|
- **Styles and formulas**: fonts, fills, borders, alignment, number formats, conditional formats, validations, tables, notes, hyperlinks, autofilters, images, protection and page setup. Formulas and styles read back too.
|
|
35
37
|
- **Older and open formats**: the same reader opens Excel 97-2003 `.xls` files and OpenDocument `.ods` files, and `OdsWriter` writes `.ods`.
|
|
@@ -285,16 +287,26 @@ SheetJS reads and writes far more formats, and ExcelJS has a longer track record
|
|
|
285
287
|
October 2023). XlsxFlow focuses on `.xlsx`: streaming in flat memory, keeping everything in a file it edits,
|
|
286
288
|
and running on Web APIs alone.
|
|
287
289
|
|
|
290
|
+
## Testing
|
|
291
|
+
|
|
292
|
+
Every push and pull request runs, [in public CI](https://github.com/xlsxflow/xlsxflow/actions/workflows/ci.yml):
|
|
293
|
+
|
|
294
|
+
- the test suite (nearly 400 tests, including fuzzing with malformed ZIP and XML input and files saved by Excel, LibreOffice, SheetJS, ExcelJS, openpyxl and xlsx-populate) on Node 20, 22 and 24, with 96% line coverage;
|
|
295
|
+
- the README's claims, checked in Node, Bun, Deno, Chromium, Firefox, WebKit and Cloudflare Workers;
|
|
296
|
+
- written and edited workbooks validated against the Office Open XML schema with Microsoft's [Open XML SDK](https://github.com/dotnet/Open-XML-SDK), then opened and recalculated in LibreOffice.
|
|
297
|
+
|
|
298
|
+
[OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/xlsxflow/xlsxflow) and CodeQL check the repository, and npm releases are published from CI with [provenance](https://docs.npmjs.com/generating-provenance-statements).
|
|
299
|
+
|
|
288
300
|
## Benchmarks
|
|
289
301
|
|
|
290
|
-
Write benchmark: 10 numeric columns, at 100,000 rows (1M cells) and 1,000,000 rows (10M cells). Each library ran in its own process on Node v25.8.2 with a 4 GB heap limit, and "Heap" is the growth in heap usage.
|
|
302
|
+
Write benchmark: 10 numeric columns, at 100,000 rows (1M cells) and 1,000,000 rows (10M cells). Each library ran in its own process on Node v25.8.2 with a 4 GB heap limit, and "Heap" is the growth in heap usage. XlsxFlow and ExcelJS times are medians of five alternating runs (XlsxFlow 1.1.6 was faster in all five at both sizes); the other libraries' times are from one run. The laptop had other apps open and single runs varied by up to 2×, so treat differences under about 20% as a tie. Reproduce with `npx tsx scripts/benchmark-competitors.ts` (inside `packages/core`, after `pnpm build`). `BENCH_ROWS=1000000` runs 10M cells, `BENCH_LIBS=xlsxflow,exceljs` runs a subset, and a library still writing after `BENCH_TIMEOUT_MIN` minutes (default 10) is stopped. The 10M runs for SheetJS and excel4node used a 30-minute limit.
|
|
291
303
|
|
|
292
304
|
1M cells:
|
|
293
305
|
|
|
294
306
|
| Library | Write Time | File Size | Heap |
|
|
295
307
|
|---|---|---|---|
|
|
296
|
-
| **XlsxFlow** | **
|
|
297
|
-
| ExcelJS 4.4 (streaming writer) |
|
|
308
|
+
| **XlsxFlow** | **1,723 ms** | **2.9 MB** | **+2 MB** |
|
|
309
|
+
| ExcelJS 4.4 (streaming writer) | 2,290 ms | 3.0 MB | +9 MB |
|
|
298
310
|
| SheetJS 0.20.3, `compression: true` | 4,370 ms | 8.4 MB | +140 MB |
|
|
299
311
|
| SheetJS 0.20.3, default options | 5,379 ms | 31.4 MB | +140 MB |
|
|
300
312
|
| write-excel-file | 7,681 ms | 2.8 MB | +2 MB |
|
|
@@ -306,8 +318,8 @@ Write benchmark: 10 numeric columns, at 100,000 rows (1M cells) and 1,000,000 ro
|
|
|
306
318
|
|
|
307
319
|
| Library | Write Time | File Size | Heap |
|
|
308
320
|
|---|---|---|---|
|
|
309
|
-
| **XlsxFlow** | **19.
|
|
310
|
-
| ExcelJS 4.4 (streaming writer) |
|
|
321
|
+
| **XlsxFlow** | **19.1 s** | **29.9 MB** | **+2 MB** |
|
|
322
|
+
| ExcelJS 4.4 (streaming writer) | 20.5 s | 31.2 MB | +7 MB |
|
|
311
323
|
| write-excel-file | 68.1 s | 29.3 MB | +1 MB |
|
|
312
324
|
| xlsx-populate | 75.3 s | 30.0 MB | +1,118 MB |
|
|
313
325
|
| SheetJS 0.20.3 (with and without compression) | not finished after 30 min | | |
|
package/dist/index.cjs
CHANGED
|
@@ -62,9 +62,11 @@ class ZipStreamWriter {
|
|
|
62
62
|
const writer = compressor.writable.getWriter();
|
|
63
63
|
const reader = compressor.readable.getReader();
|
|
64
64
|
const input = inputStream.getReader();
|
|
65
|
-
// Node's and Bun's CompressionStream accept thousands of writes without backpressure, so
|
|
66
|
-
//
|
|
65
|
+
// Node's and Bun's CompressionStream accept thousands of writes without backpressure, so only a few
|
|
66
|
+
// chunks are fed ahead: a write resolves once the chunk is compressed, which waits while nobody reads
|
|
67
67
|
// the output. Rows are then only pulled as fast as the ZIP is consumed.
|
|
68
|
+
// A few writes in flight keep the compressor busy while the next chunk is built
|
|
69
|
+
const inFlight = [];
|
|
68
70
|
const feed = (async () => {
|
|
69
71
|
try {
|
|
70
72
|
while (true) {
|
|
@@ -74,8 +76,13 @@ class ZipStreamWriter {
|
|
|
74
76
|
await this.roomInQueue();
|
|
75
77
|
uncompressedSize += value.length;
|
|
76
78
|
crc = crc32Update(crc, value);
|
|
77
|
-
|
|
79
|
+
const written = writer.write(value);
|
|
80
|
+
written.catch(() => { }); // after a failure, writes nobody awaits any more must not go unhandled
|
|
81
|
+
inFlight.push(written);
|
|
82
|
+
if (inFlight.length >= 4)
|
|
83
|
+
await inFlight.shift();
|
|
78
84
|
}
|
|
85
|
+
await Promise.all(inFlight);
|
|
79
86
|
await writer.close();
|
|
80
87
|
}
|
|
81
88
|
catch (err) {
|