-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(chore) Fix build size report (#3840)
* 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
1 parent
2dd2e4f
commit e71d91d
Showing
5 changed files
with
290 additions
and
30 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
})(); |