Skip to content
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

[docs] Track bundle size of pages #19978

Merged
merged 5 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
trigger:
branches:
include:
- '*'
- '*'
exclude:
- l10n
- dependabot/*
- l10n
- dependabot/*

pool:
vmImage: 'ubuntu-latest'
Expand Down Expand Up @@ -37,7 +37,7 @@ steps:
globExpressions: '*.tgz'
targetFolder: 'artifacts/$(Build.SourceBranchName)/$(Build.SourceVersion)'
filesAcl: 'public-read'
displayName: "Upload distributables to S3"
displayName: 'Upload distributables to S3'
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
Expand All @@ -49,8 +49,11 @@ steps:
targetPath: 'material-ui-core.tgz'

- script: |
yarn docs:build
displayName: 'build docs'
set -o pipefail
mkdir -p scripts/sizeSnapshot/build
yarn docs:build | tee scripts/sizeSnapshot/build/docs.next
set +o pipefail
displayName: 'build docs for size snapshot'

- script: |
yarn size:snapshot
Expand Down
155 changes: 105 additions & 50 deletions dangerfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,6 @@ function formatDiff(absoluteChange, relativeChange) {
)})`;
}

function computeBundleLabel(bundleId) {
if (bundleId === 'packages/material-ui/build/umd/material-ui.production.min.js') {
return '@material-ui/core[umd]';
}
if (bundleId === '@material-ui/core/Textarea') {
return 'TextareaAutosize';
}
return bundleId.replace(/^@material-ui\/core\//, '').replace(/\.esm$/, '');
}

/**
* Generates a Markdown table
* @param {{ label: string, align: 'left' | 'center' | 'right'}[]} headers
Expand Down Expand Up @@ -129,6 +119,74 @@ function generateEmphasizedChange([bundle, { parsed, gzip }]) {
return `**${bundle}**: parsed: ${changeParsed}, gzip: ${changeGzip}`;
}

/**
*
* @param {[string, object][]} entries
* @param {object} options
* @param {function (string): string} options.computeBundleLabel
*/
function createComparisonTable(entries, options) {
const { computeBundleLabel } = options;

return generateMDTable(
[
{ label: 'bundle' },
{ label: 'Size Change', align: 'right' },
{ label: 'Size', align: 'right' },
{ label: 'Gzip Change', align: 'right' },
{ label: 'Gzip', align: 'right' },
],
entries
.map(([bundleId, size]) => [computeBundleLabel(bundleId), size])
// orderBy(|parsedDiff| DESC, |gzipDiff| DESC, name ASC)
.sort(([labelA, statsA], [labelB, statsB]) => {
const compareParsedDiff =
Math.abs(statsB.parsed.absoluteDiff) - Math.abs(statsA.parsed.absoluteDiff);
const compareGzipDiff =
Math.abs(statsB.gzip.absoluteDiff) - Math.abs(statsA.gzip.absoluteDiff);
const compareName = labelA.localeCompare(labelB);

if (compareParsedDiff === 0 && compareGzipDiff === 0) {
return compareName;
}
if (compareParsedDiff === 0) {
return compareGzipDiff;
}
return compareParsedDiff;
})
.map(([label, { parsed, gzip }]) => {
return [
label,
formatDiff(parsed.absoluteDiff, parsed.relativeDiff),
prettyBytes(parsed.current),
formatDiff(gzip.absoluteDiff, gzip.relativeDiff),
prettyBytes(gzip.current),
];
}),
);
}

/**
* Puts results in different buckets wh
* @param {*} results
*/
function sieveResults(results) {
const main = [];
const pages = [];

results.forEach(entry => {
const [bundleId] = entry;

if (bundleId.startsWith('docs:')) {
pages.push(entry);
} else {
main.push(entry);
}
});

return { all: results, main, pages };
}

