Skip to content

Commit c7b4881

Browse files
committed
Implement Brotli compression
1 parent 033354d commit c7b4881

7 files changed

Lines changed: 91 additions & 25 deletions

File tree

src/analyzer.js

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

4-
const gzipSize = require('gzip-size');
54
const {parseChunked} = require('@discoveryjs/json-ext');
65

76
const Logger = require('./Logger');
87
const Folder = require('./tree/Folder').default;
98
const {parseBundle} = require('./parseUtils');
109
const {createAssetsFilter} = require('./utils');
10+
const {getCompressedSize} = require('./sizeUtils');
1111

1212
const FILENAME_QUERY_REGEXP = /\?.*$/u;
1313
const FILENAME_EXTENSIONS = /\.(js|mjs|cjs)$/iu;
@@ -20,6 +20,7 @@ module.exports = {
2020
function getViewerData(bundleStats, bundleDir, opts) {
2121
const {
2222
logger = new Logger(),
23+
compressionAlgorithm,
2324
excludeAssets = null
2425
} = opts || {};
2526

@@ -110,7 +111,8 @@ function getViewerData(bundleStats, bundleDir, opts) {
110111

111112
if (assetSources) {
112113
asset.parsedSize = Buffer.byteLength(assetSources.src);
113-
asset.gzipSize = gzipSize.sync(assetSources.src);
114+
if (compressionAlgorithm === 'gzip') asset.gzipSize = getCompressedSize('gzip', assetSources.src);
115+
if (compressionAlgorithm === 'brotli') asset.brotliSize = getCompressedSize('brotli', assetSources.src);
114116
}
115117

116118
// Picking modules from current bundle script
@@ -151,7 +153,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
151153
}
152154

153155
asset.modules = assetModules;
154-
asset.tree = createModulesTree(asset.modules);
156+
asset.tree = createModulesTree(asset.modules, {compressionAlgorithm});
155157
return result;
156158
}, {});
157159

@@ -166,6 +168,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
166168
statSize: asset.tree.size || asset.size,
167169
parsedSize: asset.parsedSize,
168170
gzipSize: asset.gzipSize,
171+
brotliSize: asset.brotliSize,
169172
groups: Object.values(asset.tree.children).map(i => i.toChartData()),
170173
isInitialByEntrypoint: chunkToInitialByEntrypoint[filename] ?? {}
171174
}));
@@ -220,8 +223,8 @@ function isRuntimeModule(statModule) {
220223
return statModule.moduleType === 'runtime';
221224
}
222225

