What's Changed in v2.8.52
New features
- New
getExportedSize()method onZipDirectoryEntryandFSinstances. It returns the exact size of the zip file the matchingexport*()call would write, without writing it. It takes the same options as theexport*()methods, so the value it returns is the one the export produces. It is meant for theContent-Lengthheader of a streamed download. It throws the newERR_UNDETERMINED_SIZEerror when the size cannot be known before writing, i.e. when an entry is compressed, when an entry has no known size, whensignCentralDirectoryis set, and when thebufferedWriteoption lets the physical layout depend on the order in which the entries are written. Encryption does not prevent the prediction, its overhead is a fixed number of bytes - New
onentryprogressoption in theexport*()methods of the filesystem API. It is called once per written entry with the number of entries written, the total number of entries, and the entry itself. It reports the entries whereasonprogressreports the bytes. It is called after the entry has been written. WhenbufferedWriteis enabled the entries are written concurrently, so it counts the entries written instead of giving the position of the entry in the zip file - New
globalCommentoption in theexport*()methods of the filesystem API. It sets the comment of the zip file. The options of these methods are applied to every entry, so settingcommentthere comments each entry instead of the archive, exactly aslastModDatethere sets the date of each entry. The zip file comment therefore needed a name of its own importZip()now accepts aZipReaderinstance in addition to the data of a zip file. The caller builds the reader, passes it, and keeps it after the import. This is the way to readprependedData,appendedData,comment,digitalSignature,directoryOffsetanddirectoryLength, which are only filled once the entries have been read. The options of the reader are merged with the options of the import, and the options of the import win- New
symlinkproperty on entries. It istruewhen the entry is a symbolic link. The target of the link is the content of the entry, which is read like any other entry, e.g. withgetData(new TextWriter()). The target is not validated, it can be an absolute path or escape the directory of the entry, so it must be checked before being used. Writing a symbolic link is done by setting theunixModeoption to a mode carrying theS_IFLNKtype, e.g.0o120777, with the path of the target as content exportFileSystemHandle()now accepts thereaderOptionsoption. Thepasswordoption must be set there to export the entries of an encrypted zip file, since thepasswordoption of the export encrypts the written entries instead- The
readerOptionsoption of the filesystem export now acceptspassThrough. The entries imported from a zip file are then written as-is, without being decompressed and decrypted, exactly as importing them with this option does. The entries added to the filesystem are compressed as usual - The
signCentralDirectoryoption is now declared in the export options of the filesystem API. It was already forwarded to theZipWriterinstance, it was simply missing from the TypeScript definitions - New
ERR_UNDETERMINED_SIZE,ERR_INVALID_PASS_THROUGHandERR_UNSUPPORTED_ENCRYPTION_PASS_THROUGHerror constants
Behavior changes
- The
export*()methods of the filesystem API now report the progress of the whole archive instead of the progress of each entry.onstartandonendare called once, with the total size of the entries and with the number of bytes written. They used to be called once per entry, whileonprogresswas already reporting the archive as a whole, so the three callbacks disagreed with each other.onprogressis unchanged. Use the newonentryprogressoption to be notified for each entry - The export options of the filesystem API now take precedence over the metadata of the entries imported from a zip file. Setting
lastModDate,comment,versionMadeBy,uid,gidor the file attributes in the export options used to have no effect on those entries, althoughunixModeandmsdosAttributesdid reach them, so the same attribute word was writable one way and not the other. The order is now the metadata of the source entry, then the export options, then the description of the entries copied as-is, then the options of the entry. The description of the entries copied as-is stays above the export options because it describes the bytes being copied, not an intent. Alevelor acompressionMethodwinning over it would write headers that do not match the content. An export using the default options is unchanged, so round trips keep their fidelity ZipReader#close()now cancels theReadableStreaminstance passed to the constructor when nothing has been read from it. It used to do nothing at all. The stream of a reader whose entries have been read is left alone, and so is aReaderinstance, which belongs to the caller. The entries stay readable after the call- The
preventCloseoption is now honored only when the caller owns the writable, i.e. when aWritableWriterinstance is passed toexportZip()orexportWritable(). It is ignored by the otherexport*()methods of the filesystem API, whose Writer instance can only return its data once its writable is closed. Setting it there used to prevent the export from ever resolving - Writing an entry with the
passThroughoption and a password now throwsERR_UNSUPPORTED_ENCRYPTION_PASS_THROUGH. The data is copied as-is, so it cannot be encrypted. The password used to be ignored silently and the entry was written unencrypted although its header announced encryption - The
passThroughoption is now ignored for the entries with no content.add("dir/", undefined, { directory: true, passThrough: true })used to throwERR_UNDEFINED_READER, so aZipWriterinstance created withpassThroughset totruecould not write a single directory - Writing an entry with the
unixModeoption now stamps the type of the file in the external file attributes. zip.js used to write a mode with no type, e.g.0o000644where Info-ZIP writes0o100644, sounzip -llisted the entry as?rw-r--r--. A mode already carrying a type is left untouched, and theexternalFileAttributesoption still writes the value verbatim - The
executableproperty of an entry is nowfalsefor symbolic links. The permission bits of a link are always0o777, so the flag was meaningless on every link
Bug fixes
- Reading a range of data no longer emits empty chunks.
Reader#createReadable()enqueued an empty chunk at the end of every entry whose size is known, and one extra read was issued for an entry of unknown size. The data was correct, the stream simply contained a chunk of length 0 getBlob()andgetData64URI()now honor the MIME type they are given. A filesystem entry holding aBlobinstance returned it as-is, with the MIME type it was created with, and ignored the requested one- The filesystem API now runs the overlapping entry check when
checkOverlappingEntryOnlyis set in its reader options. The option means "run the check and stop before reading the content" in the core API. The filesystem API needs the content, so it used to drop the option and skip the check altogether. It is now mapped tocheckOverlappingEntry, so the check runs and the content is still read - The reader options that withhold the content of an entry are now ignored by the filesystem API.
checkPasswordOnly,checkOverlappingEntryOnlyandpreventCloseused to be forwarded to the entries read from a zip file, which returned no data - Progress is now reported for the filesystem entries that were not imported from a zip file. Reading such an entry pipes the reader to the writer without going through a codec, so
onprogressnever fired. It is now reported for every entry exportFileSystemHandle()now counts the bytes it writes. It used to report the compressed size of each entry against the uncompressed size of the archive, so the progress stopped around 1% of the total, and it reported nothing at all for the entries that were not imported from a zip fileaddFileSystemHandle()now applies the entry options to the directories it creates. They were applied to the files only- The dates of the Info-ZIP unix extra field are now read as signed timestamps. A date before 1970 was read as a date in 2106
- Writing an entry whose name ends with a slash no longer corrupts the
unixModeoption. The type of the file was combined with the type already present in the mode instead of replacing it, so0o100644became a socket and0o120777became an invalid type. Both were then read back as neither a directory nor a symbolic link - The deprecated
externalFileAttributeandinternalFileAttributeoptions work again. They were removed when they were renamed toexternalFileAttributesandinternalFileAttributes. They are back as deprecated aliases, and an option passed toadd()now takes precedence over the option of theZipWriterinstance whatever the spelling of each addText()now stores the size of the text in bytes instead of its number of UTF-16 code units. OnlygetExportedSize()read that value, so the size was under-reported for any text containing non-ASCII characters. The export itself was correct- The buffer reserved for the expansion of the deflate algorithm is now allocated only for the entries that are really compressed. A stored entry reserved the same margin as a deflated one
Performance
- The
export*()methods of the filesystem API now keep a running total instead of summing the progress of every entry on every tick. The cost was quadratic in the number of entries. Passingonprogresson an archive of 10,000 entries added 50% to the duration of the export, and now adds nothing measurable
Documentation
- The
useCompressionStreamoption now states that the native API is used for compression only whenlevelis undefined or equal to 6.CompressionStreamdoes not support compression levels, so any other value compresses the data with the embedded implementation. It also states that the data produced at a given level can vary between platforms, and thatuseCompressionStreammust be set tofalseto get the same output everywhere - The
uidandgidoptions now state which unix extra field carries them ZipReader#close()and thecreateTempStreamoption now describe what they do. A temporary stream must be able to hold a whole entry, because the local header written before it holds the size and the CRC-32 of the entry. Its readable side is therefore consumed only once its writable side has been closed. A factory returningnew TransformStream()deadlocks, whereas the default buffers everything
Tests and continuous integration
- The browser test runner uses Selenium instead of Playwright. It accepts
--exe-path,--url-search,--build,--headfuland--help, and thetest-webkitscript was renamed totest-safari - A workflow runs the test suite on every push, including the native build
- A workflow checks that the markdown documentation is up to date with
index.d.ts - The API documentation is published to the GitHub Pages site on each release
- The web runner accepts a
maxParallelTestsparameter. Chromium 87 loses the wake-up of the backpressure of a stream when 16 tests run in parallel, so its jobs cap the parallelism at 4 - The tests that check the abort reason are skipped on the browsers that ignore the signal of
pipeTo()instead of being reported as failures
Credits
- @danny0838 contributed the switch to Selenium, the test workflow and the arguments of the browser runner (#673)
- Claude (Opus 5) contributed to every change listed above
Full Changelog: v2.8.51...v2.8.52