-
Notifications
You must be signed in to change notification settings - Fork 559
build(bundle-size-tools): remove dependency on JSZip #25974
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: tylerbutler <[email protected]>
Co-authored-by: tylerbutler <[email protected]>
…endencies' into copilot/reduce-build-tools-dependencies
build-tools/packages/bundle-size-tools/src/ADO/AdoArtifactFileProvider.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors zip file handling in the build tools by replacing the jszip library with the more lightweight fflate library. The changes introduce a new UnzippedContents type abstraction and implement lazy decompression to improve memory efficiency.
Key Changes:
- Replaced
jszipwithfflatefor zip file handling and introduced aLazyUnzippedContentsclass that decompresses files only when accessed - Updated all function signatures throughout the codebase to use the new
UnzippedContentstype (aMap<string, Buffer>) instead ofJSZip - Simplified iteration patterns from
forEachcallbacks tofor...ofloops where applicable
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| build-tools/pnpm-lock.yaml | Removed jszip dependency entries from lock file |
| build-tools/packages/bundle-size-tools/src/utilities/unzipStream.ts | Implemented LazyUnzippedContents class and updated unzipStream function to use fflate |
| build-tools/packages/bundle-size-tools/src/utilities/index.ts | Exported new UnzippedContents type |
| build-tools/packages/bundle-size-tools/src/index.ts | Re-exported UnzippedContents type for public API |
| build-tools/packages/bundle-size-tools/src/ADO/AdoSizeComparator.ts | Updated type signature to use UnzippedContents |
| build-tools/packages/bundle-size-tools/src/ADO/AdoArtifactFileProvider.ts | Updated function signatures and implementations to work with UnzippedContents instead of JSZip |
| build-tools/packages/bundle-size-tools/package.json | Removed jszip dependency |
| build-tools/packages/bundle-size-tools/api-report/bundle-size-tools.api.md | Updated API surface to remove JSZip references and add UnzippedContents |
| build-tools/packages/build-cli/src/library/azureDevops/getBaselineBuildMetrics.ts | Updated type references from JSZip to UnzippedContents |
| build-tools/packages/build-cli/src/codeCoverage/getCoverageMetrics.ts | Updated to use UnzippedContents and simplified iteration from forEach to for...of |
| build-tools/packages/build-cli/package.json | Removed jszip dependency |
Files not reviewed (1)
- build-tools/pnpm-lock.yaml: Language not supported
|
|
||
| override get size(): number { | ||
| return this.fileKeys.size; | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LazyUnzippedContents class doesn't override the clear(), delete(), and set() methods from Map. While set() is inherited and works, clear() and delete() could cause inconsistencies between the fileKeys Set and the parent Map's cached entries. Consider either:
- Overriding these methods to maintain consistency between
fileKeysand the parent Map - Making the class read-only by throwing errors in these methods if mutation is not intended
For example:
override delete(key: string): boolean {
this.fileKeys.delete(key);
return super.delete(key);
}
override clear(): void {
this.fileKeys.clear();
super.clear();
}| } | |
| } | |
| return this.fileKeys.size; | |
| } | |
| override delete(key: string): boolean { | |
| this.fileKeys.delete(key); | |
| return super.delete(key); | |
| } | |
| override clear(): void { | |
| this.fileKeys.clear(); | |
| super.clear(); | |
| } |
|
|
||
| /** | ||
| * Downloads an Azure Devops artifacts and parses it with the jszip library. | ||
| * Downloads an Azure Devops artifacts and unzips it. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSDoc has a typo: "Downloads an Azure Devops artifacts" should be "Downloads an Azure DevOps artifact" (singular, and correct capitalization).
| * Downloads an Azure Devops artifacts and unzips it. | |
| * Downloads an Azure DevOps artifact and unzips it. |
| import { type Unzipped, unzip } from "fflate"; | ||
|
|
||
| /** | ||
| * Lazy wrapper around unzipped archive that decompresses files on access. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Consider adding JSDoc documentation for the LazyUnzippedContents class. Since this is an internal implementation detail that implements lazy decompression (a key performance feature mentioned in the PR description), documenting its purpose and behavior would be helpful for future maintainers.
| * Lazy wrapper around unzipped archive that decompresses files on access. | |
| * Represents a lazily-decompressed view of a ZIP archive's contents. | |
| * | |
| * This class wraps the result of `fflate.unzip` and exposes a `Map<string, Buffer>`-like interface, | |
| * where each file's contents are only decompressed from the underlying archive on first access. | |
| * This reduces memory usage and improves performance when working with large archives or when | |
| * only a subset of files are needed. | |
| * | |
| * The class overrides several `Map` methods (`get`, `has`, `keys`, `entries`, `values`, `size`) | |
| * to provide lazy decompression and to filter files by an optional path prefix. | |
| * | |
| * @internal | |
| * @extends Map<string, Buffer> | |
| * | |
| * @param unzipped - The raw unzipped archive object from `fflate.unzip`, mapping file paths to Uint8Array contents. | |
| * @param prefix - Optional path prefix to filter files and strip from returned keys. | |
| * | |
| * Example usage: | |
| * const contents = new LazyUnzippedContents(unzipped, "src/"); | |
| * const fileBuffer = contents.get("index.js"); // Decompresses "src/index.js" on first access |
|
|
||
| override get size(): number { | ||
| return this.fileKeys.size; | ||
| } |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LazyUnzippedContents class extends Map<string, Buffer> but doesn't override the forEach method. This means that if someone calls forEach on an instance, it will use the parent Map's implementation which will iterate over the cached entries in the parent Map, potentially missing entries that haven't been lazily loaded yet. Consider adding an override:
override forEach(callbackfn: (value: Buffer, key: string, map: Map<string, Buffer>) => void, thisArg?: any): void {
for (const key of this.fileKeys) {
callbackfn.call(thisArg, this.get(key)!, key, this);
}
}| } | |
| } | |
| override forEach( | |
| callbackfn: (value: Buffer, key: string, map: Map<string, Buffer>) => void, | |
| thisArg?: any, | |
| ): void { | |
| for (const key of this.fileKeys) { | |
| callbackfn.call(thisArg, this.get(key)!, key, this); | |
| } | |
| } |
alexvy86
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I find it confusing that what used to be a zip file, with variables and functions named accordingly, now is an UnzippedContents in many places, still using variable and functions that suggest it's a zip file.
| // Decompress on first access | ||
| const fullPath = this.prefix ? `${this.prefix}${key}` : key; | ||
| const buffer = Buffer.from(this.unzipped[fullPath]); | ||
| super.set(key, buffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really "decompressing on access", is it? Feels like the constructor already receives the unzipped contents and this is just wrapping them somehow. I'm pretty skeptical about this class, and in general my gut feeling is that for anything in build-tools sync patterns are almost always better for simplicity and ease of use, because few places can really leverage async-ness much, and in most cases the gains are negligible in context (saving a few ms-to-seconds when running a tool in the pipeline).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The challenge imo is not the sync/async but the need to not load the whole thing into memory. I worry that our files in ci are too large to decompress everything in memory.
I guess this is why we might stick with jszip. It's just yet another decompression lib to maintain and understand.
|
Marking as a draft while I write some tests separately so we can be more confident in this change. |
This pull request refactors how zip file handling is performed in the build tools, replacing the
jsziplibrary with the more lightweightfflatelibrary and introducing a newUnzippedContentsabstraction. The changes improve memory efficiency by lazily decompressing files only when accessed, simplify APIs, and update all related code and types to use the new approach. The most important changes are grouped below.Zip Handling Refactor:
jszipdependency withfflatefor unzipping files, and removed all usage and type references tojszipthroughout the codebase.UnzippedContentstype (aMap<string, Buffer>) and implemented aLazyUnzippedContentsclass that lazily decompresses files on access, improving performance and memory usage.API and Type Updates:
UnzippedContentsinstead ofJSZip, includinggetZipObjectFromArtifact,getStatsFileFromZip,getBundleBuddyConfigFileFromZip, andgetBundlePathsFromZipObject.Code Coverage and Azure DevOps Integration:
UnzippedContentsabstraction, including updating logic for iterating over files and reading contents.