Skip to content

Commit

Permalink
(chore) Fix build size report (#3840)
Browse files Browse the repository at this point in the history
* Add buildSizeReport script

* Add attribution

* Add new workflows for computing and commenting size

* Remove old size report

* Update changes

* Run report from the PR

* Improve workflow step names

* Improve size report workflow file names

* Improve workflow names

* Also report for CSS files

* Remove CDN from desc

* Improve report description and layout

* Improve variable names

* Bold gzip

* Change language of total change

* Refactor changes into seperate function

* Split report into seperate functions for clarity

* Don't use head.sha

This is the unmerged head, which may be outdated.

* Ensure added and removed files are formatted as table
  • Loading branch information
bradleymackey authored Aug 17, 2023
1 parent 2dd2e4f commit e71d91d
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 30 deletions.
30 changes: 0 additions & 30 deletions .github/workflows/size_report.yml

This file was deleted.

52 changes: 52 additions & 0 deletions .github/workflows/size_report_comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Comment Build Size Report

on:
workflow_run:
workflows: ["Create Build Size Report"]
types:
- completed

jobs:
comment_report:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: "Download size report artifact"
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "size_report"
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/size_report.zip', Buffer.from(download.data));
- run: unzip -d size_report size_report.zip

- name: "Comment on PR"
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
var issue_number = Number(fs.readFileSync('./size_report/pull_req_nr'));
var size_report = String(fs.readFileSync('./size_report/report.md'));
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: size_report
});
45 changes: 45 additions & 0 deletions .github/workflows/size_report_create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Create Build Size Report

on:
pull_request:

jobs:
run:
runs-on: ubuntu-latest

steps:
- name: Checkout Base
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.base.ref }}
path: base

- name: Checkout PR
uses: actions/checkout@v3
with:
path: pr

- name: Build CDN (Base)
run: |
npm ci
npm run build-cdn
working-directory: ./base

- name: Build CDN (PR)
run: |
npm ci
npm run build-cdn
working-directory: ./pr

- name: Create Size Report
run: |
mkdir size_report
echo ${{ github.event.number }} > size_report/pull_req_nr
REPORT=$(./pr/tools/buildSizeReport.js ./base/build ./pr/build)
echo "$REPORT" > size_report/report.md
- name: Save Size Report as Artifact
uses: actions/upload-artifact@v3
with:
name: size_report
path: ./size_report
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Dev tool:
- (chore) Remove discontinued badges from README [Bradley Mackey][]
- (chore) Update dev tool to use the new `highlight` API. [Shah Shabbir Ahmmed][]
- (enh) Auto-update the highlighted output when the language dropdown changes. [Shah Shabbir Ahmmed][]
- (chore) Fix build size report [Bradley Mackey][]

[Robert Borghese]: https://github.com/RobertBorghese
[Isaac Nonato]: https://github.com/isaacnonato
Expand Down
192 changes: 192 additions & 0 deletions tools/buildSizeReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env node

/*
* Input: path to 2 build directories to compare.
* Output: markdown report of size changes.
*/

const fs = require("fs");
const path = require("path");
const zlib = require("zlib");
const glob = require("glob");

// https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) {
return "0 B";
}

const k = 1000;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];

const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));

return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
}

/**
* The size, in bytes, of the given file after gzip.
*/
function computedFile(dir, filePath) {
const pathToFile = path.join(dir, filePath);
const str = fs.readFileSync(pathToFile);
return zlib.gzipSync(str).length;
}

/**
* Returns list of minified files in the given directory.
*/
async function minifiedFiles(dir) {
return await new Promise((res, rej) => {
glob(dir + "/**/*.min.{js,css}", {}, (err, files) => {
if (err) {
rej(err);
} else {
res(files.map((f) => f.replace(dir + "/", "")));
}
});
});
}

/**
* Returns object of changes between the given lists of strings.
*/
function itemChanges(baseList, newList) {
const baseSet = new Set(baseList);
const newSet = new Set(newList);

let added = [];
for (const str of newList) {
if (!baseSet.has(str)) {
added.push(str);
}
}

let changed = [];
let removed = [];
for (const str of baseList) {
newSet.has(str) ? changed.push(str) : removed.push(str);
}

return {
added,
changed,
removed,
};
}

function reportHeader() {
return (
"# Build Size Report\n\n" +
"Changes to minified artifacts in `/build`, after **gzip** compression."
);
}

function reportAddedFilesSection(_base, pr, addedFiles) {
let md = "";
const maybeS = addedFiles.length === 1 ? "" : "s";
md += `## ${addedFiles.length} Added File${maybeS}\n\n`;
md += "<details>\n";
md += "<summary>View Changes</summary>\n\n";
md += "| file | size |\n";
md += "| --- | --- |\n";
for (const file of addedFiles) {
const computedSize = computedFile(pr, file);
md += `| ${file} | +${formatBytes(computedSize)} |\n`;
}
md += "\n";
md += "</details>\n";
return md;
}

function reportRemovedFilesSection(base, _pr, removedFiles) {
let md = "";
const maybeS = removedFiles.length === 1 ? "" : "s";
md += `## ${removedFiles.length} Removed File${maybeS}\n\n`;
md += "<details>\n";
md += "<summary>View Changes</summary>\n\n";
md += "| file | size |\n";
md += "| --- | --- |\n";
for (const file of removedFiles) {
const computedSize = computedFile(base, file);
md += `| ${file} | -${formatBytes(computedSize)} |\n`;
}
md += "\n";
md += "</details>\n";
return md;
}

function reportChangedFilesSection(base, pr, changedFiles) {
let md = "";
let numFilesChanged = 0;
let combinedSizeChange = 0;
let sizeChangeMd = "| file | base | pr | diff |\n";
sizeChangeMd += "| --- | --- | --- | --- |\n";
for (const file of changedFiles) {
const computedBase = computedFile(base, file);
const computedPR = computedFile(pr, file);
const diff = computedPR - computedBase;
if (diff !== 0) {
combinedSizeChange += diff;
numFilesChanged += 1;
const sign = diff >= 0 ? "+" : "";
sizeChangeMd += `| ${file} | ${formatBytes(computedBase)} | ${formatBytes(
computedPR
)} | ${sign}${formatBytes(diff)} |\n`;
}
}

if (numFilesChanged > 0) {
const maybeS = numFilesChanged === 1 ? "" : "s";
const sign = combinedSizeChange >= 0 ? "+" : "";
md += `## ${numFilesChanged} file${maybeS} changed\n`;
md += `Total change ${sign}${formatBytes(combinedSizeChange)}\n\n`;
md += "<details>\n";
md += "<summary>View Changes</summary>\n\n";
md += sizeChangeMd;
md += "\n";
md += "</details>\n";
} else {
md += "## No changes\n";
md += "No existing files changed.\n";
}

return md;
}

/**
* Returns markdown report of size differences.
*/
async function createReport() {
const [base, pr] = process.argv.slice(2);
const baseFiles = await minifiedFiles(base);
const prFiles = await minifiedFiles(pr);

const {
added: addedFiles,
removed: removedFiles,
changed: changedFiles,
} = itemChanges(baseFiles, prFiles);

let md = reportHeader();
md += "\n\n";

if (addedFiles.length > 0) {
md += reportAddedFilesSection(base, pr, addedFiles);
md += "\n";
}

if (removedFiles.length > 0) {
md += reportRemovedFilesSection(base, pr, removedFiles);
md += "\n";
}

md += reportChangedFilesSection(base, pr, changedFiles);

return md;
}

(async () => {
console.log(await createReport());
})();

0 comments on commit e71d91d

Please sign in to comment.