Skip to content

Commit

Permalink
minor #2269 Add CI workflow to compute diff between files dist files …
Browse files Browse the repository at this point in the history
…(Kocal)

This PR was squashed before being merged into the 2.x branch.

Discussion
----------

Add CI workflow to compute diff between files dist files

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

## 🚨 Before merging, we should drop the commit that modify files (in order to generate the diff table)

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

This PR is purely internal, and aims to display the `assets/dist/*.{js,css}` files diff between `2.x` and a pull-request. Similar to https://github.com/marketplace/actions/pkg-size-action, but fully internal.

I wanted a tool that display dist files size diff for each pull-request, because I was a bit afraid of changes done in #2160.

When a PR is opened, it check dist files between the base branch (`2.x`) and the pull request, and it create a GitHub comment. The comment is created by https://github.com/marocchino/sticky-pull-request-comment, and is automatically updated depending of the check state.
If any diff between dist files, then a table is displayed, with a line per file. It shows the  original size and compressed (gzip and brotli) sizes, and also a difference in %.

## States

_Currently not working on this repository, but you can see them on https://github.com/Kocal/symfony-ux/pull/1_

###
<img width="931" alt="Capture d’écran 2024-10-13 à 11 45 10" src="https://github.com/user-attachments/assets/288523cc-f8fa-48a7-a1e1-08174403b54d">
When opening a PR

### When an issue happened
<img width="914" alt="Capture d’écran 2024-10-13 à 11 45 19" src="https://github.com/user-attachments/assets/ae0dae2a-1998-498c-935f-e5d42f78889e">

### When there is no difference between base and PR
<img width="949" alt="Capture d’écran 2024-10-13 à 11 25 31" src="https://github.com/user-attachments/assets/0a7a56d8-1183-4341-99f6-ca386f552298">

### When there is difference between base and PR
<img width="913" alt="image" src="https://github.com/user-attachments/assets/9a5ccaf0-96cf-4189-9bc3-2e35157db70a">

Commits
-------

c6d4db6 Add CI workflow to compute diff between files dist files
  • Loading branch information
Kocal committed Nov 3, 2024
2 parents 57f7314 + c6d4db6 commit 137fd2c
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 0 deletions.
177 changes: 177 additions & 0 deletions .github/generate-dist-files-size-diff.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/**
* Generate a markdown table with the difference in size of the dist files between the base and the PR.
*/

/*
Usage:
```shell
BASE_DIST_FILES='{"src/Autocomplete/assets/dist/controller.js":{"size":15382,"size_gz":3716},"src/Chartjs/assets/dist/controller.js":{"size":2281,"size_gz":771},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475}}' \
PR_DIST_FILES='{"src/Chartjs/assets/dist/controller.js":{"size":1281,"size_gz":171},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475},"src/Cropperjs/assets/dist/style.min.css":{"size":32,"size_gz":66},"src/Dropzone/assets/dist/controller.js":{"size":3199,"size_gz":816},"src/Map/src/Bridge/Google/assets/dist/foo.js":{"size":3199,"size_gz":816}}' \
GITHUB_REPOSITORY='symfony/ux' \
GITHUB_HEAD_REF='my-branch-name' \
node .github/generate-dist-files-size-diff.mjs
```
*/

if (!process.env.BASE_DIST_FILES) {
throw new Error('Missing or invalid "BASE_DIST_FILES" env variable.');
}

if (!process.env.PR_DIST_FILES) {
throw new Error('Missing or invalid "PR_DIST_FILES" env variable.');
}

if (!process.env.GITHUB_REPOSITORY) {
throw new Error('Missing or invalid "GITHUB_REPOSITORY" env variable.');
}

if (!process.env.GITHUB_HEAD_REF) {
throw new Error('Missing or invalid "GITHUB_HEAD_REF" env variable.');
}

