|
| 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,"size_brotli":3125},"src/Chartjs/assets/dist/controller.js":{"size":2281,"size_gz":771,"size_brotli":642},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475,"size_brotli":371}}' \ |
| 9 | +PR_DIST_FILES='{"src/Chartjs/assets/dist/controller.js":{"size":2281,"size_gz":771,"size_brotli":642},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475,"size_brotli":371},"src/Cropperjs/assets/dist/style.min.css":{"size":32,"size_gz":66,"size_brotli":34},"src/Dropzone/assets/dist/controller.js":{"size":3199,"size_gz":816,"size_brotli":634}}' \ |
| 10 | + node .github/generate-dist-files-size-diff.mjs |
| 11 | +``` |
| 12 | + */ |
| 13 | + |
| 14 | +if (!process.env.BASE_DIST_FILES) { |
| 15 | + throw new Error('Missing or invalid "BASE_DIST_FILES" env variable.'); |
| 16 | +} |
| 17 | + |
| 18 | +if (!process.env.PR_DIST_FILES) { |
| 19 | + throw new Error('Missing or invalid "PR_DIST_FILES" env variable.'); |
| 20 | +} |
| 21 | + |
| 22 | +/** |
| 23 | + * Adapted from https://gist.github.com/zentala/1e6f72438796d74531803cc3833c039c?permalink_comment_id=4455218#gistcomment-4455218 |
| 24 | + * @param {number} bytes |
| 25 | + * @param {number} digits |
| 26 | + * @returns {string} |
| 27 | + */ |
| 28 | +function formatBytes(bytes, digits = 2) { |
| 29 | + if (bytes === 0) { |
| 30 | + return '0 B'; |
| 31 | + } |
| 32 | + const sizes = [`B`, 'kB', 'MB']; |
| 33 | + const i = Math.floor(Math.log(bytes) / Math.log(1024)); |
| 34 | + |
| 35 | + return parseFloat((bytes / Math.pow(1024, i)).toFixed(digits)) + ' ' + sizes[i]; |
| 36 | +} |
| 37 | + |
| 38 | +/** |
| 39 | + * @param {number} from |
| 40 | + * @param {number} to |
| 41 | + * @returns {number} |
| 42 | + */ |
| 43 | +function computeDiffPercent(from, to) { |
| 44 | + if (from === to) { |
| 45 | + return 0; |
| 46 | + } |
| 47 | + |
| 48 | + return Number(((from - to) / to * -100).toFixed(2)); |
| 49 | +} |
| 50 | + |
| 51 | +/** |
| 52 | + * @param {number} percent |
| 53 | + * @returns {string} |
| 54 | + */ |
| 55 | +function formatDiffPercent(percent) { |
| 56 | + return percent > 0 ? `+${percent}% 📈` : percent < 0 ? `${percent}% 📉` : `${percent}%`; |
| 57 | +} |
| 58 | + |
| 59 | +export function main() { |
| 60 | + let base = JSON.parse(process.env.BASE_DIST_FILES); |
| 61 | + let pr = JSON.parse(process.env.PR_DIST_FILES); |
| 62 | + let output = '<h1>📊 Dist files size difference</h1>\n\n'; |
| 63 | + |
| 64 | + const files = [...new Set([...Object.keys(pr), ...Object.keys(base)])].sort().reduce((acc, file) => { |
| 65 | + const added = !base[file] && pr[file]; |
| 66 | + const removed = base[file] && !pr[file]; |
| 67 | + const diff_percent_size = removed ? -100 : (added ? 100 : (computeDiffPercent(base[file].size, pr[file].size))); |
| 68 | + const diff_percent_size_gz = removed ? -100 : (added ? 100 : (computeDiffPercent(base[file].size_gz, pr[file].size_gz))); |
| 69 | + const diff_percent_size_brotli = removed ? -100 : (added ? 100 : (computeDiffPercent(base[file].size_brotli, pr[file].size_brotli))); |
| 70 | + |
| 71 | + if (diff_percent_size !== 0 && diff_percent_size_gz !== 0 && diff_percent_size_brotli !== 0) { |
| 72 | + acc.set(file, { |
| 73 | + state: added ? 'added' : (removed ? 'removed' : 'changed'), |
| 74 | + diff_percent_size, |
| 75 | + diff_percent_size_gz, |
| 76 | + diff_percent_size_brotli |
| 77 | + }); |
| 78 | + } |
| 79 | + |
| 80 | + return acc; |
| 81 | + }, new Map); |
| 82 | + |
| 83 | + if (files.size === 0) { |
| 84 | + output += 'ℹ️ No difference in dist files.\n'; |
| 85 | + return output; |
| 86 | + } |
| 87 | + |
| 88 | + output += 'Thanks for the PR! Here is the difference in size of the dist files between the base and the PR.\n'; |
| 89 | + output += 'Please review the changes and make sure they are expected.\n\n'; |
| 90 | + output += `<table> |
| 91 | + <thead><tr><th>File</th><th>Diff (B)</th><th>Diff (%)</th></tr></thead> |
| 92 | + <tbody>`; |
| 93 | + for (const [file, details] of files.entries()) { |
| 94 | + output += `<tr> |
| 95 | + <td><code>${file}</code> ${details.state === 'added' ? '(new)' : (details.state === 'removed' ? '(deleted)' : '')}</td> |
| 96 | + <td> |
| 97 | + Size: <code>${formatBytes(base[file]?.size || 0)}</code> → <code>${formatBytes(pr[file]?.size || 0)}</code><br> |
| 98 | + Gzip: <code>${formatBytes(base[file]?.size_gz || 0)}</code> → <code>${formatBytes(pr[file]?.size_gz || 0)}</code><br> |
| 99 | + Brotli: <code>${formatBytes(base[file]?.size_brotli || 0)}</code> → <code>${formatBytes(pr[file]?.size_brotli || 0)}</code> |
| 100 | + </td> |
| 101 | + <td align="right"> |
| 102 | + Size: <b>${formatDiffPercent(details.diff_percent_size)}</b><br> |
| 103 | + Gzip: <b>${formatDiffPercent(details.diff_percent_size_gz)}</b><br> |
| 104 | + Brotli: <b>${formatDiffPercent(details.diff_percent_size_brotli)}</b> |
| 105 | + </td> |
| 106 | + </tr> |
| 107 | + `; |
| 108 | + } |
| 109 | + output += `</tbody> |
| 110 | +</table> |
| 111 | +`; |
| 112 | + |
| 113 | + return output; |
| 114 | +} |
| 115 | + |
| 116 | +if (!process.env.CI) { |
| 117 | + console.log(main()); |
| 118 | +} |
0 commit comments