Skip to content

Commit 743e26d

Browse files
authored
[docs] Track bundle size of pages (mui#19978)
1 parent 7124101 commit 743e26d

File tree

4 files changed

+181
-80
lines changed

4 files changed

+181
-80
lines changed

azure-pipelines.yml

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
trigger:
22
branches:
33
include:
4-
- '*'
4+
- '*'
55
exclude:
6-
- l10n
7-
- dependabot/*
6+
- l10n
7+
- dependabot/*
88

99
pool:
1010
vmImage: 'ubuntu-latest'
@@ -37,7 +37,7 @@ steps:
3737
globExpressions: '*.tgz'
3838
targetFolder: 'artifacts/$(Build.SourceBranchName)/$(Build.SourceVersion)'
3939
filesAcl: 'public-read'
40-
displayName: "Upload distributables to S3"
40+
displayName: 'Upload distributables to S3'
4141
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
4242
env:
4343
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
@@ -49,8 +49,11 @@ steps:
4949
targetPath: 'material-ui-core.tgz'
5050

5151
- script: |
52-
yarn docs:build
53-
displayName: 'build docs'
52+
set -o pipefail
53+
mkdir -p scripts/sizeSnapshot/build
54+
yarn docs:build | tee scripts/sizeSnapshot/build/docs.next
55+
set +o pipefail
56+
displayName: 'build docs for size snapshot'
5457
5558
- script: |
5659
yarn size:snapshot

dangerfile.js

+105-50
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,6 @@ function formatDiff(absoluteChange, relativeChange) {
9090
)})`;
9191
}
9292

93-
function computeBundleLabel(bundleId) {
94-
if (bundleId === 'packages/material-ui/build/umd/material-ui.production.min.js') {
95-
return '@material-ui/core[umd]';
96-
}
97-
if (bundleId === '@material-ui/core/Textarea') {
98-
return 'TextareaAutosize';
99-
}
100-
return bundleId.replace(/^@material-ui\/core\//, '').replace(/\.esm$/, '');
101-
}
102-
10393
/**
10494
* Generates a Markdown table
10595
* @param {{ label: string, align: 'left' | 'center' | 'right'}[]} headers
@@ -129,6 +119,74 @@ function generateEmphasizedChange([bundle, { parsed, gzip }]) {
129119
return `**${bundle}**: parsed: ${changeParsed}, gzip: ${changeGzip}`;
130120
}
131121

122+
/**
123+
*
124+
* @param {[string, object][]} entries
125+
* @param {object} options
126+
* @param {function (string): string} options.computeBundleLabel
127+
*/
128+
function createComparisonTable(entries, options) {
129+
const { computeBundleLabel } = options;
130+
131+
return generateMDTable(
132+
[
133+
{ label: 'bundle' },
134+
{ label: 'Size Change', align: 'right' },
135+
{ label: 'Size', align: 'right' },
136+
{ label: 'Gzip Change', align: 'right' },
137+
{ label: 'Gzip', align: 'right' },
138+
],
139+
entries
140+
.map(([bundleId, size]) => [computeBundleLabel(bundleId), size])
141+
// orderBy(|parsedDiff| DESC, |gzipDiff| DESC, name ASC)
142+
.sort(([labelA, statsA], [labelB, statsB]) => {
143+
const compareParsedDiff =
144+
Math.abs(statsB.parsed.absoluteDiff) - Math.abs(statsA.parsed.absoluteDiff);
145+
const compareGzipDiff =
146+
Math.abs(statsB.gzip.absoluteDiff) - Math.abs(statsA.gzip.absoluteDiff);
147+
const compareName = labelA.localeCompare(labelB);
148+
149+
if (compareParsedDiff === 0 && compareGzipDiff === 0) {
150+
return compareName;
151+
}
152+
if (compareParsedDiff === 0) {
153+
return compareGzipDiff;
154+
}
155+
return compareParsedDiff;
156+
})
157+
.map(([label, { parsed, gzip }]) => {
158+
return [
159+
label,
160+
formatDiff(parsed.absoluteDiff, parsed.relativeDiff),
161+
prettyBytes(parsed.current),
162+
formatDiff(gzip.absoluteDiff, gzip.relativeDiff),
163+
prettyBytes(gzip.current),
164+
];
165+
}),
166+
);
167+
}
168+
169+
/**
170+
* Puts results in different buckets wh
171+
* @param {*} results
172+
*/
173+
function sieveResults(results) {
174+
const main = [];
175+
const pages = [];
176+
177+
results.forEach(entry => {
178+
const [bundleId] = entry;
179+
180+
if (bundleId.startsWith('docs:')) {
181+
pages.push(entry);
182+
} else {
183+
main.push(entry);
184+
}
185+
});
186+
187+
return { all: results, main, pages };
188+
}
189+
132190
async function run() {
133191
// Use git locally to grab the commit which represents the place
134192
// where the branches differ
@@ -145,11 +203,14 @@ async function run() {
145203
const commitRange = `${mergeBaseCommit}...${danger.github.pr.head.sha}`;
146204

147205
const comparison = await loadComparison(mergeBaseCommit, upstreamRef);
148-
const results = Object.entries(comparison.bundles);
149-
const anyResultsChanges = results.filter(createComparisonFilter(1, 1));
206+
207+
const { all: allResults, main: mainResults, pages: pageResults } = sieveResults(
208+
Object.entries(comparison.bundles),
209+
);
210+
const anyResultsChanges = allResults.filter(createComparisonFilter(1, 1));
150211

151212
if (anyResultsChanges.length > 0) {
152-
const importantChanges = results
213+
const importantChanges = mainResults
153214
.filter(createComparisonFilter(parsedSizeChangeThreshold, gzipSizeChangeThreshold))
154215
.filter(isPackageComparison)
155216
.map(generateEmphasizedChange);
@@ -159,50 +220,44 @@ async function run() {
159220
markdown(importantChanges.join('\n'));
160221
}
161222

162-
const detailsTable = generateMDTable(
163-
[
164-
{ label: 'bundle' },
165-
{ label: 'Size Change', align: 'right' },
166-
{ label: 'Size', align: 'right' },
167-
{ label: 'Gzip Change', align: 'right' },
168-
{ label: 'Gzip', align: 'right' },
169-
],
170-
results
171-
.map(([bundleId, size]) => [computeBundleLabel(bundleId), size])
172-
// orderBy(|parsedDiff| DESC, |gzipDiff| DESC, name ASC)
173-
.sort(([labelA, statsA], [labelB, statsB]) => {
174-
const compareParsedDiff =
175-
Math.abs(statsB.parsed.absoluteDiff) - Math.abs(statsA.parsed.absoluteDiff);
176-
const compareGzipDiff =
177-
Math.abs(statsB.gzip.absoluteDiff) - Math.abs(statsA.gzip.absoluteDiff);
178-
const compareName = labelA.localeCompare(labelB);
179-
180-
if (compareParsedDiff === 0 && compareGzipDiff === 0) {
181-
return compareName;
182-
}
183-
if (compareParsedDiff === 0) {
184-
return compareGzipDiff;
185-
}
186-
return compareParsedDiff;
187-
})
188-
.map(([label, { parsed, gzip }]) => {
189-
return [
190-
label,
191-
formatDiff(parsed.absoluteDiff, parsed.relativeDiff),
192-
prettyBytes(parsed.current),
193-
formatDiff(gzip.absoluteDiff, gzip.relativeDiff),
194-
prettyBytes(gzip.current),
195-
];
196-
}),
197-
);
223+
const mainDetailsTable = createComparisonTable(mainResults, {
224+
computeBundleLabel: bundleId => {
225+
if (bundleId === 'packages/material-ui/build/umd/material-ui.production.min.js') {
226+
return '@material-ui/core[umd]';
227+
}
228+
if (bundleId === '@material-ui/core/Textarea') {
229+
return 'TextareaAutosize';
230+
}
231+
if (bundleId === 'docs.main') {
232+
return 'docs:/_app';
233+
}
234+
if (bundleId === 'docs.landing') {
235+
return 'docs:/';
236+
}
237+
return bundleId.replace(/^@material-ui\/core\//, '').replace(/\.esm$/, '');
238+
},
239+
});
240+
const pageDetailsTable = createComparisonTable(pageResults, {
241+
computeBundleLabel: bundleId => {
242+
const host = `https://deploy-preview-${danger.github.pr.number}--material-ui.netlify.com`;
243+
const page = bundleId.replace(/^docs:/, '');
244+
return `[${page}](${host}${page})`;
245+
},
246+
});
198247

199248
const details = `
200249
<details>
201250
<summary>Details of bundle changes.</summary>
202251
203252
<p>Comparing: ${commitRange}</p>
204253
205-
${detailsTable}
254+
<details>
255+
<summary>Details of page changes</summary>
256+
257+
${pageDetailsTable}
258+
</details>
259+
260+
${mainDetailsTable}
206261
207262
</details>`;
208263

scripts/sizeSnapshot/create.js

+67-1
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,78 @@ async function getWebpackSizes() {
4949
});
5050
}
5151

52+
// waiting for String.prototype.matchAll in node 10
53+
function* matchAll(string, regex) {
54+
let match = null;
55+
do {
56+
match = regex.exec(string);
57+
if (match !== null) {
58+
yield match;
59+
}
60+
} while (match !== null);
61+
}
62+
63+
/**
64+
* Inverse to `pretty-bytes`
65+
*
66+
* @param {string} n
67+
* @param {'B', 'kB' | 'MB' | 'GB' | 'TB' | 'PB'} unit
68+
* @returns {number}
69+
*/
70+
71+
function prettyBytesInverse(n, unit) {
72+
const metrixPrefix = unit.length < 2 ? '' : unit[0];
73+
const metricPrefixes = ['', 'k', 'M', 'G', 'T', 'P'];
74+
const metrixPrefixIndex = metricPrefixes.indexOf(metrixPrefix);
75+
if (metrixPrefixIndex === -1) {
76+
throw new TypeError(
77+
`unrecognized metric prefix '${metrixPrefix}' in unit '${unit}'. only '${metricPrefixes.join(
78+
"', '",
79+
)}' are allowed`,
80+
);
81+
}
82+
83+
const power = metrixPrefixIndex * 3;
84+
return n * 10 ** power;
85+
}
86+
87+
/**
88+
* parses output from next build to size snapshot format
89+
* @returns {[string, { gzip: number, files: number, packages: number }][]}
90+
*/
91+
92+
async function getNextPagesSize() {
93+
const consoleOutput = await fse.readFile(path.join(__dirname, 'build/docs.next'), {
94+
encoding: 'utf8',
95+
});
96+
const pageRegex = /^(?<treeViewPresentation>||)\s+(?<fileType>σ||)\s+(?<pageUrl>[^\s]+)\s+(?<sizeFormatted>[0-9.]+)\s+(?<sizeUnit>\w+)/gm;
97+
98+
return Array.from(matchAll(consoleOutput, pageRegex), match => {
99+
const { pageUrl, sizeFormatted, sizeUnit } = match.groups;
100+
101+
let snapshotId = `docs:${pageUrl}`;
102+
// used to be tracked with custom logic hence the different ids
103+
if (pageUrl === '/') {
104+
snapshotId = 'docs.main';
105+
} else if (pageUrl === '/_app') {
106+
snapshotId = 'docs.main';
107+
}
108+
return [
109+
snapshotId,
110+
{
111+
parsed: prettyBytesInverse(sizeFormatted, sizeUnit),
112+
gzip: -1,
113+
},
114+
];
115+
});
116+
}
117+
52118
async function run() {
53119
const rollupBundles = [path.join(workspaceRoot, 'packages/material-ui/size-snapshot.json')];
54-
55120
const bundleSizes = lodash.fromPairs([
56121
...(await getWebpackSizes()),
57122
...lodash.flatten(await Promise.all(rollupBundles.map(getRollupSize))),
123+
...(await getNextPagesSize()),
58124
]);
59125

60126
await fse.writeJSON(snapshotDestPath, bundleSizes, { spaces: 2 });

scripts/sizeSnapshot/webpack.config.js

-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const fse = require('fs-extra');
21
const globCallback = require('glob');
32
const path = require('path');
43
const CompressionPlugin = require('compression-webpack-plugin');
@@ -9,18 +8,6 @@ const glob = promisify(globCallback);
98
const workspaceRoot = path.join(__dirname, '..', '..');
109

1110
async function getSizeLimitBundles() {
12-
const nextDir = path.join(workspaceRoot, 'docs/.next');
13-
const buildId = await fse.readFile(path.join(nextDir, 'BUILD_ID'), 'utf8');
14-
15-
const dirname = path.join(nextDir, 'static/chunks');
16-
const [main] = (await fse.readdir(dirname)).reduce((result, filename) => {
17-
if (filename.length === 31) {
18-
return [...result, { path: `${dirname}/${filename}` }];
19-
}
20-
21-
return result;
22-
}, []);
23-
2411
const corePackagePath = path.join(workspaceRoot, 'packages/material-ui/build/esm');
2512
const coreComponents = (await glob(path.join(corePackagePath, '[A-Z]*'))).map(componentPath => {
2613
const componentName = path.basename(componentPath);
@@ -95,16 +82,6 @@ async function getSizeLimitBundles() {
9582
webpack: true,
9683
path: 'packages/material-ui/build/esm/useMediaQuery/index.js',
9784
},
98-
{
99-
name: 'docs.main',
100-
webpack: false,
101-
path: path.relative(workspaceRoot, main.path),
102-
},
103-
{
104-
name: 'docs.landing',
105-
webpack: false,
106-
path: path.relative(workspaceRoot, path.join(nextDir, `static/${buildId}/pages/index.js`)),
107-
},
10885
];
10986
}
11087

0 commit comments

Comments
 (0)