@zip.js/zip.js 2.18.2 → 2.20.0

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.
Files changed (45) hide show
  1. package/BENCHMARKS.md +172 -144
  2. package/deno.json +1 -1
  3. package/dist/zip-core-external.js +192 -120
  4. package/dist/zip-core-external.min.js +1 -1
  5. package/dist/zip-core.js +193 -119
  6. package/dist/zip-core.min.js +1 -1
  7. package/dist/zip-fs-core-external.js +192 -120
  8. package/dist/zip-fs-core-external.min.js +1 -1
  9. package/dist/zip-fs-core.js +193 -119
  10. package/dist/zip-fs-core.min.js +1 -1
  11. package/dist/zip-fs-external.js +192 -120
  12. package/dist/zip-fs-external.min.js +1 -1
  13. package/dist/zip-fs-native.js +195 -121
  14. package/dist/zip-fs-native.min.js +1 -1
  15. package/dist/zip-fs.js +194 -120
  16. package/dist/zip-fs.min.js +1 -1
  17. package/dist/zip-legacy.js +195 -121
  18. package/dist/zip-legacy.min.js +1 -1
  19. package/dist/zip-native.js +195 -121
  20. package/dist/zip-native.min.js +1 -1
  21. package/dist/zip-web-worker-native.js +1 -1
  22. package/dist/zip-web-worker.js +1 -1
  23. package/dist/zip.js +194 -120
  24. package/dist/zip.min.js +1 -1
  25. package/index-native.cjs +195 -121
  26. package/index-native.min.js +1 -1
  27. package/index.cjs +194 -120
  28. package/index.d.cts +95 -26
  29. package/index.d.ts +95 -26
  30. package/index.min.js +1 -1
  31. package/lib/core/codec-pool.js +1 -2
  32. package/lib/core/codec-worker-web.js +11 -39
  33. package/lib/core/codec-worker.js +1 -2
  34. package/lib/core/configuration.js +1 -1
  35. package/lib/core/options.js +0 -2
  36. package/lib/core/streams/codec-stream.js +1 -1
  37. package/lib/core/version.js +1 -1
  38. package/lib/core/web-worker-base.js +2 -3
  39. package/lib/core/web-worker-inline-native.js +1 -1
  40. package/lib/core/web-worker-inline-wasm.js +1 -1
  41. package/lib/core/zip-reader.js +100 -40
  42. package/lib/core/zip-writer.js +75 -34
  43. package/lib/zip-core-reader.js +2 -0
  44. package/package.json +1 -1
  45. package/worker-message-property-names.js +1 -1
package/BENCHMARKS.md CHANGED
@@ -1,15 +1,17 @@
1
1
  # Benchmarks
2
2
 
