Skip to content
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
22 changes: 10 additions & 12 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ jobs:
command: cd packages/material-ui-system && yarn build
- persist_to_workspace:
root: .
paths: packages/*/build
paths:
- packages/*/build
# rollup snapshot
- packages/material-ui/size-snapshot.json
test_browser:
<<: *defaults
steps:
Expand Down Expand Up @@ -228,17 +231,11 @@ jobs:
- *install_js
- attach_workspace:
at: /tmp/workspace
- run:
name: Inspect
command: du -a /tmp/workspace
- run:
name: Restore build
command: |
sudo apt install -y rsync
rsync -a /tmp/workspace/ .
- run:
name: Inspect
command: du -a packages/
# Netlify already do it for us but we need to check the size.
- run:
name: Can we build the docs?
Expand All @@ -247,18 +244,19 @@ jobs:
name: Create a size snapshot
command: yarn size:snapshot
# downloaded by aws lambda to s3 bucket
# lambda allowes us to limit this to mui-org branches only while hiding credentials
# that would allow write access to s3
# lambda allowes us to limit this to mui-org-branches-only while hiding credentials
# that allow write access to s3
- store_artifacts:
path: size-snapshot.json
- run:
name: Possibly persist size snapshot
command: |
if [ $CIRCLE_BRANCH ~= ^pull/ ]; then
echo "pull request; do not persist the size snapshot"
else
if [ -z "$CI_PULL_REQUEST" ]; then
echo "no pull request; lets persist the size snapshot"
curl -X PUT --header "x-api-key: $CIRCLE_AWS_API_KEY" https://t6nulys5kl.execute-api.us-east-1.amazonaws.com/v1/persist-size-snapshot?build-id=$CIRCLE_BUILD_NUM
else
echo "pull request; let's run dangerJS"
yarn danger ci
fi
workflows:
version: 2
Expand Down
175 changes: 175 additions & 0 deletions dangerfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// inspire by reacts dangerfile
// danger has to be the first thing required!
const { danger, markdown } = require('danger');
const { exec } = require('child_process');
const { loadComparison } = require('./scripts/sizeSnapshot');

const parsedSizeChangeThreshold = 300;
const gzipSizeChangeThreshold = 100;

/**
* executes a git subcommand
* @param {any} args
*/
function git(args) {
return new Promise((resolve, reject) => {
exec(`git ${args}`, (err, stdout) => {
if (err) {
reject(err);
} else {
resolve(stdout.trim());
}
});
});
}

const upstreamRemote = 'danger-upstream';

async function cleanup() {
await git(`remote remove ${upstreamRemote}`);
}

function createComparisonFilter(parsedThreshold, gzipThreshold) {
return ([, { parsed, gzip }]) => {
return (
Math.abs(parsed.absoluteDiff) >= parsedThreshold ||
Math.abs(gzip.absoluteDiff) >= gzipThreshold
);
};
}

/**
* checks if the bundle is of a package e.b. `@material-ui/core` but not
* `@material-ui/core/Paper`
* @param {[string]} comparison entry
*/
function isPackageComparison([bundle]) {
return /^@[\w-]+\/[\w-]+$/.test(bundle);
}

/**
* Generates a user-readable string from a percentage change
* @param {number} change
* @param {string} goodEmoji emoji on reduction
* @param {string} badEmooji emoji on increase
*/
function addPercent(change, goodEmoji = '', badEmooji = ':small_red_triangle_down:') {
if (!Number.isFinite(change)) {
// When a new package is created
return 'n/a';
}
const formatted = (change * 100).toFixed(2);
if (/^-|^0(?:\.0+)$/.test(formatted)) {
return `${goodEmoji}${formatted}%`;
}
return `${badEmooji}:+${formatted}%`;
}

