Skip to content

Commit 90951ad

Browse files
authored
feat: add support for Zstandard compression algorithm (#693)
* feat: add support for Zstandard compression algorithm * fix: ensure Zstandard compression is only used if supported * feat: update documentation to include Zstandard compression support
1 parent 290913e commit 90951ad

15 files changed

Lines changed: 73 additions & 10 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ _Note: Gaps between patch versions are faulty, broken or test releases._
1212

1313
## UNRELEASED
1414

15+
* **New Feature**
16+
* Add support for Zstandard compression ([#693](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/693) by [@bjohansebas](https://github.com/bjohansebas))
17+
1518
* **Internal**
1619
* Update `sirv` dependency ([#692](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/692) by [@bjohansebas](https://github.com/bjohansebas))
1720
* Update `ws` dependency ([#691](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/691) by [@bjohansebas](https://github.com/bjohansebas))

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ This module will help you:
4545
4. Optimize it!
4646

4747
And the best thing is it supports minified bundles! It parses them to get real size of bundled modules.
48-
And it also shows their gzipped or Brotli sizes!
48+
And it also shows their gzipped, Brotli, or Zstandard sizes!
4949

5050
<h2 align="center">Options (for plugin)</h2>
5151

@@ -62,7 +62,7 @@ new BundleAnalyzerPlugin(options?: object)
6262
|**`reportFilename`**|`{String}`|Default: `report.html`. Path to bundle report file that will be generated in `static` mode. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config).|
6363
|**`reportTitle`**|`{String\|function}`|Default: function that returns pretty printed current date and time. Content of the HTML `title` element; or a function of the form `() => string` that provides the content.|
6464
|**`defaultSizes`**|One of: `stat`, `parsed`, `gzip`, `brotli`|Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.|
65-
|**`compressionAlgorithm`**|One of: `gzip`, `brotli`|Default: `gzip`. Compression type used to calculate the compressed module sizes.|
65+
|**`compressionAlgorithm`**|One of: `gzip`, `brotli`, `zstd`|Default: `gzip`. Compression type used to calculate the compressed module sizes.|
6666
|**`openAnalyzer`**|`{Boolean}`|Default: `true`. Automatically open report in default browser.|
6767
|**`generateStatsFile`**|`{Boolean}`|Default: `false`. If `true`, webpack stats JSON file will be generated in bundle output directory|
6868
|**`statsFilename`**|`{String}`|Default: `stats.json`. Name of webpack stats JSON file that will be generated if `generateStatsFile` is `true`. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config).|
@@ -122,9 +122,9 @@ Directory containing all generated bundles.
122122
-r, --report <file> Path to bundle report file that will be generated in `static` mode. (default: report.html)
123123
-t, --title <title> String to use in title element of html report. (default: pretty printed current date)
124124
-s, --default-sizes <type> Module sizes to show in treemap by default.
125-
Possible values: stat, parsed, gzip, brotli (default: parsed)
125+
Possible values: stat, parsed, gzip, brotli, zstd (default: parsed)
126126
--compression-algorithm <type> Compression algorithm that will be used to calculate the compressed module sizes.
127-
Possible values: gzip, brotli (default: gzip)
127+
Possible values: gzip, brotli, zstd (default: gzip)
128128
-O, --no-open Don't open report in default browser automatically.
129129
-e, --exclude <regexp> Assets that should be excluded from the report.
130130
Can be specified multiple times.
@@ -158,6 +158,10 @@ This is the size of running the parsed bundles/modules through gzip compression.
158158

159159
This is the size of running the parsed bundles/modules through Brotli compression.
160160

161+
### `zstd`
162+
163+
This is the size of running the parsed bundles/modules through Zstandard compression. (Node.js 22.15.0+ is required for this feature)
164+
161165
<h2 align="center">Selecting Which Chunks to Display</h2>
162166

163167
When opened, the report displays all of the Webpack chunks for your project. It's possible to filter to a more specific list of chunks by using the sidebar or the chunk context menu.

client/components/ModulesTreemap.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ function getSizeSwitchItems() {
2929

3030
if (window.compressionAlgorithm === 'brotli') items.push({label: 'Brotli', prop: 'brotliSize'});
3131

32+
if (window.compressionAlgorithm === 'zstd') items.push({label: 'Zstandard', prop: 'zstdSize'});
33+
3234
return items;
3335
};
3436

client/store.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import localStorage from './localStorage';
44

55
export class Store {
66
cid = 0;
7-
sizes = new Set(['statSize', 'parsedSize', 'gzipSize', 'brotliSize']);
7+
sizes = new Set(['statSize', 'parsedSize', 'gzipSize', 'brotliSize', 'zstdSize']);
88

99
@observable.ref allChunks;
1010
@observable.shallow selectedChunks;

src/analyzer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
113113
asset.parsedSize = Buffer.byteLength(assetSources.src);
114114
if (compressionAlgorithm === 'gzip') asset.gzipSize = getCompressedSize('gzip', assetSources.src);
115115
if (compressionAlgorithm === 'brotli') asset.brotliSize = getCompressedSize('brotli', assetSources.src);
116+
if (compressionAlgorithm === 'zstd') asset.zstdSize = getCompressedSize('zstd', assetSources.src);
116117
}
117118

118119
// Picking modules from current bundle script
@@ -169,6 +170,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
169170
parsedSize: asset.parsedSize,
170171
gzipSize: asset.gzipSize,
171172
brotliSize: asset.brotliSize,
173+
zstdSize: asset.zstdSize,
172174
groups: Object.values(asset.tree.children).map(i => i.toChartData()),
173175
isInitialByEntrypoint: chunkToInitialByEntrypoint[filename] ?? {}
174176
}));

src/bin/analyzer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ const analyzer = require('../analyzer');
99
const viewer = require('../viewer');
1010
const Logger = require('../Logger');
1111
const utils = require('../utils');
12+
const {isZstdSupported} = require('../sizeUtils');
1213

1314
const SIZES = new Set(['stat', 'parsed', 'gzip']);
14-
const COMPRESSION_ALGORITHMS = new Set(['gzip', 'brotli']);
15+
const COMPRESSION_ALGORITHMS = new Set(isZstdSupported ? ['gzip', 'brotli', 'zstd'] : ['gzip', 'brotli']);
1516

1617
const program = commander
1718
.version(require('../../package.json').version)

src/sizeUtils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
const zlib = require('zlib');
22

3+
export const isZstdSupported = 'createZstdCompress' in zlib;
4+
35
export function getCompressedSize(compressionAlgorithm, input) {
46
if (compressionAlgorithm === 'gzip') return zlib.gzipSync(input, {level: 9}).length;
57
if (compressionAlgorithm === 'brotli') return zlib.brotliCompressSync(input).length;
8+
if (compressionAlgorithm === 'zstd' && isZstdSupported) {
9+
return zlib.zstdCompressSync(input).length;
10+
}
611

712
throw new Error(`Unsupported compression algorithm: ${compressionAlgorithm}.`);
813
}

src/tree/ConcatenatedModule.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export default class ConcatenatedModule extends Module {
2424
return this.getBrotliSize() ?? this.getEstimatedSize('brotliSize');
2525
}
2626

27+
get zstdSize() {
28+
return this.getZstdSize() ?? this.getEstimatedSize('zstdSize');
29+
}
30+
2731
getEstimatedSize(sizeType) {
2832
const parentModuleSize = this.parent[sizeType];
2933

src/tree/ContentFolder.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export default class ContentFolder extends BaseFolder {
1919
return this.getSize('brotliSize');
2020
}
2121

22+
get zstdSize() {
23+
return this.getSize('zstdSize');
24+
}
25+
2226
getSize(sizeType) {
2327
const ownerModuleSize = this.ownerModule[sizeType];
2428

@@ -33,6 +37,7 @@ export default class ContentFolder extends BaseFolder {
3337
parsedSize: this.parsedSize,
3438
gzipSize: this.gzipSize,
3539
brotliSize: this.brotliSize,
40+
zstdSize: this.zstdSize,
3641
inaccurateSizes: true
3742
};
3843
}

src/tree/ContentModule.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export default class ContentModule extends Module {
1919
return this.getSize('brotliSize');
2020
}
2121

22+
get zstdSize() {
23+
return this.getSize('zstdSize');
24+
}
25+
2226
getSize(sizeType) {
2327
const ownerModuleSize = this.ownerModule[sizeType];
2428

0 commit comments

Comments
 (0)