size analysis workflow #63
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Bundle Analysis | |
on: | |
pull_request: | |
branches: [main] | |
jobs: | |
analyze: | |
runs-on: ubuntu-latest | |
timeout-minutes: 5 | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v2 | |
with: | |
fetch-depth: 0 | |
- name: Setup bun | |
uses: oven-sh/setup-bun@v1 | |
with: | |
bun-version: latest | |
- name: Install dependencies | |
run: bun install | |
- name: Build PR branch | |
run: | | |
echo "Building PR branch..." | |
bun run build | |
mkdir -p /tmp/dist-pr | |
cp -r dist/* /tmp/dist-pr/ | |
- name: Try build main branch | |
continue-on-error: true | |
id: main-build | |
run: | | |
echo "Attempting to build main branch..." | |
git stash -u | |
git checkout main | |
bun install | |
if bun run build; then | |
mkdir -p /tmp/dist-main | |
cp -r dist/* /tmp/dist-main/ | |
echo "main_build_success=true" >> $GITHUB_OUTPUT | |
else | |
echo "main_build_success=false" >> $GITHUB_OUTPUT | |
fi | |
# Restore PR dist | |
rm -rf dist | |
cp -r /tmp/dist-pr/* dist/ | |
- name: Generate size report | |
id: size-report | |
run: | | |
cat > analyze.js << 'SCRIPT' | |
const fs = require('fs'); | |
function analyzeDirectory(dir) { | |
const files = fs.readdirSync(dir); | |
let result = { total: 0, assets: {} }; | |
// First handle files in root of dist | |
files.forEach(file => { | |
const fullPath = `${dir}/${file}`; | |
if (fs.statSync(fullPath).isFile()) { | |
if (file.endsWith('.js') || file.endsWith('.css') || file.endsWith('.html')) { | |
const stats = fs.statSync(fullPath); | |
result.assets[file] = stats.size; | |
result.total += stats.size; | |
} | |
} | |
}); | |
// Then handle assets directory if it exists | |
const assetsPath = `${dir}/assets`; | |
if (fs.existsSync(assetsPath)) { | |
const assetFiles = fs.readdirSync(assetsPath); | |
assetFiles.forEach(file => { | |
if (file.endsWith('.js') || file.endsWith('.css')) { | |
const stats = fs.statSync(`${assetsPath}/${file}`); | |
result.assets[`assets/${file}`] = stats.size; | |
result.total += stats.size; | |
} | |
}); | |
} | |
return result; | |
} | |
function formatSize(bytes) { | |
if (bytes < 1024) return `${bytes.toFixed(2)} B`; | |
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`; | |
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; | |
} | |
try { | |
const sizes = analyzeDirectory('./dist'); | |
// Sort assets by size for better reporting | |
const sortedAssets = Object.entries(sizes.assets) | |
.sort(([, a], [, b]) => b - a); | |
fs.appendFileSync(process.env.GITHUB_OUTPUT, `pr_total=${formatSize(sizes.total)}\n`); | |
// Compare with main if available | |
const hasMainBuild = process.env.MAIN_BUILD_SUCCESS === 'true' && fs.existsSync('/tmp/dist-main'); | |
let diffMessage = 'No comparison available - first PR with bundle analysis'; | |
if (hasMainBuild) { | |
const mainSizes = analyzeDirectory('/tmp/dist-main'); | |
const diff = sizes.total - mainSizes.total; | |
const diffPercent = ((diff / mainSizes.total) * 100).toFixed(2); | |
const diffFormatted = formatSize(Math.abs(diff)); | |
const trend = diff > 0 ? '📈' : '📉'; | |
diffMessage = `${trend} Diff from main: **${diff > 0 ? '+' : '-'}${diffFormatted}** (${diffPercent}%)\nBase | |
size (main): ${formatSize(mainSizes.total)}`; | |
} | |
fs.appendFileSync(process.env.GITHUB_OUTPUT, `diff_message=${diffMessage}\n`); | |
// Format assets list with better formatting | |
const assets = sortedAssets | |
.map(([name, size]) => `${name.padEnd(40)} ${formatSize(size)}`) | |
.join('\\n'); | |
fs.appendFileSync(process.env.GITHUB_OUTPUT, `assets=${assets || 'No assets found'}\n`); | |
// List files larger than 5MB | |
const largeFiles = sortedAssets | |
.filter(([_, size]) => size > 5 * 1024 * 1024) | |
.map(([name, size]) => `${name.padEnd(40)} ${formatSize(size)}`) | |
.join('\\n'); | |
fs.appendFileSync(process.env.GITHUB_OUTPUT, `breakdown=${largeFiles || 'No files over 5MB'}\n`); | |
} catch (error) { | |
console.error('Error processing build output:', error); | |
process.exit(1); | |
} | |
SCRIPT | |
MAIN_BUILD_SUCCESS=${{ steps.main-build.outputs.main_build_success }} node analyze.js | |
- name: Add PR Comment | |
uses: mshick/add-pr-comment@v2 | |
with: | |
message: | | |
# 📦 Bundle Size Report | |
## Total Bundle Size | |
Current: **${{ steps.size-report.outputs.pr_total }}** | |
${{ steps.size-report.outputs.diff_message }} | |
## Large Files (>5MB) | |
``` | |
${{ steps.size-report.outputs.breakdown }} | |
``` | |
## All Assets | |
``` | |
${{ steps.size-report.outputs.assets }} | |
``` | |
<details> | |
<summary>ℹ️ About this report</summary> | |
- Sizes are shown in bytes (B), kilobytes (KB), or megabytes (MB) | |
- Large files are those over 5MB | |
- Asset types include .js, .css, and .html files | |
- Diff shows size change compared to main branch when available | |
</details> | |
proxy-url: https://add-pr-comment-proxy-tscircuit.vercel.app/api | |
repo-token: ${{ secrets.GITHUB_TOKEN }} | |
allow-repeats: false | |