mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Adds Danger and a rule showing build size differences (#11865)
* Adds danger_js with an initial rule for warning about large PRs Signed-off-by: Anandaroop Roy <roop@artsymail.com> * [WIP] Get the before and after for the build results * [Dev] More work on the Dangerfile * [Danger] Split the reports into sections based on their package * Remove the --extract-errors on the circle build * [Danger] Improve the lookup for previous -> current build to also include the environment * Fix rebase
This commit is contained in:
+151
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {markdown, danger} = require('danger');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const {generateResultsArray} = require('./scripts/rollup/stats');
|
||||
const {readFileSync} = require('fs');
|
||||
|
||||
const currentBuildResults = JSON.parse(
|
||||
readFileSync('./scripts/rollup/results.json')
|
||||
);
|
||||
|
||||
/**
|
||||
* Generates a Markdown table
|
||||
* @param {string[]} headers
|
||||
* @param {string[][]} body
|
||||
*/
|
||||
function generateMDTable(headers, body) {
|
||||
const tableHeaders = [
|
||||
headers.join(' | '),
|
||||
headers.map(() => ' --- ').join(' | '),
|
||||
];
|
||||
|
||||
const tablebody = body.map(r => r.join(' | '));
|
||||
return tableHeaders.join('\n') + '\n' + tablebody.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a user-readable string from a percentage change
|
||||
* @param {string[]} headers
|
||||
*/
|
||||
function emojiPercent(change) {
|
||||
if (change > 0) {
|
||||
return `:small_red_triangle:+${change}%`;
|
||||
} else if (change <= 0) {
|
||||
return `${change}%`;
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the results.json before we ran CI via the GH API
|
||||
// const baseMerge = danger.github.pr.base.sha
|
||||
const parentOfOldestCommit = danger.git.commits[0].parents[0];
|
||||
const commitURL = sha =>
|
||||
`http://react.zpao.com/builds/master/_commits/${sha}/results.json`;
|
||||
|
||||
fetch(commitURL(parentOfOldestCommit)).then(async response => {
|
||||
const previousBuildResults = await response.json();
|
||||
const results = generateResultsArray(
|
||||
currentBuildResults,
|
||||
previousBuildResults
|
||||
);
|
||||
|
||||
const percentToWarrentShowing = 1;
|
||||
const packagesToShow = results
|
||||
.filter(
|
||||
r =>
|
||||
Math.abs(r.prevFileSizeChange) > percentToWarrentShowing ||
|
||||
Math.abs(r.prevGzipSizeChange) > percentToWarrentShowing
|
||||
)
|
||||
.map(r => r.packageName);
|
||||
|
||||
if (packagesToShow.length) {
|
||||
let allTables = [];
|
||||
|
||||
// Highlight React and React DOM changes inline
|
||||
// e.g. react: `react.production.min.js`: -3%, `react.development.js`: +4%
|
||||
|
||||
if (packagesToShow.includes('react')) {
|
||||
const reactProd = results.find(
|
||||
r => r.bundleType === 'UMD_PROD' && r.packageName === 'react'
|
||||
);
|
||||
if (
|
||||
reactProd.prevFileSizeChange !== 0 ||
|
||||
reactProd.prevGzipSizeChange !== 0
|
||||
) {
|
||||
const changeSize = emojiPercent(reactProd.prevFileSizeChange);
|
||||
const changeGzip = emojiPercent(reactProd.prevGzipSizeChange);
|
||||
markdown(`React: size: ${changeSize}, gzip: ${changeGzip}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (packagesToShow.includes('react-dom')) {
|
||||
const reactDOMProd = results.find(
|
||||
r => r.bundleType === 'UMD_PROD' && r.packageName === 'react-dom'
|
||||
);
|
||||
if (
|
||||
reactDOMProd.prevFileSizeChange !== 0 ||
|
||||
reactDOMProd.prevGzipSizeChange !== 0
|
||||
) {
|
||||
const changeSize = emojiPercent(reactDOMProd.prevFileSizeChange);
|
||||
const changeGzip = emojiPercent(reactDOMProd.prevGzipSizeChange);
|
||||
markdown(`ReactDOM: size: ${changeSize}, gzip: ${changeGzip}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Show a hidden summary table for all diffs
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
for (var name of new Set(packagesToShow)) {
|
||||
const thisBundleResults = results.filter(r => r.packageName === name);
|
||||
const changedFiles = thisBundleResults.filter(
|
||||
r => r.prevGzipSizeChange !== 0 || r.prevGzipSizeChange !== 0
|
||||
);
|
||||
|
||||
const mdHeaders = [
|
||||
'File',
|
||||
'Filesize Diff',
|
||||
'Gzip Diff',
|
||||
'Prev Size',
|
||||
'Current Size',
|
||||
'Prev Gzip',
|
||||
'Current Gzip',
|
||||
'ENV',
|
||||
];
|
||||
|
||||
const mdRows = changedFiles.map(r => [
|
||||
r.filename,
|
||||
emojiPercent(r.prevFileSizeChange),
|
||||
emojiPercent(r.prevGzipSizeChange),
|
||||
r.prevSize,
|
||||
r.prevFileSize,
|
||||
r.prevGzip,
|
||||
r.prevGzipSize,
|
||||
r.bundleType,
|
||||
]);
|
||||
|
||||
allTables.push(`\n## ${name}`);
|
||||
allTables.push(generateMDTable(mdHeaders, mdRows));
|
||||
}
|
||||
|
||||
const summary = `
|
||||
<details>
|
||||
<summary>Details of bundled changes.</summary>
|
||||
|
||||
<p>Comparing: ${parentOfOldestCommit}...${danger.github.pr.head.sha}</p>
|
||||
|
||||
|
||||
${allTables.join('\n')}
|
||||
|
||||
</details>
|
||||
`;
|
||||
markdown(summary);
|
||||
}
|
||||
});
|
||||
@@ -47,6 +47,7 @@
|
||||
"coveralls": "^2.11.6",
|
||||
"create-react-class": "^15.6.2",
|
||||
"cross-env": "^5.1.1",
|
||||
"danger": "^3.0.4",
|
||||
"del": "^2.0.2",
|
||||
"derequire": "^2.0.3",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
|
||||
@@ -25,6 +25,7 @@ if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/build.sh')
|
||||
COMMANDS_TO_RUN+=('yarn test-build --maxWorkers=2')
|
||||
COMMANDS_TO_RUN+=('yarn test-build-prod --maxWorkers=2')
|
||||
COMMANDS_TO_RUN+=('node ./scripts/tasks/danger')
|
||||
COMMANDS_TO_RUN+=('./scripts/circleci/upload_build.sh')
|
||||
fi
|
||||
|
||||
|
||||
@@ -400,4 +400,4 @@
|
||||
"gzip": 525
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+61
-35
@@ -21,8 +21,10 @@ function saveResults() {
|
||||
}
|
||||
|
||||
function percentChange(prev, current) {
|
||||
const change = Math.floor((current - prev) / prev * 100);
|
||||
return Math.floor((current - prev) / prev * 100);
|
||||
}
|
||||
|
||||
function percentChangeString(change) {
|
||||
if (change > 0) {
|
||||
return chalk.red.bold(`+${change} %`);
|
||||
} else if (change <= 0) {
|
||||
@@ -30,51 +32,75 @@ function percentChange(prev, current) {
|
||||
}
|
||||
}
|
||||
|
||||
const resultsHeaders = [
|
||||
'Bundle',
|
||||
'Prev Size',
|
||||
'Current Size',
|
||||
'Diff',
|
||||
'Prev Gzip',
|
||||
'Current Gzip',
|
||||
'Diff',
|
||||
];
|
||||
|
||||
function generateResultsArray(current, prevResults) {
|
||||
return current.bundleSizes
|
||||
.map(result => {
|
||||
const prev = prevResults.bundleSizes.filter(
|
||||
res =>
|
||||
res.filename === result.filename &&
|
||||
res.bundleType === result.bundleType
|
||||
)[0];
|
||||
if (result === prev) {
|
||||
// We didn't rebuild this bundle.
|
||||
return;
|
||||
}
|
||||
|
||||
const size = result.size;
|
||||
const gzip = result.gzip;
|
||||
let prevSize = prev ? prev.size : 0;
|
||||
let prevGzip = prev ? prev.gzip : 0;
|
||||
|
||||
return {
|
||||
filename: result.filename,
|
||||
bundleType: result.bundleType,
|
||||
packageName: result.packageName,
|
||||
prevSize: filesize(prevSize),
|
||||
prevFileSize: filesize(size),
|
||||
prevFileSizeChange: percentChange(prevSize, size),
|
||||
prevGzip: filesize(prevGzip),
|
||||
prevGzipSize: filesize(gzip),
|
||||
prevGzipSizeChange: percentChange(prevGzip, gzip),
|
||||
};
|
||||
// Strip any nulls
|
||||
})
|
||||
.filter(f => f);
|
||||
}
|
||||
|
||||
function printResults() {
|
||||
const table = new Table({
|
||||
head: [
|
||||
chalk.gray.yellow('Bundle'),
|
||||
chalk.gray.yellow('Prev Size'),
|
||||
chalk.gray.yellow('Current Size'),
|
||||
chalk.gray.yellow('Diff'),
|
||||
chalk.gray.yellow('Prev Gzip'),
|
||||
chalk.gray.yellow('Current Gzip'),
|
||||
chalk.gray.yellow('Diff'),
|
||||
],
|
||||
head: resultsHeaders.map(chalk.gray.yellow),
|
||||
});
|
||||
currentBuildResults.bundleSizes.forEach(result => {
|
||||
const matches = prevBuildResults.bundleSizes.filter(
|
||||
({filename, bundleType}) =>
|
||||
filename === result.filename && bundleType === result.bundleType
|
||||
);
|
||||
if (matches.length > 1) {
|
||||
throw new Error(`Ambiguous bundle size record for: ${result.filename}`);
|
||||
}
|
||||
const prev = matches[0];
|
||||
if (result === prev) {
|
||||
// We didn't rebuild this bundle.
|
||||
return;
|
||||
}
|
||||
|
||||
const size = result.size;
|
||||
const gzip = result.gzip;
|
||||
let prevSize = prev ? prev.size : 0;
|
||||
let prevGzip = prev ? prev.gzip : 0;
|
||||
const results = generateResultsArray(currentBuildResults, prevBuildResults);
|
||||
results.forEach(result => {
|
||||
table.push([
|
||||
chalk.white.bold(`${result.filename} (${result.bundleType})`),
|
||||
chalk.gray.bold(filesize(prevSize)),
|
||||
chalk.white.bold(filesize(size)),
|
||||
percentChange(prevSize, size),
|
||||
chalk.gray.bold(filesize(prevGzip)),
|
||||
chalk.white.bold(filesize(gzip)),
|
||||
percentChange(prevGzip, gzip),
|
||||
chalk.white.bold(`${result.filename} (${result.bundleType})`),
|
||||
chalk.gray.bold(result.prevSize),
|
||||
chalk.white.bold(result.prevFileSize),
|
||||
percentChangeString(result.prevFileSizeChange),
|
||||
chalk.gray.bold(result.prevGzip),
|
||||
chalk.white.bold(result.prevGzipSize),
|
||||
percentChangeString(result.prevGzipSizeChange),
|
||||
]);
|
||||
});
|
||||
|
||||
return table.toString();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
currentBuildResults,
|
||||
generateResultsArray,
|
||||
printResults,
|
||||
saveResults,
|
||||
currentBuildResults,
|
||||
resultsHeaders,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
const extension = process.platform === 'win32' ? '.cmd' : '';
|
||||
|
||||
// This came from React Native's circle.yml
|
||||
const token = 'e622517d9f1136ea8900' + '07c6373666312cdfaa69';
|
||||
spawn(path.join('node_modules', '.bin', 'danger-ci' + extension), [], {
|
||||
// Allow colors to pass through
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
DANGER_GITHUB_API_TOKEN: token,
|
||||
},
|
||||
}).on('close', function(code) {
|
||||
if (code !== 0) {
|
||||
console.error('Danger failed');
|
||||
} else {
|
||||
console.log('Danger passed');
|
||||
}
|
||||
|
||||
process.exit(code);
|
||||
});
|
||||
Reference in New Issue
Block a user