223-
function createModulesTree(modules) {
224-
const root = new Folder('.');
226+
function createModulesTree(modules, opts) {
227+
const root = new Folder('.', opts);
225228

226229
modules.forEach(module => root.addModule(module));
227230
root.mergeNestedFolders();

src/tree/ConcatenatedModule.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {getModulePathParts} from './utils';
55

66
export default class ConcatenatedModule extends Module {
77

8-
constructor(name, data, parent) {
9-
super(name, data, parent);
8+
constructor(name, data, parent, opts) {
9+
super(name, data, parent, opts);
1010
this.name += ' (concatenated)';
1111
this.children = Object.create(null);
1212
this.fillContentModules();
@@ -20,6 +20,10 @@ export default class ConcatenatedModule extends Module {
2020
return this.getGzipSize() ?? this.getEstimatedSize('gzipSize');
2121
}
2222

23+
get brotliSize() {
24+
return this.getBrotliSize() ?? this.getEstimatedSize('brotliSize');
25+
}
26+
2327
getEstimatedSize(sizeType) {
2428
const parentModuleSize = this.parent[sizeType];
2529

@@ -53,7 +57,7 @@ export default class ConcatenatedModule extends Module {
5357
});
5458

5559
const ModuleConstructor = moduleData.modules ? ConcatenatedModule : ContentModule;
56-
const module = new ModuleConstructor(fileName, moduleData, this);
60+
const module = new ModuleConstructor(fileName, moduleData, this, this.opts);
5761
currentFolder.addChildModule(module);
5862
}
5963

src/tree/ContentFolder.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export default class ContentFolder extends BaseFolder {
1515
return this.getSize('gzipSize');
1616
}
1717

18+
get brotliSize() {
19+
return this.getSize('brotliSize');
20+
}
21+
1822
getSize(sizeType) {
1923
const ownerModuleSize = this.ownerModule[sizeType];
2024

@@ -28,6 +32,7 @@ export default class ContentFolder extends BaseFolder {
2832
...super.toChartData(),
2933
parsedSize: this.parsedSize,
3034
gzipSize: this.gzipSize,
35+
brotliSize: this.brotliSize,
3136
inaccurateSizes: true
3237
};
3338
}

src/tree/ContentModule.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export default class ContentModule extends Module {
1515
return this.getSize('gzipSize');
1616
}
1717

18+
get brotliSize() {
19+
return this.getSize('brotliSize');
20+
}
21+
1822
getSize(sizeType) {
1923
const ownerModuleSize = this.ownerModule[sizeType];
2024

src/tree/Folder.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
1-
import gzipSize from 'gzip-size';
2-
31
import Module from './Module';
42
import BaseFolder from './BaseFolder';
53
import ConcatenatedModule from './ConcatenatedModule';
64
import {getModulePathParts} from './utils';
5+
import {getCompressedSize} from '../sizeUtils';
76

87
export default class Folder extends BaseFolder {
98

9+
constructor(name, opts) {
10+
super(name);
11+
this.opts = opts;
12+
}
13+
1014
get parsedSize() {
1115
return this.src ? this.src.length : 0;
1216
}
1317

1418
get gzipSize() {
15-
if (!Object.prototype.hasOwnProperty.call(this, '_gzipSize')) {
16-
this._gzipSize = this.src ? gzipSize.sync(this.src) : 0;
19+
return this.opts.compressionAlgorithm === 'gzip' ? this.getCompressedSize('gzip') : undefined;
20+
}
21+
22+
get brotliSize() {
23+
return this.opts.compressionAlgorithm === 'brotli' ? this.getCompressedSize('brotli') : undefined;
24+
}
25+
26+
getCompressedSize(compressionAlgorithm) {
27+
const key = `_${compressionAlgorithm}Size`;
28+
29+
if (!Object.prototype.hasOwnProperty.call(this, key)) {
30+
this[key] = this.src ? getCompressedSize(compressionAlgorithm, this.src) : 0;
1731
}
1832

19-
return this._gzipSize;
33+
return this[key];
2034
}
2135

2236
addModule(moduleData) {
@@ -41,22 +55,23 @@ export default class Folder extends BaseFolder {
4155
// See `test/stats/with-invalid-dynamic-require.json` as an example.
4256
!(childNode instanceof Folder)
4357
) {
44-
childNode = currentFolder.addChildFolder(new Folder(folderName));
58+
childNode = currentFolder.addChildFolder(new Folder(folderName, this.opts));
4559
}
4660

4761
currentFolder = childNode;
4862
});
4963

5064
const ModuleConstructor = moduleData.modules ? ConcatenatedModule : Module;
51-
const module = new ModuleConstructor(fileName, moduleData, this);
65+
const module = new ModuleConstructor(fileName, moduleData, this, this.opts);
5266
currentFolder.addChildModule(module);
5367
}
5468

5569
toChartData() {
5670
return {
5771
...super.toChartData(),
5872
parsedSize: this.parsedSize,
59-
gzipSize: this.gzipSize
73+
gzipSize: this.gzipSize,
74+
brotliSize: this.brotliSize
6075
};
6176
}
6277

src/tree/Module.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import gzipSize from 'gzip-size';
2-
31
import Node from './Node';
2+
import {getCompressedSize} from '../sizeUtils';
43

54
export default class Module extends Node {
65

7-
constructor(name, data, parent) {
6+
constructor(name, data, parent, opts) {
87
super(name, parent);
98
this.data = data;
9+
this.opts = opts;
1010
}
1111

1212
get src() {
@@ -16,6 +16,7 @@ export default class Module extends Node {
1616
set src(value) {
1717
this.data.parsedSrc = value;
1818
delete this._gzipSize;
19+
delete this._brotliSize;
1920
}
2021

2122
get size() {
@@ -34,16 +35,29 @@ export default class Module extends Node {
3435
return this.getGzipSize();
3536
}
3637

38+
get brotliSize() {
39+
return this.getBrotliSize();
40+
}
41+
3742
getParsedSize() {
3843
return this.src ? this.src.length : undefined;
3944
}
4045

4146
getGzipSize() {
42-
if (!('_gzipSize' in this)) {
43-
this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined;
47+
return this.opts.compressionAlgorithm === 'gzip' ? this.getCompressedSize('gzip') : undefined;
48+
}
49+
50+
getBrotliSize() {
51+
return this.opts.compressionAlgorithm === 'brotli' ? this.getCompressedSize('brotli') : undefined;
52+
}
53+
54+
getCompressedSize(compressionAlgorithm) {
55+
const key = `_${compressionAlgorithm}Size`;
56+
if (!(key in this)) {
57+
this[key] = this.src ? getCompressedSize(compressionAlgorithm, this.src) : undefined;
4458
}
4559

46-
return this._gzipSize;
60+
return this[key];
4761
}
4862

4963
mergeData(data) {
@@ -63,7 +77,8 @@ export default class Module extends Node {
6377
path: this.path,
6478
statSize: this.size,
6579
parsedSize: this.parsedSize,
66-
gzipSize: this.gzipSize
80+
gzipSize: this.gzipSize,
81+
brotliSize: this.brotliSize
6782
};
6883
}
6984

test/plugin.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,26 @@ describe('Plugin', function () {
170170
expect(error).to.equal(reportTitleError);
171171
});
172172
});
173+
174+
describe('compressionAlgorithm', function () {
175+
it('should default to gzip', async function () {
176+
const config = makeWebpackConfig({analyzerOpts: {}});
177+
await webpackCompile(config, '4.44.2');
178+
await expectValidReport({parsedSize: 1311, gzipSize: 342});
179+
});
180+
181+
it('should support gzip', async function () {
182+
const config = makeWebpackConfig({analyzerOpts: {compressionAlgorithm: 'gzip'}});
183+
await webpackCompile(config, '4.44.2');
184+
await expectValidReport({parsedSize: 1311, gzipSize: 342});
185+
});
186+
187+
it('should support brotli', async function () {
188+
const config = makeWebpackConfig({analyzerOpts: {compressionAlgorithm: 'brotli'}});
189+
await webpackCompile(config, '4.44.2');
190+
await expectValidReport({parsedSize: 1311, gzipSize: undefined, brotliSize: 302});
191+
});
192+
});
173193
});
174194

175195
async function expectValidReport(opts) {
@@ -179,8 +199,8 @@ describe('Plugin', function () {
179199
bundleLabel = 'bundle.js',
180200
statSize = 141,
181201
parsedSize = 2821,
182-
gzipSize = 770
183-
} = opts || {};
202+
gzipSize
203+
} = {gzipSize: 770, ...opts};
184204

185205
expect(fs.existsSync(`${__dirname}/output/${bundleFilename}`), 'bundle file missing').to.be.true;
186206
expect(fs.existsSync(`${__dirname}/output/${reportFilename}`), 'report file missing').to.be.true;

0 commit comments

Comments
 (0)