Skip to content

Commit 014baca

Browse files
committed
Add CI workflow to compute diff between files dist files
1 parent 57f7314 commit 014baca

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/**
2+
* Generate a markdown table with the difference in size of the dist files between the base and the PR.
3+
*/
4+
5+
/*
6+
Usage:
7+
```shell
8+
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}}' \
9+
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}}' \
10+
GITHUB_REPOSITORY='symfony/ux' \
11+
GITHUB_HEAD_REF='my-branch-name' \
12+
node .github/generate-dist-files-size-diff.mjs
13+
```
14+
*/
15+
16+
if (!process.env.BASE_DIST_FILES) {
17+
throw new Error('Missing or invalid "BASE_DIST_FILES" env variable.');
18+
}
19+
20+
if (!process.env.PR_DIST_FILES) {
21+
throw new Error('Missing or invalid "PR_DIST_FILES" env variable.');
22+
}
23+
24+
if (!process.env.GITHUB_REPOSITORY) {
25+
throw new Error('Missing or invalid "GITHUB_REPOSITORY" env variable.');
26+
}
27+
28+
if (!process.env.GITHUB_HEAD_REF) {
29+
throw new Error('Missing or invalid "GITHUB_HEAD_REF" env variable.');
30+
}
31+
32+
/**
33+
* Adapted from https://gist.github.com/zentala/1e6f72438796d74531803cc3833c039c?permalink_comment_id=4455218#gistcomment-4455218
34+
* @param {number} bytes
35+
* @param {number} digits
36+
* @returns {string}
37+
*/
38+
function formatBytes(bytes, digits = 2) {
39+
if (bytes === 0) {
40+
return '0 B';
41+
}
42+
const sizes = [`B`, 'kB', 'MB'];
43+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
44+
45+
return parseFloat((bytes / Math.pow(1024, i)).toFixed(digits)) + ' ' + sizes[i];
46+
}
47+
48+
/**
49+
* @param {number} from
50+
* @param {number} to
51+
* @returns {number}
52+
*/
53+
function computeDiffPercent(from, to) {
54+
if (from === to) {
55+
return 0;
56+
}
57+
58+
return Math.round((from - to) / from * -100);
59+
}
60+
61+
/**
62+
* @param {number} percent
63+
* @returns {string}
64+
*/
65+
function formatDiffPercent(percent) {
66+
return percent > 0 ? `+${percent}% 📈` : percent < 0 ? `${percent}% 📉` : `${percent}%`;
67+
}
68+
69+
export function main() {
70+
const repoUrl = `https://github.com/${process.env.GITHUB_REPOSITORY}`;
71+
/** @type {Record<string, {size: number, size_gz: number}>} */
72+
const base = JSON.parse(process.env.BASE_DIST_FILES);
73+
/** @type {Record<string, {size: number, size_gz: number}>} */
74+
const pr = JSON.parse(process.env.PR_DIST_FILES);
75+
let output = '<h1>📊 Dist packagesFiles size difference</h1>\n\n';
76+
77+
/**
78+
* @type {Map<string, {
79+
* meta: {
80+
* packageName: string,
81+
* bridgeName: string,
82+
* url: string,
83+
* },
84+
* files: Set<{
85+
* state: 'added' | 'removed' | 'changed',
86+
* before: {size: number, sizeGz: number},
87+
* after: {size: number, sizeGz: number},
88+
* diffPercent: {size: number, sizeGz: number},
89+
* meta: {fileNameShort: string, fileNameUrl: string}
90+
* }>
91+
* }>}
92+
*/
93+
const packagesFiles = [...new Set([...Object.keys(pr), ...Object.keys(base)])]
94+
.sort()
95+
.reduce((acc, file) => {
96+
const beforeSize = base[file]?.size || 0;
97+
const afterSize = pr[file]?.size || 0;
98+
const beforeSizeGz = base[file]?.size_gz || 0;
99+
const afterSizeGz = pr[file]?.size_gz || 0;
100+
101+
if (beforeSize !== afterSize) {
102+
const isBridge = file.includes('src/Bridge'); // we assume that's enough for now
103+
const packageName = file.split('/')[1];
104+
const bridgeName = isBridge ? file.split('/')[4] : '';
105+
const key = isBridge ? `${packageName} (Bridge ${bridgeName})` : packageName;
106+
if (!acc.has(key)) {
107+
acc.set(key, {
108+
meta: {
109+
packageName,
110+
bridgeName,
111+
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`,
112+
}, files: new Set(),
113+
});
114+
}
115+
116+
const added = !base[file] && pr[file];
117+
const removed = base[file] && !pr[file];
118+
119+
acc.get(key).files.add({
120+
state: added ? 'added' : (removed ? 'removed' : 'changed'),
121+
before: { size: beforeSize, sizeGz: beforeSizeGz },
122+
after: { size: afterSize, sizeGz: afterSizeGz },
123+
diffPercent: {
124+
size: removed ? -100 : (added ? 100 : computeDiffPercent(beforeSize, afterSize)),
125+
sizeGz: removed ? -100 : (added ? 100 : computeDiffPercent(beforeSizeGz, afterSizeGz)),
126+
},
127+
meta: {
128+
fileNameShort: file.replace(isBridge ? `src/${file.split('/')[1]}/src/Bridge/${file.split('/')[4]}/assets/dist/` : `src/${file.split('/')[1]}/assets/dist/`, ''),
129+
fileNameUrl: `${repoUrl}/blob/${process.env.GITHUB_HEAD_REF}/${file}`,
130+
},
131+
});
132+
}
133+
134+
return acc;
135+
}, new Map);
136+
137+
if (packagesFiles.size === 0) {
138+
output += 'ℹ️ No difference in dist packagesFiles.\n';
139+
return output;
140+
}
141+
142+
output += 'Thanks for the PR! Here is the difference in size of the dist packagesFiles between the base and the PR.\n';
143+
output += 'Please review the changes and make sure they are expected.\n\n';
144+
output += `<table>
145+
<thead><tr><th>File</th><th>Before (Size / Gzip)</th><th>After (Size / Gzip)</th></tr></thead>
146+
<tbody>`;
147+
for (const [pkgKey, pkg] of packagesFiles.entries()) {
148+
output += `<tr><td colspan="3"><a href="${pkg.meta.url}"><b>${pkgKey}</b></a></td></tr>`;
149+
for (const file of pkg.files) {
150+
output += `<tr>
151+
<td><a href="${file.meta.fileNameUrl}"><code>${file.meta.fileNameShort}</code></a></td>
152+
`;
153+
output += file.state === 'added'
154+
? `<td><em>Added</em></td>`
155+
: `<td>
156+
<code>${formatBytes(file.before.size)}</code>
157+
/ <code>${formatBytes(file.before.sizeGz)}</code>
158+
</td>`;
159+
output += file.state === 'removed'
160+
? `<td><em>Removed</em></td>`
161+
: `<td>
162+
<code>${formatBytes(file.after.size)}</code>${file.state === 'changed' ? `<sup>${formatDiffPercent(file.diffPercent.size)}</sup>` : ''}
163+
/ <code>${formatBytes(file.after.sizeGz)}</code>${file.state === 'changed' ? `<sup>${formatDiffPercent(file.diffPercent.sizeGz)}</sup>` : ''}
164+
</td>`;
165+
output += `</tr>`;
166+
}
167+
}
168+
output += `</tbody>
169+
</table>
170+
`;
171+
172+
return output;
173+
}
174+
175+
if (!process.env.CI) {
176+
console.log(main());
177+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Dist Files Size Diff
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'src/*/assets/dist/**'
7+
- 'src/*/src/Bridge/*/assets/dist/**'
8+
9+
jobs:
10+
dist-files-size-diff:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
pull-requests: write # for marocchino/sticky-pull-request-comment@v2
14+
steps:
15+
- name: Configure git
16+
run: |
17+
git config --global user.email ""
18+
git config --global user.name "github-action[bot]"
19+
20+
- uses: marocchino/sticky-pull-request-comment@v2
21+
with:
22+
message: |
23+
⏳ The dist files size difference is being calculated...
24+
25+
- uses: actions/checkout@v4
26+
with:
27+
ref: ${{ github.base_ref }}
28+
29+
- name: Get dist files size (from base branch)
30+
id: base-dist-files
31+
run: |
32+
set -e
33+
34+
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
35+
echo "{\"$file\": {\"size\": $(wc -c < "$file"), \"size_gz\": $(gzip -c "$file" | wc -c)}}"
36+
done | jq -s 'add' -c)
37+
38+
echo "files=$FILES" >> $GITHUB_OUTPUT
39+
40+
- uses: actions/checkout@v4
41+
42+
- name: Get dist files size (from pull request)
43+
id: pr-dist-files
44+
run: |
45+
set -e
46+
47+
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
48+
echo "{\"$file\": {\"size\": $(wc -c < "$file"), \"size_gz\": $(gzip -c "$file" | wc -c)}}"
49+
done | jq -s 'add' -c)
50+
51+
echo "files=$FILES" >> $GITHUB_OUTPUT
52+
53+
- name: Generate the diff
54+
id: diff
55+
uses: actions/github-script@v7
56+
env:
57+
BASE_DIST_FILES: ${{ steps.base-dist-files.outputs.files }}
58+
PR_DIST_FILES: ${{ steps.pr-dist-files.outputs.files }}
59+
with:
60+
result-encoding: string
61+
script: |
62+
const { main } = await import('${{ github.workspace }}/.github/generate-dist-files-size-diff.mjs')
63+
64+
return await main()
65+
66+
- name: Comment on the pull request (if any failure)
67+
if: ${{ failure() }}
68+
uses: marocchino/sticky-pull-request-comment@v2
69+
with:
70+
message: |
71+
❌ The dist files size difference could not be calculated. Please check the logs for more details.
72+
73+
- name: Comment on the pull request (if success)
74+
if: ${{ always() && steps.diff.conclusion == 'success' }}
75+
uses: marocchino/sticky-pull-request-comment@v2
76+
with:
77+
message: ${{ steps.diff.outputs.result }}

0 commit comments

Comments
 (0)