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
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/[email protected]
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());
})();