/**
* Adapted from https://gist.github.com/zentala/1e6f72438796d74531803cc3833c039c?permalink_comment_id=4455218#gistcomment-4455218
* @param {number} bytes
* @param {number} digits
* @returns {string}
*/
function formatBytes(bytes, digits = 2) {
if (bytes === 0) {
return '0 B';
}
const sizes = [`B`, 'kB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));

return parseFloat((bytes / Math.pow(1024, i)).toFixed(digits)) + ' ' + sizes[i];
}

/**
* @param {number} from
* @param {number} to
* @returns {number}
*/
function computeDiffPercent(from, to) {
if (from === to) {
return 0;
}

return Math.round((from - to) / from * -100);
}

/**
* @param {number} percent
* @returns {string}
*/
function formatDiffPercent(percent) {
return percent > 0 ? `+${percent}% 📈` : percent < 0 ? `${percent}% 📉` : `${percent}%`;
}

export function main() {
const repoUrl = `https://github.com/${process.env.GITHUB_REPOSITORY}`;
/** @type {Record<string, {size: number, size_gz: number}>} */
const base = JSON.parse(process.env.BASE_DIST_FILES);
/** @type {Record<string, {size: number, size_gz: number}>} */
const pr = JSON.parse(process.env.PR_DIST_FILES);
let output = '<h1>📊 Dist packagesFiles size difference</h1>\n\n';

/**
* @type {Map<string, {
* meta: {
* packageName: string,
* bridgeName: string,
* url: string,
* },
* files: Set<{
* state: 'added' | 'removed' | 'changed',
* before: {size: number, sizeGz: number},
* after: {size: number, sizeGz: number},
* diffPercent: {size: number, sizeGz: number},
* meta: {fileNameShort: string, fileNameUrl: string}
* }>
* }>}
*/
const packagesFiles = [...new Set([...Object.keys(pr), ...Object.keys(base)])]
.sort()
.reduce((acc, file) => {
const beforeSize = base[file]?.size || 0;
const afterSize = pr[file]?.size || 0;
const beforeSizeGz = base[file]?.size_gz || 0;
const afterSizeGz = pr[file]?.size_gz || 0;

if (beforeSize !== afterSize) {
const isBridge = file.includes('src/Bridge'); // we assume that's enough for now
const packageName = file.split('/')[1];
const bridgeName = isBridge ? file.split('/')[4] : '';
const key = isBridge ? `${packageName} (Bridge ${bridgeName})` : packageName;
if (!acc.has(key)) {
acc.set(key, {
meta: {
packageName,
bridgeName,
url: isBridge ? `${repoUrl}/tree/${process.env.GITHUB_HEAD_REF}/src/${packageName}/src/Bridge/${bridgeName}/assets/dist` : `${repoUrl}/tree/${process.env.GITHUB_HEAD_REF}/src/${packageName}/assets/dist`,
}, files: new Set(),
});
}

const added = !base[file] && pr[file];
const removed = base[file] && !pr[file];

acc.get(key).files.add({
state: added ? 'added' : (removed ? 'removed' : 'changed'),
before: { size: beforeSize, sizeGz: beforeSizeGz },
after: { size: afterSize, sizeGz: afterSizeGz },
diffPercent: {
size: removed ? -100 : (added ? 100 : computeDiffPercent(beforeSize, afterSize)),
sizeGz: removed ? -100 : (added ? 100 : computeDiffPercent(beforeSizeGz, afterSizeGz)),
},
meta: {
fileNameShort: file.replace(isBridge ? `src/${file.split('/')[1]}/src/Bridge/${file.split('/')[4]}/assets/dist/` : `src/${file.split('/')[1]}/assets/dist/`, ''),
fileNameUrl: `${repoUrl}/blob/${process.env.GITHUB_HEAD_REF}/${file}`,
},
});
}

return acc;
}, new Map);

if (packagesFiles.size === 0) {
output += 'ℹ️ No difference in dist packagesFiles.\n';
return output;
}

output += 'Thanks for the PR! Here is the difference in size of the dist packagesFiles between the base and the PR.\n';
output += 'Please review the changes and make sure they are expected.\n\n';
output += `<table>
<thead><tr><th>File</th><th>Before (Size / Gzip)</th><th>After (Size / Gzip)</th></tr></thead>
<tbody>`;
for (const [pkgKey, pkg] of packagesFiles.entries()) {
output += `<tr><td colspan="3"><a href="${pkg.meta.url}"><b>${pkgKey}</b></a></td></tr>`;
for (const file of pkg.files) {
output += `<tr>
<td><a href="${file.meta.fileNameUrl}"><code>${file.meta.fileNameShort}</code></a></td>
`;
output += file.state === 'added'
? `<td><em>Added</em></td>`
: `<td>
<code>${formatBytes(file.before.size)}</code>
/ <code>${formatBytes(file.before.sizeGz)}</code>
</td>`;
output += file.state === 'removed'
? `<td><em>Removed</em></td>`
: `<td>
<code>${formatBytes(file.after.size)}</code>${file.state === 'changed' ? `<sup>${formatDiffPercent(file.diffPercent.size)}</sup>` : ''}
/ <code>${formatBytes(file.after.sizeGz)}</code>${file.state === 'changed' ? `<sup>${formatDiffPercent(file.diffPercent.sizeGz)}</sup>` : ''}
</td>`;
output += `</tr>`;
}
}
output += `</tbody>
</table>
`;

return output;
}

if (!process.env.CI) {
console.log(main());
}
22 changes: 22 additions & 0 deletions .github/workflows/dist-files-size-diff-comment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Dist Files Size Diff (Comment)

on:
workflow_run:
workflows: ["Dist Files Size Diff"]
types:
- completed

jobs:
dist-files-size-diff:
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: dist-size-${{ github.event.number }}

- name: Comment on the pull request (if success)
if: ${{ always() && steps.diff.conclusion == 'success' }}
uses: marocchino/sticky-pull-request-comment@v2
with:
path: ./dist-size.md
68 changes: 68 additions & 0 deletions .github/workflows/dist-files-size-diff.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Dist Files Size Diff

on:
pull_request:
types: [opened, synchronize]
paths:
- 'src/*/assets/dist/**'
- 'src/*/src/Bridge/*/assets/dist/**'

jobs:
dist-files-size-diff:
runs-on: ubuntu-latest
steps:
- name: Configure git
run: |
git config --global user.email ""
git config --global user.name "github-action[bot]"
- uses: actions/checkout@v4
with:
ref: ${{ github.base_ref }}

- name: Get dist files size (from base branch)
id: base-dist-files
run: |
set -e
FILES=$(find src -mindepth 2 -path '*/assets/dist/*' \( -name "*.js" -o -name "*.css" \) -not \( -path '*/tests/*' -o -path '*/public/*' -o -path '*/vendor/*' \) | sort | while read -r file; do
echo "{\"$file\": {\"size\": $(wc -c < "$file"), \"size_gz\": $(gzip -c "$file" | wc -c)}}"
done | jq -s 'add' -c)
echo "files=$FILES" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4

- name: Get dist files size (from pull request)
id: pr-dist-files
run: |
set -e
FILES=$(find src -mindepth 2 -path '*/assets/dist/*' \( -name "*.js" -o -name "*.css" \) -not \( -path '*/tests/*' -o -path '*/public/*' -o -path '*/vendor/*' \) | sort | while read -r file; do
echo "{\"$file\": {\"size\": $(wc -c < "$file"), \"size_gz\": $(gzip -c "$file" | wc -c)}}"
done | jq -s 'add' -c)
echo "files=$FILES" >> $GITHUB_OUTPUT
- name: Generate the diff
id: diff
uses: actions/github-script@v7
env:
BASE_DIST_FILES: ${{ steps.base-dist-files.outputs.files }}
PR_DIST_FILES: ${{ steps.pr-dist-files.outputs.files }}
with:
result-encoding: string
script: |
const fs = require('fs')
const { main } = await import('${{ github.workspace }}/.github/generate-dist-files-size-diff.mjs')
const diff = await main()
console.log(diff);
fs.writeFileSync(process.env.GITHUB_WORKSPACE + '/dist-size.md', diff)
- name: Upload the diff
uses: actions/upload-artifact@v4
with:
name: dist-size-${{ github.event.number }}
path: ./dist-size.md

0 comments on commit 137fd2c

Please sign in to comment.