3
3
  Measurements of [@zip.js/zip.js](https://www.npmjs.com/package/@zip.js/zip.js) against
4
- [jszip](https://github.com/Stuk/jszip), [fflate](https://github.com/101arrowz/fflate) and
5
- [archiver](https://github.com/archiverjs/node-archiver), of zip.js's own codec backends, and of
4
+ [jszip](https://github.com/Stuk/jszip), [fflate](https://github.com/101arrowz/fflate),
5
+ [archiver](https://github.com/archiverjs/node-archiver) and the
6
+ [ZIP API of `node:zlib`](https://nodejs.org/api/zlib.html#class-zlibzipentry), of zip.js's own
7
+ codec backends, and of
6
8
  the runtimes it runs on. Every number on this page comes from the harness in
7
9
  [`benchmarks/`](benchmarks/), on the machine, date and versions below. The results are specific
8
10
  to them; run the harness on your machine before relying on any of them.
9
11
 
10
12
  The page answers four questions, each from one or two scripts of the harness:
11
13
 
12
- - [How does zip.js compare with jszip, fflate and archiver?](#zipjs-against-jszip-fflate-and-archiver)
14
+ - [How does zip.js compare with jszip, fflate, archiver and node:zlib?](#zipjs-against-jszip-fflate-archiver-and-nodezlib)
13
15
  `bench.js`, on Node.
14
16
  - [Which codec backend of zip.js is fastest, and what does concurrent `add()` buy?](#the-codec-backends-of-zipjs)
15
17
  `bench-codecs.js` and `bench-backends.js`, on Node.
@@ -22,14 +24,15 @@ The page answers four questions, each from one or two scripts of the harness:
22
24
  | | |
23
25
  |---|---|
24
26
  | Machine | Apple M2, 8 cores (4 performance + 4 efficiency), 16 GB RAM |
25
- | OS | macOS 26.6.2 (arm64) |
26
- | Runtimes | Node.js v26.7.0, Bun 1.4.2, Deno 2.9.6 |
27
- | Browsers | Firefox 154, Chrome 153 (the browser encryption table only) |
28
- | zip.js | 2.14.0 |
27
+ | OS | macOS 27.0 (arm64) |
28
+ | Runtimes | Node.js v26.10.0, Bun 1.4.2, Deno 2.9.7 |
29
+ | Browsers | Firefox 156, Chrome 153, headless (the browser encryption table only) |
30
+ | zip.js | 2.19.0 |
29
31
  | jszip | 3.10.2 |
30
32
  | fflate | 0.8.3 |
31
33
  | archiver | 8.0.0 |
32
- | Measured | 2026-09-10; the two encryption tables on 2026-09-12, on the engines that follow 2.14.0 |
34
+ | node:zlib | the ZIP API built into Node.js 26.10.0, experimental, added in 26.8.0 |
35
+ | Measured | 2026-09-30; the browser encryption table on 2026-09-26 |
33
36
 
34
37
  ## Method
35
38
 
@@ -51,29 +54,37 @@ The page answers four questions, each from one or two scripts of the harness:
51
54
  table; it is not a verdict.
52
55
  - **Memory.** Peak resident set size reported by `/usr/bin/time -l`, as a delta over an empty
53
56
  Node process (about 50 MB). It is the highest of the 3 runs, while the time is their median.
54
- - **Level.** Every library compresses at its own level 6. A level is not a unit shared between
57
+ - **Versions of this page.** A re-run replaces the whole page, and the runtimes, browsers and OS
58
+ move between two runs along with zip.js, so a number read from an older version of the page
59
+ is not comparable with one here: only a column measured in the same run isolates a single
60
+ variable, as the `chunkSize` 64 KB column of the
61
+ [runtime table](#concurrent-add-per-runtime) does for the default chunk size.
62
+ - **Level.** Every library compresses at its own level 6; the ZIP API of `node:zlib` has no level
63
+ option and deflates at zlib's default, which is 6. A level is not a unit shared between
55
64
  libraries: zlib's level 6 and fflate's level 6 are different parameter sets and produce
56
65
  different sizes, so every time is printed next to the size it achieved, and the
57
66
  [Codecs](#codecs-at-equal-output-size) table compares by output size instead.
58
- - **Codecs.** Every head-to-head table names the codec of each row. archiver runs Node's `zlib`
59
- module, the host's zlib written in C; jszip (pako) and fflate deflate and inflate in
60
- JavaScript. zip.js selects its codec at runtime, so it has one row per backend: the
67
+ - **Codecs.** Every head-to-head table names the codec of each row. archiver and the ZIP API of
68
+ `node:zlib` run Node's `zlib` module, the host's zlib written in C; jszip (pako) and fflate
69
+ deflate and inflate in JavaScript. zip.js selects its codec at runtime, so it has one row per backend: the
61
70
  `CompressionStream` and `DecompressionStream` of the runtime, Node's zlib here and the default
62
71
  where they exist; the bundled WebAssembly zlib; and the pure-JavaScript zlib port. The last two
63
72
  ship with the library, like the codecs of jszip and fflate, so those rows compare the
64
73
  libraries without the host's zlib in the picture.
65
74
  - **Checks.** No decompression row verifies the CRC-32 of the entries: zip.js and jszip leave
66
- the check off by default, and fflate has none on read. Turning it on in zip.js makes the
75
+ the check off by default, fflate has none on read, and `node:zlib` verifies by default so its
76
+ row passes `verify: false`. Turning it on in zip.js makes the
67
77
  inflater verify the CRC-32 through a gzip trailer: about 7 ms per 20 MB on the WebAssembly
68
78
  codec, within the noise on Node's `DecompressionStream`; the pure-JavaScript port keeps a
69
79
  separate pass over the output, about 15 ms.
70
80
  - **Units.** MB in the tables is 10^6 bytes. The workload sizes (20 MB, 8 MB, 256 MB) and the
71
81
  MB/s throughputs use 2^20 bytes.
72
82
 
73
- ## zip.js against jszip, fflate and archiver
83
+ ## zip.js against jszip, fflate, archiver and node:zlib
74
84
 
75
85
  One Node process per measurement (`benchmarks/bench.js`), one thread, every library at its
76
- level 6. zip.js appears once per codec backend.
86
+ level 6. zip.js appears once per codec backend. The `node:zlib` rows use the ZIP API built into
87
+ Node.js since 26.8.0: `ZipEntry.create()` and `createZipArchive()` to write, `ZipBuffer` to read.
77
88
 
78
89
  ### Compression
79
90
 
@@ -82,33 +93,37 @@ size each row produced:
82
93
 
83
94
  | Library | Codec | Compressible text (20 MB) | Incompressible data (20 MB) | 5,000 files × ~2 KB |
84
95
  |---|---|--:|--:|--:|
85
- | @zip.js/zip.js | `CompressionStream` | **700 ms** / 6.1 MB | 365 ms / 21.0 MB | 888 ms / 4.9 MB |
86
- | @zip.js/zip.js | WASM zlib | 1057 ms / 6.2 MB | 475 ms / 21.0 MB | 679 ms / 4.9 MB |
87
- | @zip.js/zip.js | pure-JS zlib | 1238 ms / 6.2 MB | 802 ms / 21.0 MB | 1125 ms / 4.9 MB |
88
- | jszip | pako (JavaScript) | 1723 ms / 6.2 MB | 862 ms / 21.0 MB | 843 ms / 4.7 MB |
89
- | fflate | fflate (JavaScript) | 909 ms / 6.3 MB | **297 ms** / 21.0 MB | **281 ms** / 4.8 MB |
90
- | archiver | Node `zlib` (C) | 702 ms / 6.1 MB | 359 ms / 21.0 MB | 424 ms / 4.8 MB |
96
+ | @zip.js/zip.js | `CompressionStream` | 730 ms / 6.1 MB | 357 ms / 21.0 MB | 731 ms / 4.9 MB |
97
+ | @zip.js/zip.js | WASM zlib | 1098 ms / 6.2 MB | 460 ms / 21.0 MB | 551 ms / 4.9 MB |
98
+ | @zip.js/zip.js | pure-JS zlib | 1229 ms / 6.2 MB | 787 ms / 21.0 MB | 962 ms / 4.9 MB |
99
+ | jszip | pako (JavaScript) | 1753 ms / 6.2 MB | 854 ms / 21.0 MB | 846 ms / 4.7 MB |
100
+ | fflate | fflate (JavaScript) | 908 ms / 6.3 MB | **298 ms** / 21.0 MB | **278 ms** / 4.8 MB |
101
+ | archiver | Node `zlib` (C) | 708 ms / 6.1 MB | 359 ms / 21.0 MB | 423 ms / 4.8 MB |
102
+ | node:zlib | Node `zlib` (C) | **678 ms** / 6.1 MB | 346 ms / 21.0 MB | 371 ms / 4.7 MB |
91
103
 
92
104
  Peak memory for the same runs (Δ over baseline):
93
105
 
94
106
  | Library | Codec | Compressible text (20 MB) | Incompressible data (20 MB) | 5,000 files × ~2 KB |
95
107
  |---|---|--:|--:|--:|
96
- | @zip.js/zip.js | `CompressionStream` | 91 MB | 153 MB | 245 MB |
97
- | @zip.js/zip.js | WASM zlib | 119 MB | 170 MB | 250 MB |
98
- | @zip.js/zip.js | pure-JS zlib | 114 MB | 172 MB | 276 MB |
99
- | jszip | pako (JavaScript) | 69 MB | 93 MB | 269 MB |
100
- | fflate | fflate (JavaScript) | **62 MB** | 108 MB | **102 MB** |
101
- | archiver | Node `zlib` (C) | 72 MB | **74 MB** | 137 MB |
102
-
103
- On the text file zip.js with `CompressionStream` and archiver take the same time, 700 and
104
- 702 ms, for the same output size: both run Node's zlib. With the codecs that ship with each
105
- library, fflate takes 909 ms for 2 % more bytes, zip.js's WebAssembly zlib 1057 ms, its
106
- pure-JavaScript port 1238 ms and jszip 1723 ms. On incompressible data fflate is the fastest row
107
- and archiver the smallest in memory. On 5,000 small files the WebAssembly backend is 23 % faster
108
- than `CompressionStream`, so a native stream costs more per entry than a WebAssembly one; fflate
109
- takes 41 % of the WebAssembly row's time there against 86 % on the 20 MB file, and the
110
- difference is the work zip.js does per entry, a stream and a header each. The zip.js rows use
111
- the most memory on the two 20 MB files.
108
+ | @zip.js/zip.js | `CompressionStream` | 115 MB | 184 MB | 262 MB |
109
+ | @zip.js/zip.js | WASM zlib | 139 MB | 206 MB | 244 MB |
110
+ | @zip.js/zip.js | pure-JS zlib | 126 MB | 182 MB | 287 MB |
111
+ | jszip | pako (JavaScript) | 76 MB | 102 MB | 269 MB |
112
+ | fflate | fflate (JavaScript) | 68 MB | 114 MB | **106 MB** |
113
+ | archiver | Node `zlib` (C) | 78 MB | 90 MB | 151 MB |
114
+ | node:zlib | Node `zlib` (C) | **51 MB** | **82 MB** | 245 MB |
115
+
116
+ On the text file the three rows that run Node's zlib take the same time within 8 %, for the
117
+ same output size: `node:zlib` 678 ms, archiver 708 ms and zip.js with `CompressionStream`
118
+ 730 ms. With the codecs that ship with each library, fflate takes 908 ms for 2 % more bytes,
119
+ zip.js's WebAssembly zlib 1098 ms, its pure-JavaScript port 1229 ms and jszip 1753 ms. On
120
+ incompressible data fflate is the fastest row and `node:zlib` the smallest in memory. On 5,000
121
+ small files the WebAssembly backend is 25 % faster than `CompressionStream`, so a native stream
122
+ costs more per entry than a WebAssembly one; fflate takes 50 % of the WebAssembly row's time
123
+ there against 83 % on the 20 MB file, and the difference is the work zip.js does per entry, a
124
+ stream and a header each. `node:zlib` takes 371 ms on the same files and archiver 423 ms, both
125
+ on the same zlib as `CompressionStream`. The zip.js rows use the most memory on the two 20 MB
126
+ files, where `node:zlib`, which holds one buffer per entry and no stream, uses the least.
112
127
 
113
128
  ### Decompression
114
129
 
@@ -117,56 +132,59 @@ excluded.
117
132
 
118
133
  | Library | Codec | Compressible text (20 MB) | 5,000 files × ~2 KB |
119
134
  |---|---|--:|--:|
120
- | @zip.js/zip.js | `DecompressionStream` | **66 ms** | 628 ms |
121
- | @zip.js/zip.js | WASM zlib | 78 ms | 476 ms |
122
- | @zip.js/zip.js | pure-JS zlib | 180 ms | 615 ms |
123
- | jszip | pako (JavaScript) | 137 ms | 444 ms |
124
- | fflate | fflate (JavaScript) | 116 ms | **102 ms** |
135
+ | @zip.js/zip.js | `DecompressionStream` | 64 ms | 485 ms |
136
+ | @zip.js/zip.js | WASM zlib | 71 ms | 342 ms |
137
+ | @zip.js/zip.js | pure-JS zlib | 155 ms | 527 ms |
138
+ | jszip | pako (JavaScript) | 140 ms | 438 ms |
139
+ | fflate | fflate (JavaScript) | 116 ms | **101 ms** |
140
+ | node:zlib | Node `zlib` (C) | **60 ms** | 183 ms |
125
141
 
126
142
  Peak memory for the same runs (Δ over baseline):
127
143
 
128
144
  | Library | Codec | Compressible text (20 MB) | 5,000 files × ~2 KB |
129
145
  |---|---|--:|--:|
130
- | @zip.js/zip.js | `DecompressionStream` | 116 MB | 350 MB |
131
- | @zip.js/zip.js | WASM zlib | 163 MB | 484 MB |
132
- | @zip.js/zip.js | pure-JS zlib | 154 MB | 451 MB |
133
- | jszip | pako (JavaScript) | 103 MB | 306 MB |
134
- | fflate | fflate (JavaScript) | **92 MB** | **120 MB** |
135
-
136
- On the 20 MB stream `DecompressionStream` takes 66 ms and the WebAssembly inflate 78 ms, then
137
- fflate 116 ms, jszip 137 ms and the pure-JavaScript port 180 ms. The WebAssembly row includes
138
- the module instantiation a fresh process pays once, about 20 ms. Warm, the two inflates are
139
- within 6 % of each other in the [Codecs](#codecs-at-equal-output-size) table. On 5,000 small
140
- files the order reverses: fflate takes 102 ms, jszip 444 ms and the three zip.js rows 476 to
141
- 628 ms, with `DecompressionStream` the slowest of them, the same per-entry cost as in
142
- compression. fflate uses the least memory on both workloads, 34 % of the `DecompressionStream`
143
- row on the small files, and the WebAssembly and pure-JavaScript rows peak higher than
144
- `DecompressionStream`. The three zip.js rows allocate the same 800 MB of stream objects over the
145
- 5,000 entries, about 160 KB per entry. The WebAssembly and pure-JavaScript codecs run on the
146
- JavaScript thread, so V8's incremental marking keeps more of that garbage alive between
147
- collections, and that is the difference between the rows.
146
+ | @zip.js/zip.js | `DecompressionStream` | 151 MB | 355 MB |
147
+ | @zip.js/zip.js | WASM zlib | 171 MB | 358 MB |
148
+ | @zip.js/zip.js | pure-JS zlib | 158 MB | 353 MB |
149
+ | jszip | pako (JavaScript) | 103 MB | 284 MB |
150
+ | fflate | fflate (JavaScript) | **97 MB** | **123 MB** |
151
+ | node:zlib | Node `zlib` (C) | 99 MB | 255 MB |
152
+
153
+ On the 20 MB stream `node:zlib` takes 60 ms and `DecompressionStream` 64 ms, the same zlib
154
+ reached two ways, then the WebAssembly inflate 71 ms, fflate 116 ms, jszip 140 ms and the
155
+ pure-JavaScript port 155 ms. The WebAssembly row includes the module instantiation a fresh
156
+ process pays once, about 20 ms. Warm, the WebAssembly inflate is 27 % faster than
157
+ `DecompressionStream` in the [Codecs](#codecs-at-equal-output-size) table. On 5,000 small files
158
+ the order changes: fflate takes 101 ms, `node:zlib` 183 ms, then the WebAssembly row 342 ms,
159
+ jszip 438 ms, `DecompressionStream` 485 ms and the pure-JavaScript port 527 ms, the same
160
+ per-entry cost as in compression. fflate uses the least memory on both workloads, 35 % of the
161
+ `DecompressionStream` row on the small files. The three zip.js rows allocate the same 800 MB of
162
+ stream objects over the 5,000 entries, about 160 KB per entry, and peak within 5 MB of each
163
+ other.
148
164
 
149
165
  ### Streaming a 256 MB file, disk to disk
150
166
 
151
167
  One file read with `fs.createReadStream` in 64 KB chunks and one archive written with
152
168
  `fs.createWriteStream`, one entry, each library's streaming API. zip.js takes Web Streams, so its
153
169
  rows go through Node's `Readable.toWeb` and `Writable.toWeb` bridges; the other libraries take
154
- the Node streams directly.
170
+ the Node streams directly, `node:zlib` through `ZipEntry.createStream()`.
155
171
 
156
172
  | Library | Codec | Median time | Peak memory (Δ) | Output |
157
173
  |---|---|--:|--:|--:|
158
- | @zip.js/zip.js | `CompressionStream` | **9055 ms** | 62 MB | 78.3 MB |
159
- | archiver | Node `zlib` (C) | 9115 ms | 71 MB | 78.3 MB |
160
- | fflate | fflate (JavaScript) | 12456 ms | **41 MB** | 80.2 MB |
161
- | @zip.js/zip.js | WASM zlib | 13629 ms | 88 MB | 78.9 MB |
162
- | @zip.js/zip.js | pure-JS zlib | 15485 ms | 120 MB | 78.9 MB |
163
- | jszip | pako (JavaScript) | 22539 ms | 74 MB | 78.9 MB |
164
-
165
- Every row streams: the peaks stay between 41 MB (fflate) and 120 MB (the pure-JavaScript port)
166
- over baseline on the 256 MB input. zip.js with `CompressionStream` and archiver take the same
167
- time within 1 %; jszip takes 2.5× that. With its WebAssembly zlib zip.js takes 1.5× the
168
- `CompressionStream` time and 1.1× fflate's, for 1.6 % fewer bytes; the pure-JavaScript port
169
- takes 1.2× fflate's time.
174
+ | node:zlib | Node `zlib` (C) | **8905 ms** | **42 MB** | 78.3 MB |
175
+ | @zip.js/zip.js | `CompressionStream` | 8926 ms | 117 MB | 78.3 MB |
176
+ | archiver | Node `zlib` (C) | 9167 ms | 81 MB | 78.3 MB |
177
+ | fflate | fflate (JavaScript) | 12781 ms | 57 MB | 80.2 MB |
178
+ | @zip.js/zip.js | WASM zlib | 13405 ms | 128 MB | 78.9 MB |
179
+ | @zip.js/zip.js | pure-JS zlib | 15326 ms | 123 MB | 78.9 MB |
180
+ | jszip | pako (JavaScript) | 22809 ms | 83 MB | 78.9 MB |
181
+
182
+ Every row streams: the peaks stay between 42 MB (`node:zlib`) and 128 MB (the WebAssembly zlib)
183
+ over baseline on the 256 MB input. The three rows on Node's zlib take the same time within 3 %;
184
+ jszip takes 2.6× that. With its WebAssembly zlib zip.js takes 1.5× the `CompressionStream` time
185
+ and 1.05× fflate's, for 1.6 % fewer bytes; the pure-JavaScript port takes 1.2× fflate's time.
186
+ The 256 KB chunks zip.js holds at each stage of its pipeline are what separates its
187
+ `CompressionStream` row from `node:zlib` in memory, 117 against 42 MB, at the same speed.
170
188
 
171
189
  ## The codec backends of zip.js
172
190
 
@@ -187,20 +205,20 @@ fastest such row. A row with an empty last column is beaten by nothing on the ta
187
205
 
188
206
  | Codec | Setting | Output | Ratio | Time | Throughput | Beaten by |
189
207
  |---|---|--:|--:|--:|--:|---|
190
- | WASM zlib | level 8 | 6,115,980 | 3.429 | 1504 ms | 13.3 MB/s | |
191
- | `CompressionStream` | no level control | 6,120,503 | 3.426 | 716 ms | 27.9 MB/s | |
192
- | WASM zlib | level 6 | 6,165,103 | 3.402 | 1047 ms | 19.1 MB/s | `CompressionStream` |
193
- | fflate | level 8, its best | 6,239,025 | 3.361 | 767 ms | 26.1 MB/s | `CompressionStream` |
194
- | fflate | level 6 | 6,257,881 | 3.351 | 815 ms | 24.6 MB/s | `CompressionStream` |
195
- | WASM zlib | level 5 | 6,562,182 | 3.196 | 505 ms | 39.6 MB/s | |
196
- | fflate | level 5 | 6,648,574 | 3.154 | 541 ms | 36.9 MB/s | WASM zlib level 5 |
197
- | WASM zlib | level 4 | 6,907,987 | 3.036 | 278 ms | 71.9 MB/s | |
198
- | fflate | level 1 | 7,261,700 | 2.888 | 318 ms | 62.9 MB/s | WASM zlib level 4 |
199
- | WASM zlib | level 2 | 7,359,387 | 2.850 | 195 ms | 102.5 MB/s | |
200
- | WASM zlib | level 1 | 7,531,042 | 2.785 | 175 ms | 114.1 MB/s | |
208
+ | WASM zlib | level 8 | 6,115,980 | 3.429 | 1486 ms | 13.5 MB/s | |
209
+ | `CompressionStream` | no level control | 6,120,503 | 3.426 | 695 ms | 28.8 MB/s | |
210
+ | WASM zlib | level 6 | 6,165,103 | 3.402 | 1025 ms | 19.5 MB/s | `CompressionStream` |
211
+ | fflate | level 8, its best | 6,239,025 | 3.361 | 755 ms | 26.5 MB/s | `CompressionStream` |
212
+ | fflate | level 6 | 6,257,881 | 3.351 | 751 ms | 26.6 MB/s | `CompressionStream` |
213
+ | WASM zlib | level 5 | 6,562,182 | 3.196 | 493 ms | 40.6 MB/s | |
214
+ | fflate | level 5 | 6,648,574 | 3.154 | 534 ms | 37.5 MB/s | WASM zlib level 5 |
215
+ | WASM zlib | level 4 | 6,907,987 | 3.036 | 277 ms | 72.3 MB/s | |
216
+ | fflate | level 1 | 7,261,700 | 2.888 | 319 ms | 62.8 MB/s | WASM zlib level 4 |
217
+ | WASM zlib | level 2 | 7,359,387 | 2.850 | 210 ms | 95.0 MB/s | |
218
+ | WASM zlib | level 1 | 7,531,042 | 2.785 | 173 ms | 115.8 MB/s | |
201
219
 
202
220
  The pure-JavaScript port produces the same size as the WebAssembly codec at every level and
203
- takes 1.0 to 1.8× its time, the gap closing at the higher levels; the full 28-row table is in
221
+ takes 1.0 to 1.9× its time, the gap closing at the higher levels; the full 28-row table is in
204
222
  `benchmarks/results/codecs-results.json`.
205
223
 
206
224
  Every fflate row is beaten by a zlib row: by the host's `CompressionStream` at fflate's levels 6
@@ -212,16 +230,16 @@ Decompression of the same stream:
212
230
 
213
231
  | Codec | Time | Throughput |
214
232
  |---|--:|--:|
215
- | WASM zlib | **54 ms** | 369.5 MB/s |
216
- | `DecompressionStream` | 57 ms | 352.6 MB/s |
217
- | pure-JS zlib | 111 ms | 180.6 MB/s |
218
- | fflate | 128 ms | 156.0 MB/s |
233
+ | WASM zlib | **41 ms** | 483.7 MB/s |
234
+ | `DecompressionStream` | 56 ms | 359.1 MB/s |
235
+ | pure-JS zlib | 108 ms | 185.1 MB/s |
236
+ | fflate | 122 ms | 164.4 MB/s |
219
237
 
220
- The WebAssembly inflate and Node's `DecompressionStream` are within 6 % of each other; fflate's
221
- inflate takes 2.4× the time of the WebAssembly one, the pure-JavaScript port 2.1×.
238
+ The WebAssembly inflate is 27 % faster than Node's `DecompressionStream`; fflate's inflate takes
239
+ 3.0× the time of the WebAssembly one, the pure-JavaScript port 2.6×.
222
240
 
223
- Alone, the WebAssembly backend deflates at level 6 in 1.46× the time of `CompressionStream` for
224
- 0.7 % more bytes and inflates within 6 % of `DecompressionStream`. zip.js uses it by itself
241
+ Alone, the WebAssembly backend deflates at level 6 in 1.47× the time of `CompressionStream` for
242
+ 0.7 % more bytes and inflates 27 % faster than `DecompressionStream`. zip.js uses it by itself
225
243
  when no `CompressionStream` exists or when a level other than the default is requested;
226
244
  `useCompressionStream: false` selects it on every host, for an output that does not depend on
227
245
  the host's zlib.
@@ -234,22 +252,28 @@ that buys with each backend.
234
252
 
235
253
  | Configuration | Median time | Output | vs jszip |
236
254
  |---|--:|--:|--:|
237
- | **zip.js — `CompressionStream`, concurrent `add()`** | **676 ms** | 19.6 MB | **8.6×** |
238
- | fflate — async (its own worker pool) | 768 ms | 20.0 MB | 7.5× |
239
- | archiver — Node zlib | 2324 ms | 19.6 MB | 2.5× |
240
- | zip.js — `CompressionStream`, sequential `add()` | 2421 ms | 19.6 MB | 2.4× |
241
- | fflate — `zipSync` (single thread) | 3165 ms | 20.0 MB | 1.8× |
242
- | zip.js — WASM zlib, sequential `add()` | 3553 ms | 19.7 MB | 1.6× |
243
- | zip.js — WASM zlib, concurrent `add()` | 3577 ms | 19.7 MB | 1.6× |
244
- | zip.js — pure-JS zlib, concurrent `add()` | 3954 ms | 19.7 MB | 1.5× |
245
- | zip.js — pure-JS zlib, sequential `add()` | 4008 ms | 19.7 MB | 1.4× |
246
- | jszip (pako, single thread) | 5788 ms | 19.7 MB | 1.0× |
247
-
248
- With the host's `CompressionStream`, zip.js goes from 2421 ms with sequential `add()` calls to
249
- 676 ms with concurrent ones, 3.6×, without Web Workers: Node runs `CompressionStream` off the
255
+ | node:zlib — `ZipEntry.create()` for every entry at once | **596 ms** | 19.6 MB | **9.6×** |
256
+ | **zip.js — `CompressionStream`, concurrent `add()`** | **602 ms** | 19.6 MB | **9.5×** |
257
+ | fflate — async (its own worker pool) | 757 ms | 20.0 MB | 7.6× |
258
+ | node:zlib — `ZipEntry.create()` one entry at a time | 2209 ms | 19.6 MB | 2.6× |
259
+ | zip.js — `CompressionStream`, sequential `add()` | 2257 ms | 19.6 MB | 2.5× |
260
+ | archiver — Node zlib | 2278 ms | 19.6 MB | 2.5× |
261
+ | fflate — `zipSync` (single thread) | 3191 ms | 20.0 MB | 1.8× |
262
+ | zip.js — WASM zlib, sequential `add()` | 3367 ms | 19.7 MB | 1.7× |
263
+ | zip.js — WASM zlib, concurrent `add()` | 3402 ms | 19.7 MB | 1.7× |
264
+ | zip.js — pure-JS zlib, sequential `add()` | 3868 ms | 19.7 MB | 1.5× |
265
+ | zip.js — pure-JS zlib, concurrent `add()` | 3930 ms | 19.7 MB | 1.5× |
266
+ | jszip (pako, single thread) | 5719 ms | 19.7 MB | 1.0× |
267
+
268
+ With the host's `CompressionStream`, zip.js goes from 2257 ms with sequential `add()` calls to
269
+ 602 ms with concurrent ones, 3.7×, without Web Workers: Node runs `CompressionStream` off the
250
270
  main thread, so the entries compress on several cores while the JavaScript thread only feeds
251
- them. fflate's async API, which runs its own worker pool, takes 768 ms on the same input; its
252
- output is 2 % larger than the zlib rows, so it does slightly less work. The WebAssembly and
271
+ them. The ZIP API of `node:zlib` gets the same 3.7× from the same threadpool when its
272
+ `ZipEntry.create()` calls are issued at once, 2209 to 596 ms, the same time as zip.js within
273
+ noise; the difference is in memory, not time: `node:zlib` holds every compressed entry until
274
+ `createZipArchive()` serializes them, while zip.js writes each entry as its codec finishes.
275
+ fflate's async API, which runs its own worker pool, takes 757 ms on the same input; its output
276
+ is 2 % larger than the zlib rows, so it does slightly less work. The WebAssembly and
253
277
  pure-JavaScript backends run on the main thread, and concurrent `add()` changes nothing for them.
254
278
 
255
279
  Requesting a non-default level, e.g. `{ level: 5 }`, selects the bundled codec instead of
@@ -269,22 +293,23 @@ JavaScript thread. Same 8 × 8 MB workload as the backends table, level 6. That
269
293
  process per run and this one measures in-process after a warmup, which is why their Node numbers
270
294
  differ:
271
295
 
272
- | Runtime | sequential `add()` | concurrent `add()` | concurrent `add()`, `chunkSize` 256 KB | concurrent `add()` + `useWebWorkers` |
296
+ | Runtime | sequential `add()` | concurrent `add()` | concurrent `add()`, `chunkSize` 64 KB | concurrent `add()` + `useWebWorkers` |
273
297
  |---|--:|--:|--:|--:|
274
- | Node.js v26.7.0 | 2.25 s | **0.59 s** | 0.59 s | 0.59 s |
275
- | Bun 1.4.2 | 1.19 s | 1.19 s | **0.24 s** | 0.25 s |
276
- | Deno 2.9.6 | 1.31 s | 1.31 s | 1.33 s | **0.34 s** |
298
+ | Node.js v26.10.0 | 2.25 s | **0.62 s** | 0.59 s | 0.59 s |
299
+ | Bun 1.4.2 | 1.19 s | **0.26 s** | 1.20 s | 0.27 s |
300
+ | Deno 2.9.7 | 1.35 s | 1.35 s | 1.39 s | **0.37 s** |
277
301
 
278
302
  - **Node** runs `CompressionStream` off the JavaScript thread, on the libuv threadpool, four
279
- threads by default (`UV_THREADPOOL_SIZE`): concurrent `add()` alone is 3.8× faster than
303
+ threads by default (`UV_THREADPOOL_SIZE`): concurrent `add()` alone is 3.6× faster than
280
304
  sequential, and the chunk size changes nothing. Node has no `Worker` global, so
281
305
  `useWebWorkers` spawns nothing there and the entries run in-process, in the same time.
282
306
  - **Bun** runs `CompressionStream` off the JavaScript thread only for writes larger than 128 KB
283
- (its native implementation, since Bun 1.4 in August 2026). zip.js writes 64 KB chunks by
284
- default, so concurrent `add()` alone gains nothing on Bun; `chunkSize: 256 * 1024` makes it
285
- 4.9× faster, and `useWebWorkers: true` 4.7×.
307
+ (its native implementation, since Bun 1.4 in August 2026). With the 256 KB chunks zip.js
308
+ writes by default, concurrent `add()` alone is 4.5× faster than sequential; with the 64 KB
309
+ chunks that were the default up to version 2.18.2 it gains nothing, and `useWebWorkers: true`
310
+ is 4.5× faster too.
286
311
  - **Deno** runs `CompressionStream` on the JavaScript thread whatever the write size: concurrent
287
- `add()` alone gains nothing, `useWebWorkers: true` is 3.9× faster.
312
+ `add()` alone gains nothing, `useWebWorkers: true` is 3.6× faster.
288
313
 
289
314
  ### Compression levels
290
315
 
@@ -294,13 +319,13 @@ column runs the same JavaScript on each engine. One 20 MB text entry, one thread
294
319
 
295
320
  | Runtime | level 6, `CompressionStream` | level 5, WASM zlib | level 5, pure-JS zlib | level 1, WASM zlib |
296
321
  |---|--:|--:|--:|--:|
297
- | Node.js v26.7.0 | 686 ms / 6.12 MB | 521 ms / 6.56 MB | 780 ms / 6.56 MB | 192 ms / 7.53 MB |
298
- | Bun 1.4.2 | 371 ms / 6.20 MB | 464 ms / 6.56 MB | 665 ms / 6.56 MB | 168 ms / 7.53 MB |
299
- | Deno 2.9.6 | 403 ms / 6.21 MB | 590 ms / 6.56 MB | 689 ms / 6.56 MB | 228 ms / 7.53 MB |
322
+ | Node.js v26.10.0 | 691 ms / 6.12 MB | 512 ms / 6.56 MB | 774 ms / 6.56 MB | 186 ms / 7.53 MB |
323
+ | Bun 1.4.2 | 430 ms / 6.20 MB | 465 ms / 6.56 MB | 665 ms / 6.56 MB | 166 ms / 7.53 MB |
324
+ | Deno 2.9.7 | 409 ms / 6.20 MB | 592 ms / 6.56 MB | 682 ms / 6.56 MB | 236 ms / 7.53 MB |
300
325
 
301
- On Node, level 5 on the WebAssembly codec is 24 % faster than the default for 7 % more bytes.
326
+ On Node, level 5 on the WebAssembly codec is 26 % faster than the default for 7 % more bytes.
302
327
  On Bun and Deno the default is already faster than level 5, so level 5 there is slower and
303
- larger; only level 1 buys speed on them, 55 % on Bun and 44 % on Deno, at 21 % more bytes. The
328
+ larger; only level 1 buys speed on them, 61 % on Bun and 42 % on Deno, at 21 % more bytes. The
304
329
  pure-JavaScript port at level 5 takes 1.5× the WebAssembly time on Node, 1.4× on Bun and 1.2×
305
330
  on Deno, and is slower than the default on every runtime. The two bundled codecs produce the
306
331
  same size on every runtime; the level-6 sizes differ because they come from three different
@@ -310,17 +335,17 @@ zlib builds.
310
335
 
311
336
  On bulk data zip.js adds almost nothing over the host's `CompressionStream`, so its throughput is
312
337
  that of the zlib the runtime ships, and they differ. One 256 MB text file, one thread, in
313
- memory, fed in 64 KB writes as zip.js does; gzip is Apple's, timed as a child process:
338
+ memory, fed in 256 KB writes as zip.js does; gzip is Apple's, timed as a child process:
314
339
 
315
340
  | Runtime | `CompressionStream` alone | zip.js, one entry | Output |
316
341
  |---|--:|--:|--:|
317
- | Node.js v26.7.0 | 8.99 s | 8.78 s | 78.3 MB |
318
- | Bun 1.4.2 | 4.73 s | 4.77 s | 79.4 MB |
319
- | Deno 2.9.6 | 5.13 s | 5.23 s | 79.5 MB |
320
- | Apple `gzip -6`, classic zlib | 12.3 to 12.4 s | | 78.9 MB |
342
+ | Node.js v26.10.0 | 9.06 s | 9.10 s | 78.3 MB |
343
+ | Bun 1.4.2 | 4.74 s | 4.85 s | 79.4 MB |
344
+ | Deno 2.9.7 | 5.22 s | 5.21 s | 79.4 MB |
345
+ | Apple `gzip -6`, classic zlib | 12.4 to 12.5 s | | 78.9 MB |
321
346
 
322
- zip.js is within 2 % of the raw stream on every runtime. On this file Bun's `CompressionStream`
323
- takes 53 % of Node's time and Deno's 57 %, for about 1.5 % more bytes; Apple's gzip takes 1.4×
347
+ zip.js is within 3 % of the raw stream on every runtime. On this file Bun's `CompressionStream`
348
+ takes 52 % of Node's time and Deno's 58 %, for about 1.4 % more bytes; Apple's gzip takes 1.4×
324
349
  Node's time. Which zlib each runtime ships is not verified here; the table measures the result.
325
350
 
326
351
  ## Encryption: AES-256 on a stored entry
@@ -334,29 +359,29 @@ one archive written, then read back.
334
359
 
335
360
  | Engine | Node.js | Bun | Deno |
336
361
  |---|--:|--:|--:|
337
- | WebAssembly, linked into the module of the WebAssembly builds | **131 / 130 MB/s** | **118 / 121** | 123 / 122 |
338
- | JavaScript, the fallback of those builds and the engine of the native and core builds | 114 / 114 | 112 / 111 | **142 / 129** |
339
- | 2.13.1, sjcl | 25 / 25 | 27 / 27 | 19 / 19 |
362
+ | WebAssembly, linked into the module of the WebAssembly builds | **134 / 135 MB/s** | **119 / 121** | 124 / 124 |
363
+ | JavaScript, the fallback of those builds and the engine of the native and core builds | 119 / 108 | 115 / 117 | **144 / 133** |
364
+ | 2.13.1, sjcl (measured on 2026-09-26, Node.js v26.7.0) | 26 / 26 | 28 / 28 | 20 / 19 |
340
365
 
341
366
  The same measurement in a browser, through the Web Worker pool, on 32 MB of bytes generated in
342
367
  the page (the corpus is not served to the browser), median of 3 passes
343
368
  ([`benchmarks/bench-aes.html`](benchmarks/bench-aes.html), which prints its result and writes no
344
369
  file):
345
370
 
346
- | Engine | Firefox 154 | Chrome 153 |
371
+ | Engine | Firefox 156 | Chrome 153 |
347
372
  |---|--:|--:|
348
- | WebAssembly | **96 / 101 MB/s** | **118 / 121** |
349
- | JavaScript | 68 / 69 | 100 / 98 |
373
+ | WebAssembly | **125 / 125 MB/s** | **136 / 138** |
374
+ | JavaScript | 79 / 79 | 104 / 104 |
350
375
  | 2.13.1, sjcl | 17 / 17 | 22 / 22 |
351
376
 
352
- - The JavaScript engine is 4.0 to 4.6× sjcl on Node, Bun and in the two browsers, and 6.8 to
353
- 7.5× on Deno, where sjcl was slowest. sjcl ran the cipher 16 bytes
377
+ - The JavaScript engine is 4.1 to 4.7× sjcl on Node, Bun and in the two browsers, and 7.0 to
378
+ 7.2× on Deno, where sjcl was slowest. sjcl ran the cipher 16 bytes
354
379
  at a time through a bit-array layer; the new engine works on typed arrays and is fed whole
355
380
  chunks, with the AES rounds and the SHA-1 steps written out. Every build has it.
356
381
  - The WebAssembly kernel is the same C code on every host, with the same rounds written out:
357
- 118 to 131 MB/s on Node, Bun and Deno, 96 to 121 MB/s in the two browsers. The JavaScript
358
- engine is within 13 % of it on Node and Bun, ahead of it on Deno, and 1.2 to 1.4× slower in
359
- the browsers. The WebAssembly builds use
382
+ 119 to 135 MB/s on Node, Bun and Deno, 125 to 138 MB/s in the two browsers. The JavaScript
383
+ engine is within 4 % of it on Bun and 20 % on Node, ahead of it on Deno, and 1.3 to 1.6×
384
+ slower in the browsers. The WebAssembly builds use
360
385
  the kernel whenever their module loads and fall back to the JavaScript engine when it cannot,
361
386
  for instance under a Content Security Policy that does not allow WebAssembly
362
387
  (`'wasm-unsafe-eval'`).
@@ -369,8 +394,8 @@ file):
369
394
  bytes in 2.13.1 to 67,820 in 2.14.0, `dist/zip-native.min.js` from 73,798 to 72,060 and
370
395
  `dist/zip-core.min.js` from 36,405 to 35,493 (`git show <tag>:<file> | gzip -9 | wc -c`):
371
396
  sjcl's removal outweighs the JavaScript engine and the kernel. Writing the rounds out after
372
- 2.14.0 gives some of it back: 71,238, 73,339 and 36,235 bytes for the three files behind the
373
- encryption tables above.
397
+ 2.14.0 gave some of it back, and the three files behind the encryption tables above gzip to
398
+ 73,184, 75,050 and 37,264 bytes.
374
399
 
375
400
  ## Reproduce
376
401
 
@@ -379,7 +404,7 @@ The harness lives in [`benchmarks/`](benchmarks/). `bench.js` and `bench-backend
379
404
 
380
405
  ```sh
381
406
  cd benchmarks
382
- npm install # jszip, fflate, archiver (zip.js is used from the repository)
407
+ npm install # jszip, fflate, archiver (zip.js is used from the repository, node:zlib is built in)
383
408
  npm run corpus # generate the datasets under .corpus/
384
409
  node bench.js # the head-to-head tables: compress, decompress, disk streaming
385
410
  node bench-backends.js # the concurrent add() and backends table
@@ -390,7 +415,10 @@ node bench-runtimes.js # the runtime tables; also: bun bench-runtimes.js, deno
390
415
 
391
416
  Each script writes its JSON to `benchmarks/results/`; the `*-run.log` files there are the
392
417
  console output of the runs behind this page. `RUNS=<n>` sets the number of repetitions (default
393
- 3, 5 in `bench-aes.js`). `ZIPJS_BUNDLE=<path to a previous index.min.js> node bench-aes.js` adds
418
+ 3, 5 in `bench-aes.js`). `QUICK=1` divides every workload by 8, runs each measurement once and
419
+ writes under `benchmarks/results/quick/`; the scripts then finish in seconds instead of minutes,
420
+ which is enough to check that they run and not enough to read a number from.
421
+ `ZIPJS_BUNDLE=<path to a previous index.min.js> node bench-aes.js` adds
394
422
  the row of a previous release; `git show v2.13.1:index.min.js` gave the sjcl row. The browser
395
423
  encryption table comes from `benchmarks/bench-aes.html`, served from the repository root
396
424
  (`npx http-server -p 8080`, then `http://localhost:8080/benchmarks/bench-aes.html`), with
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zip-js/zip-js",
3
- "version": "2.18.2",
3
+ "version": "2.20.0",
4
4
  "exports": {
5
5
  ".": "./index.js",
6
6
  "./external": "./lib/zip-fs-external.js",