async function run() {
// Use git locally to grab the commit which represents the place
// where the branches differ
Expand All @@ -145,11 +203,14 @@ async function run() {
const commitRange = `${mergeBaseCommit}...${danger.github.pr.head.sha}`;

const comparison = await loadComparison(mergeBaseCommit, upstreamRef);
const results = Object.entries(comparison.bundles);
const anyResultsChanges = results.filter(createComparisonFilter(1, 1));

const { all: allResults, main: mainResults, pages: pageResults } = sieveResults(
Object.entries(comparison.bundles),
);
const anyResultsChanges = allResults.filter(createComparisonFilter(1, 1));

if (anyResultsChanges.length > 0) {
const importantChanges = results
const importantChanges = mainResults
.filter(createComparisonFilter(parsedSizeChangeThreshold, gzipSizeChangeThreshold))
.filter(isPackageComparison)
.map(generateEmphasizedChange);
Expand All @@ -159,50 +220,44 @@ async function run() {
markdown(importantChanges.join('\n'));
}

const detailsTable = generateMDTable(
[
{ label: 'bundle' },
{ label: 'Size Change', align: 'right' },
{ label: 'Size', align: 'right' },
{ label: 'Gzip Change', align: 'right' },
{ label: 'Gzip', align: 'right' },
],
results
.map(([bundleId, size]) => [computeBundleLabel(bundleId), size])
// orderBy(|parsedDiff| DESC, |gzipDiff| DESC, name ASC)
.sort(([labelA, statsA], [labelB, statsB]) => {
const compareParsedDiff =
Math.abs(statsB.parsed.absoluteDiff) - Math.abs(statsA.parsed.absoluteDiff);
const compareGzipDiff =
Math.abs(statsB.gzip.absoluteDiff) - Math.abs(statsA.gzip.absoluteDiff);
const compareName = labelA.localeCompare(labelB);

if (compareParsedDiff === 0 && compareGzipDiff === 0) {
return compareName;
}
if (compareParsedDiff === 0) {
return compareGzipDiff;
}
return compareParsedDiff;
})
.map(([label, { parsed, gzip }]) => {
return [
label,
formatDiff(parsed.absoluteDiff, parsed.relativeDiff),
prettyBytes(parsed.current),
formatDiff(gzip.absoluteDiff, gzip.relativeDiff),
prettyBytes(gzip.current),
];
}),
);
const mainDetailsTable = createComparisonTable(mainResults, {
computeBundleLabel: bundleId => {
if (bundleId === 'packages/material-ui/build/umd/material-ui.production.min.js') {
return '@material-ui/core[umd]';
}
if (bundleId === '@material-ui/core/Textarea') {
return 'TextareaAutosize';
}
if (bundleId === 'docs.main') {
return 'docs:/_app';
}
if (bundleId === 'docs.landing') {
return 'docs:/';
}
return bundleId.replace(/^@material-ui\/core\//, '').replace(/\.esm$/, '');
},
});
const pageDetailsTable = createComparisonTable(pageResults, {
computeBundleLabel: bundleId => {
const host = `https://deploy-preview-${danger.github.pr.number}--material-ui.netlify.com`;
const page = bundleId.replace(/^docs:/, '');
return `[${page}](${host}${page})`;
},
});

const details = `
<details>
<summary>Details of bundle changes.</summary>

<p>Comparing: ${commitRange}</p>

${detailsTable}
<details>
<summary>Details of page changes</summary>

${pageDetailsTable}
</details>

${mainDetailsTable}

</details>`;

Expand Down
68 changes: 67 additions & 1 deletion scripts/sizeSnapshot/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,78 @@ async function getWebpackSizes() {
});
}

// waiting for String.prototype.matchAll in node 10
function* matchAll(string, regex) {
let match = null;
do {
match = regex.exec(string);
if (match !== null) {
yield match;
}
} while (match !== null);
}

/**
* Inverse to `pretty-bytes`
*
* @param {string} n
* @param {'B', 'kB' | 'MB' | 'GB' | 'TB' | 'PB'} unit
* @returns {number}
*/

function prettyBytesInverse(n, unit) {
const metrixPrefix = unit.length < 2 ? '' : unit[0];
const metricPrefixes = ['', 'k', 'M', 'G', 'T', 'P'];
const metrixPrefixIndex = metricPrefixes.indexOf(metrixPrefix);
if (metrixPrefixIndex === -1) {
throw new TypeError(
`unrecognized metric prefix '${metrixPrefix}' in unit '${unit}'. only '${metricPrefixes.join(
"', '",
)}' are allowed`,
);
}

const power = metrixPrefixIndex * 3;
return n * 10 ** power;
}

/**
* parses output from next build to size snapshot format
* @returns {[string, { gzip: number, files: number, packages: number }][]}
*/

async function getNextPagesSize() {
const consoleOutput = await fse.readFile(path.join(__dirname, 'build/docs.next'), {
encoding: 'utf8',
});
const pageRegex = /^(?<treeViewPresentation>┌|├|└)\s+(?<fileType>σ|⚡|)\s+(?<pageUrl>[^\s]+)\s+(?<sizeFormatted>[0-9.]+)\s+(?<sizeUnit>\w+)/gm;

return Array.from(matchAll(consoleOutput, pageRegex), match => {
const { pageUrl, sizeFormatted, sizeUnit } = match.groups;

let snapshotId = `docs:${pageUrl}`;
// used to be tracked with custom logic hence the different ids
if (pageUrl === '/') {
snapshotId = 'docs.main';
} else if (pageUrl === '/_app') {
snapshotId = 'docs.main';
}
return [
snapshotId,
{
parsed: prettyBytesInverse(sizeFormatted, sizeUnit),
gzip: -1,
},
];
});
}

async function run() {
const rollupBundles = [path.join(workspaceRoot, 'packages/material-ui/size-snapshot.json')];

const bundleSizes = lodash.fromPairs([
...(await getWebpackSizes()),
...lodash.flatten(await Promise.all(rollupBundles.map(getRollupSize))),
...(await getNextPagesSize()),
]);

await fse.writeJSON(snapshotDestPath, bundleSizes, { spaces: 2 });
Expand Down
23 changes: 0 additions & 23 deletions scripts/sizeSnapshot/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const fse = require('fs-extra');
const globCallback = require('glob');
const path = require('path');
const CompressionPlugin = require('compression-webpack-plugin');
Expand All @@ -9,18 +8,6 @@ const glob = promisify(globCallback);
const workspaceRoot = path.join(__dirname, '..', '..');

async function getSizeLimitBundles() {
const nextDir = path.join(workspaceRoot, 'docs/.next');
const buildId = await fse.readFile(path.join(nextDir, 'BUILD_ID'), 'utf8');

const dirname = path.join(nextDir, 'static/chunks');
const [main] = (await fse.readdir(dirname)).reduce((result, filename) => {
if (filename.length === 31) {
return [...result, { path: `${dirname}/${filename}` }];
}

return result;
}, []);

const corePackagePath = path.join(workspaceRoot, 'packages/material-ui/build/esm');
const coreComponents = (await glob(path.join(corePackagePath, '[A-Z]*'))).map(componentPath => {
const componentName = path.basename(componentPath);
Expand Down Expand Up @@ -95,16 +82,6 @@ async function getSizeLimitBundles() {
webpack: true,
path: 'packages/material-ui/build/esm/useMediaQuery/index.js',
},
{
name: 'docs.main',
webpack: false,
path: path.relative(workspaceRoot, main.path),
},
{
name: 'docs.landing',
webpack: false,
path: path.relative(workspaceRoot, path.join(nextDir, `static/${buildId}/pages/index.js`)),
},
];
}

Expand Down