From 743e26d89638d8f52c66c25f1dbc6e49ee9a4138 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 5 Mar 2020 00:50:13 +0100 Subject: [PATCH] [docs] Track bundle size of pages (#19978) --- azure-pipelines.yml | 15 ++- dangerfile.js | 155 +++++++++++++++++-------- scripts/sizeSnapshot/create.js | 68 ++++++++++- scripts/sizeSnapshot/webpack.config.js | 23 ---- 4 files changed, 181 insertions(+), 80 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ea9f463816041c..c7a64a698f5cee 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,10 +1,10 @@ trigger: branches: include: - - '*' + - '*' exclude: - - l10n - - dependabot/* + - l10n + - dependabot/* pool: vmImage: 'ubuntu-latest' @@ -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) @@ -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 diff --git a/dangerfile.js b/dangerfile.js index cfe09b018cfe4d..cb4d2edd125215 100644 --- a/dangerfile.js +++ b/dangerfile.js @@ -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 @@ -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 @@ -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); @@ -159,42 +220,30 @@ 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 = `
@@ -202,7 +251,13 @@ async function run() {

Comparing: ${commitRange}

- ${detailsTable} +
+ Details of page changes + + ${pageDetailsTable} +
+ + ${mainDetailsTable}
`; diff --git a/scripts/sizeSnapshot/create.js b/scripts/sizeSnapshot/create.js index 8ccc35c863bb50..66f4863efcc07a 100644 --- a/scripts/sizeSnapshot/create.js +++ b/scripts/sizeSnapshot/create.js @@ -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 = /^(?┌|├|└)\s+(?σ|⚡|)\s+(?[^\s]+)\s+(?[0-9.]+)\s+(?\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 }); diff --git a/scripts/sizeSnapshot/webpack.config.js b/scripts/sizeSnapshot/webpack.config.js index 6026b4b3c21e00..6a0f823fe2930d 100644 --- a/scripts/sizeSnapshot/webpack.config.js +++ b/scripts/sizeSnapshot/webpack.config.js @@ -1,4 +1,3 @@ -const fse = require('fs-extra'); const globCallback = require('glob'); const path = require('path'); const CompressionPlugin = require('compression-webpack-plugin'); @@ -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); @@ -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`)), - }, ]; }