|
| 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":1281,"size_gz":171,"size_brotli":641},"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},"src/Map/src/Bridge/Google/assets/dist/foo.js":{"size":3199,"size_gz":816,"size_brotli":634}}' \ |
| 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 Number(((from - to) / to * -100).toFixed(1)); |
| 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, size_brotli: number}>} */ |
| 72 | + const base = JSON.parse(process.env.BASE_DIST_FILES); |
| 73 | + /** @type {Record<string, {size: number, size_gz: number, size_brotli: 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, sizeBrotli: number}, |
| 87 | + * after: {size: number, sizeGz: number, sizeBrotli: number}, |
| 88 | + * diffPercent: {size: number, sizeGz: number, sizeBrotli: 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 | + const beforeSizeBrotli = base[file]?.size_brotli || 0; |
| 101 | + const afterSizeBrotli = pr[file]?.size_brotli || 0; |
| 102 | + |
| 103 | + if (beforeSize !== afterSize) { |
| 104 | + const isBridge = file.includes('src/Bridge'); // we assume that's enough for now |
| 105 | + const packageName = file.split('/')[1]; |
| 106 | + const bridgeName = isBridge ? file.split('/')[4] : ''; |
| 107 | + const key = isBridge ? `${packageName} (Bridge ${bridgeName})` : packageName; |
| 108 | + if (!acc.has(key)) { |
| 109 | + acc.set(key, { |
| 110 | + meta: { |
| 111 | + packageName, |
| 112 | + bridgeName, |
| 113 | + 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`, |
| 114 | + }, files: new Set(), |
| 115 | + }); |
| 116 | + } |
| 117 | + |
| 118 | + const added = !base[file] && pr[file]; |
| 119 | + const removed = base[file] && !pr[file]; |
| 120 | + |
| 121 | + acc.get(key).files.add({ |
| 122 | + state: added ? 'added' : (removed ? 'removed' : 'changed'), |
| 123 | + before: { size: beforeSize, sizeGz: beforeSizeGz, sizeBrotli: beforeSizeBrotli }, |
| 124 | + after: { size: afterSize, sizeGz: afterSizeGz, sizeBrotli: afterSizeBrotli }, |
| 125 | + diffPercent: { |
| 126 | + size: removed ? -100 : (added ? 100 : (computeDiffPercent(beforeSize, afterSize))), |
| 127 | + sizeGz: removed ? -100 : (added ? 100 : (computeDiffPercent(beforeSizeGz, afterSizeGz))), |
| 128 | + sizeBrotli: removed ? -100 : (added ? 100 : (computeDiffPercent(beforeSizeBrotli, afterSizeBrotli))), |
| 129 | + }, |
| 130 | + meta: { |
| 131 | + fileNameShort: file.replace(isBridge ? `src/${file.split('/')[1]}/src/Bridge/${file.split('/')[4]}/assets/dist/` : `src/${file.split('/')[1]}/assets/dist/`, ''), |
| 132 | + fileNameUrl: `${repoUrl}/blob/${process.env.GITHUB_HEAD_REF}/${file}`, |
| 133 | + }, |
| 134 | + }); |
| 135 | + } |
| 136 | + |
| 137 | + return acc; |
| 138 | + }, new Map); |
| 139 | + |
| 140 | + if (packagesFiles.size === 0) { |
| 141 | + output += 'ℹ️ No difference in dist packagesFiles.\n'; |
| 142 | + return output; |
| 143 | + } |
| 144 | + |
| 145 | + output += 'Thanks for the PR! Here is the difference in size of the dist packagesFiles between the base and the PR.\n'; |
| 146 | + output += 'Please review the changes and make sure they are expected.\n\n'; |
| 147 | + output += `<table> |
| 148 | + <thead><tr><th>File</th><th>Before (Size / Gzip / Brotli)</th><th>After (Size / Gzip / Brotli)</th></tr></thead> |
| 149 | + <tbody>`; |
| 150 | + for (const [pkgKey, pkg] of packagesFiles.entries()) { |
| 151 | + output += `<tr><td colspan="3"><a href="${pkg.meta.url}"><b>${pkgKey}</b></a></td></tr>`; |
| 152 | + for (const file of pkg.files) { |
| 153 | + output += `<tr> |
| 154 | + <td><a href="${file.meta.fileNameUrl}"><code>${file.meta.fileNameShort}</code></a></td> |
| 155 | + `; |
| 156 | + output += file.state === 'added' |
| 157 | + ? `<td><em>Added</em></td>` |
| 158 | + : `<td> |
| 159 | + <code>${formatBytes(file.before.size)}</code> |
| 160 | + / <code>${formatBytes(file.before.sizeGz)}</code> |
| 161 | + / <code>${formatBytes(file.before.sizeBrotli)}</code> |
| 162 | + </td>`; |
| 163 | + output += file.state === 'removed' |
| 164 | + ? `<td><em>Removed</em></td>` |
| 165 | + : `<td> |
| 166 | + <code>${formatBytes(file.after.size)}</code>${file.state === 'changed' ? `: ${formatDiffPercent(file.diffPercent.size)}<br/>` : ' / '} |
| 167 | + <code>${formatBytes(file.after.sizeGz)}</code>${file.state === 'changed' ? `: ${formatDiffPercent(file.diffPercent.sizeGz)}<br/>` : ' / '} |
| 168 | + <code>${formatBytes(file.after.sizeBrotli)}</code>${file.state === 'changed' ? `: ${formatDiffPercent(file.diffPercent.sizeBrotli)}` : ''} |
| 169 | + </td>`; |
| 170 | + output += `</tr>`; |
| 171 | + } |
| 172 | + } |
| 173 | + output += `</tbody> |
| 174 | +</table> |
| 175 | +`; |
| 176 | + |
| 177 | + return output; |
| 178 | +} |
| 179 | + |
| 180 | +if (!process.env.CI) { |
| 181 | + console.log(main()); |
| 182 | +} |
0 commit comments