/**
* 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')}`;
}

function generateEmphasizedChange([bundle, { parsed, gzip }]) {
// increase might be a bug fix which is a nice thing. reductions are always nice
const changeParsed = addPercent(parsed.relativeDiff, ':heart_eyes:', '');
const changeGzip = addPercent(gzip.relativeDiff, ':heart_eyes:', '');

return `**${bundle}**: parsed: ${changeParsed}, gzip: ${changeGzip}`;
}

async function run() {
// Use git locally to grab the commit which represents the place
// where the branches differ
const upstreamRepo = danger.github.pr.base.repo.full_name;
const upstreamRef = danger.github.pr.base.ref;
try {
await git(`remote add ${upstreamRemote} https://github.com/${upstreamRepo}.git`);
} catch (err) {
// ignore if it already exist for local testing
}
await git(`fetch ${upstreamRemote}`);
const mergeBaseCommit = await git(`merge-base HEAD ${upstreamRemote}/${upstreamRef}`);

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));

if (anyResultsChanges.length > 0) {
markdown('This PR introduced some changes to the bundle size.');

const importantChanges = results
.filter(createComparisonFilter(parsedSizeChangeThreshold, gzipSizeChangeThreshold))
.filter(isPackageComparison)
.map(generateEmphasizedChange);

// have to guard against empty strings
if (importantChanges.length > 0) {
markdown(importantChanges.join('\n'));
}

const detailsTable = generateMDTable(
[
'bundle',
'parsed diff',
'gzip diff',
'prev parsed',
'current parsed',
'prev gzip',
'current gzip',
],
results.map(([bundle, { parsed, gzip }]) => {
return [
bundle,
addPercent(parsed.relativeDiff),
addPercent(gzip.relativeDiff),
parsed.previous,
parsed.current,
gzip.previous,
gzip.current,
];
}),
);

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

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

${detailsTable}

</details>`;

markdown(details);
} else {
// this can later be removed to reduce PR noise. It is kept for now for debug
// purposes only. DangerJS will swallow console.logs if completes successfully
markdown(`No bundle size changes comparing ${commitRange}`);
}
}

(async () => {
try {
await run();
} catch (err) {
console.error(err);
}

try {
await cleanup();
} catch (err) {
console.error(err);
// unhandled promise rejects exit with 0
process.exit(1);
}
})();
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"cross-env": "^5.1.1",
"css-loader": "^2.0.0",
"css-mediaquery": "^0.1.2",
"danger": "^7.0.12",
"date-fns": "2.0.0-alpha.21",
"doctrine": "^3.0.0",
"downshift": "^3.0.0",
Expand Down
7 changes: 0 additions & 7 deletions packages/material-ui/.size-snapshot.json

This file was deleted.

2 changes: 1 addition & 1 deletion packages/material-ui/scripts/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default [
commonjs(commonjsOptions),
nodeGlobals(), // Wait for https://github.com/cssinjs/jss/pull/893
replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
sizeSnapshot(),
sizeSnapshot({ snapshotPath: 'size-snapshot.json' }),
uglify(),
],
},
Expand Down
3 changes: 3 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# material-ui scripts

Top level files are scripts, everything below modules
2 changes: 1 addition & 1 deletion scripts/createSizeSnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ async function getSizeLimitBundles() {
}

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

const bundleSizes = fromEntries(
Expand Down
3 changes: 3 additions & 0 deletions scripts/sizeSnapshot/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const loadComparison = require('./loadComparison');

module.exports = { loadComparison };
79 changes: 79 additions & 0 deletions scripts/sizeSnapshot/loadComparison.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* `snapshots` always refer to size snapshots in this file
*/
const fse = require('fs-extra');
const path = require('path');
const fetch = require('node-fetch');

const artifactServer = 'https://s3.eu-central-1.amazonaws.com/eps1lon-material-ui';

async function loadCurrentSnapshot() {
return fse.readJSON(path.join(__dirname, '../../size-snapshot.json'));
}

/**
* @param {string} commitId the sha of a commit
* @param {string} ref the branch containing that commit
*/
async function loadSnapshot(commitId, ref = 'master') {
const response = await fetch(`${artifactServer}/artifacts/${ref}/${commitId}/size-snapshot.json`);
return response.json();
}

function flatten(array) {
return array.reduce((acc, entry) => acc.concat(entry), []);
}

function fromEntries(entries) {
return entries.reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, []);
}

function uniqueKeys(...objects) {
return Array.from(new Set(flatten(objects.map(Object.keys))));
}

const nullSnapshot = { gzip: Number.NaN, parsed: Number.NaN };

module.exports = async function loadComparison(parrentId, ref) {
const [currentSnapshot, previousSnapshot] = await Promise.all([
loadCurrentSnapshot(),
// silence non existing snapshots
loadSnapshot(parrentId, ref).catch(() => ({})),
]);

const bundleKeys = uniqueKeys(currentSnapshot, previousSnapshot);

const bundles = fromEntries(
bundleKeys.map(bundle => {
const currentSize = currentSnapshot[bundle] || nullSnapshot;
const previousSize = previousSnapshot[bundle] || nullSnapshot;

return [
bundle,
{
parsed: {
previous: previousSize.parsed,
current: currentSize.parsed,
absoluteDiff: currentSize.parsed - previousSize.parsed,
relativeDiff: currentSize.parsed / previousSize.parsed - 1,
},
gzip: {
previous: previousSize.gzip,
current: currentSize.gzip,
absoluteDiff: currentSize.gzip - previousSize.gzip,
relativeDiff: currentSize.gzip / previousSize.gzip - 1,
},
},
];
}),
);

return {
previous: parrentId,
current: 'HEAD',
bundles,
};
};
Loading