From bd04a12397aa99a1ccfd8ffb9e9346d0428bb12f Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sun, 13 Oct 2024 08:42:46 +0200 Subject: [PATCH] Add CI workflow to compute diff between files dist files --- .github/generate-dist-files-size-diff.mjs | 118 ++++++++++++++++++++ .github/workflows/dist-files-size-diff.yaml | 78 +++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 .github/generate-dist-files-size-diff.mjs create mode 100644 .github/workflows/dist-files-size-diff.yaml diff --git a/.github/generate-dist-files-size-diff.mjs b/.github/generate-dist-files-size-diff.mjs new file mode 100644 index 0000000000..6df0b3c575 --- /dev/null +++ b/.github/generate-dist-files-size-diff.mjs @@ -0,0 +1,118 @@ +/** + * 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,"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}}' \ +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}}' \ + 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.'); +} + +/** + * 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 Number(((from - to) / to * -100).toFixed(2)); +} + +/** + * @param {number} percent + * @returns {string} + */ +function formatDiffPercent(percent) { + return percent > 0 ? `+${percent}% 📈` : percent < 0 ? `${percent}% 📉` : `${percent}%`; +} + +export function main() { + let base = JSON.parse(process.env.BASE_DIST_FILES); + let pr = JSON.parse(process.env.PR_DIST_FILES); + let output = '

📊 Dist files size difference

\n\n'; + + const files = [...new Set([...Object.keys(pr), ...Object.keys(base)])].sort().reduce((acc, file) => { + const added = !base[file] && pr[file]; + const removed = base[file] && !pr[file]; + const diff_percent_size = removed ? -100 : (added ? 100 : (computeDiffPercent(base[file].size, pr[file].size))); + const diff_percent_size_gz = removed ? -100 : (added ? 100 : (computeDiffPercent(base[file].size_gz, pr[file].size_gz))); + const diff_percent_size_brotli = removed ? -100 : (added ? 100 : (computeDiffPercent(base[file].size_brotli, pr[file].size_brotli))); + + if (diff_percent_size !== 0 && diff_percent_size_gz !== 0 && diff_percent_size_brotli !== 0) { + acc.set(file, { + state: added ? 'added' : (removed ? 'removed' : 'changed'), + diff_percent_size, + diff_percent_size_gz, + diff_percent_size_brotli + }); + } + + return acc; + }, new Map); + + if (files.size === 0) { + output += 'ℹī¸ No difference in dist files.\n'; + return output; + } + + output += 'Thanks for the PR! Here is the difference in size of the dist files between the base and the PR.\n'; + output += 'Please review the changes and make sure they are expected.\n\n'; + output += ` + + `; + for (const [file, details] of files.entries()) { + output += ` + + + + + `; + } + output += ` +
FileDiff (B)Diff (%)
${file} ${details.state === 'added' ? '(new)' : (details.state === 'removed' ? '(deleted)' : '')} + Size: ${formatBytes(base[file]?.size || 0)} → ${formatBytes(pr[file]?.size || 0)}
+ Gzip: ${formatBytes(base[file]?.size_gz || 0)} → ${formatBytes(pr[file]?.size_gz || 0)}
+ Brotli: ${formatBytes(base[file]?.size_brotli || 0)} → ${formatBytes(pr[file]?.size_brotli || 0)} +
+ Size: ${formatDiffPercent(details.diff_percent_size)}
+ Gzip: ${formatDiffPercent(details.diff_percent_size_gz)}
+ Brotli: ${formatDiffPercent(details.diff_percent_size_brotli)} +
+`; + + return output; +} + +if (!process.env.CI) { + console.log(main()); +} diff --git a/.github/workflows/dist-files-size-diff.yaml b/.github/workflows/dist-files-size-diff.yaml new file mode 100644 index 0000000000..679cec6787 --- /dev/null +++ b/.github/workflows/dist-files-size-diff.yaml @@ -0,0 +1,78 @@ +name: Dist Files Size Diff + +on: + pull_request: + paths: + - 'src/*/**' + - '!src/*/doc/**' + - '.github/**' + +jobs: + dist-files-size-diff: + runs-on: ubuntu-latest + permissions: + pull-requests: write # for marocchino/sticky-pull-request-comment@v2 + steps: + - name: Configure git + run: | + git config --global user.email "" + git config --global user.name "github-action[bot]" + + - uses: marocchino/sticky-pull-request-comment@v2 + with: + message: | + âŗ The dist files size difference is being calculated... + + - 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), \"size_brotli\": $(brotli -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), \"size_brotli\": $(brotli -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 { main } = await import('${{ github.workspace }}/.github/generate-dist-files-size-diff.mjs') + + return await main() + + - name: Comment on the pull request (if any failure) + if: ${{ failure() }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + message: | + ❌ The dist files size difference could not be calculated. Please check the logs for more details. + + - name: Comment on the pull request (if success) + if: ${{ always() && steps.diff.conclusion == 'success' }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + message: ${{ steps.diff.outputs.result }}