size analysis workflow #76
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: | | |
bun run build | |
mkdir -p /tmp/dist-pr | |
cp -r dist/* /tmp/dist-pr/ | |
cp stats.json /tmp/dist-pr/ | |
- name: Build main branch | |
continue-on-error: true | |
id: main-build | |
run: | | |
git stash -u | |
git checkout main | |
bun install | |
if bun run build; then | |
mkdir -p /tmp/dist-main | |
cp -r dist/* /tmp/dist-main/ | |
cp stats.json /tmp/dist-main/ || true | |
echo "main_build_success=true" >> $GITHUB_OUTPUT | |
else | |
echo "main_build_success=false" >> $GITHUB_OUTPUT | |
fi | |
- name: Create analysis script | |
run: | | |
echo 'import { readFileSync, readdirSync, writeFileSync } from "fs"; | |
function analyzeBundle(buildPath) { | |
try { | |
const stats = JSON.parse(readFileSync(`${buildPath}/stats.json`, "utf-8")); | |
// Calculate total size from all modules | |
const totalSize = stats.children?.[0]?.gzipSize || 0; | |
// Analyze chunks | |
const chunks = { | |
main: 0, | |
vendors: 0, | |
runtime: 0 | |
}; | |
// Handle new visualizer format | |
if (stats.children?.[0]?.groups) { | |
stats.children[0].groups.forEach(group => { | |
if (group.label.includes("vendor")) chunks.vendors += group.gzipSize || 0; | |
else if (group.label.includes("runtime")) chunks.runtime += group.gzipSize || 0; | |
else chunks.main += group.gzipSize || 0; | |
}); | |
} | |
// Analyze dependencies | |
const deps = {}; | |
if (stats.children?.[0]?.groups) { | |
stats.children[0].groups | |
.filter(g => g.label.includes("node_modules")) | |
.forEach(g => { | |
const depName = g.label.split("node_modules/")[1]?.split("/")[0]; | |
if (depName) { | |
deps[depName] = (deps[depName] || 0) + (g.gzipSize || 0); | |
} | |
}); | |
} | |
return { | |
totalSize, | |
chunks, | |
dependencies: Object.entries(deps) | |
.sort(([,a], [,b]) => b - a) | |
.slice(0, 6) // Top 6 dependencies | |
}; | |
} catch (error) { | |
console.error(`Error analyzing bundle at ${buildPath}:`, error); | |
return null; | |
} | |
} | |
function formatSize(bytes) { | |
return (bytes / 1024).toFixed(1) + " KB"; | |
} | |
const prAnalysis = analyzeBundle("/tmp/dist-pr") || { | |
totalSize: 0, | |
chunks: { main: 0, vendors: 0, runtime: 0 }, | |
dependencies: [] | |
}; | |
const mainAnalysis = process.env.MAIN_BUILD_SUCCESS === "true" | |
? analyzeBundle("/tmp/dist-main") | |
: null; | |
const report = { | |
totalSize: formatSize(prAnalysis.totalSize), | |
chunks: Object.entries(prAnalysis.chunks).map(([name, size]) => ({ | |
name, | |
size: formatSize(size), | |
// Only include diff if mainAnalysis exists and has the chunk | |
diff: mainAnalysis && mainAnalysis.chunks[name] !== undefined | |
? formatSize(size - mainAnalysis.chunks[name]) | |
: null | |
})), | |
dependencies: prAnalysis.dependencies.map(([name, size]) => ({ | |
name, | |
size: formatSize(size), | |
// Only include diff if mainAnalysis exists and has the dependency | |
diff: mainAnalysis?.dependencies.find(([n]) => n === name)?.[1] !== undefined | |
? formatSize(size - mainAnalysis.dependencies.find(([n]) => n === name)[1]) | |
: null | |
})) | |
}; | |
console.log("Writing output:", JSON.stringify(report)); | |
writeFileSync("output.json", JSON.stringify(report));' > analyze.js | |
- name: Generate size report | |
id: size-report | |
run: | | |
MAIN_BUILD_SUCCESS=${{ steps.main-build.outputs.main_build_success }} node analyze.js | |
echo "report=$(cat output.json)" >> $GITHUB_OUTPUT | |
- name: Add PR Comment | |
uses: mshick/add-pr-comment@v2 | |
with: | |
message: | | |
# 📦 Bundle Size Analysis | |
Total Bundle Size: ${{ fromJSON(steps.size-report.outputs.totalSize) }} | |
## Chunk Distribution | |
``` | |
${{ join(fromJSON(steps.size-report.outputs.chunks).*.name, ': ') }} | |
${{ join(fromJSON(steps.size-report.outputs.chunks).*.size, ' ') }} | |
${{ steps.main-build.outputs.main_build_success == 'true' && | |
join(fromJSON(steps.size-report.outputs.chunks).*.diff, ' ') || '' }} | |
``` | |
## Top Dependencies | |
``` | |
${{ join(fromJSON(steps.size-report.outputs.dependencies).*.name, ': ') }} | |
${{ join(fromJSON(steps.size-report.outputs.dependencies).*.size, ' ') }} | |
${{ steps.main-build.outputs.main_build_success == 'true' && | |
join(fromJSON(steps.size-report.outputs.dependencies).*.diff, ' ') || '' }} | |
``` | |
proxy-url: https://add-pr-comment-proxy-tscircuit.vercel.app/api | |
repo-token: ${{ secrets.GITHUB_TOKEN }} | |
allow-repeats: false | |