Skip to content

Commit 5e51d67

Browse files
authored
Merge pull request #343 from ForgeRock/bundle-sizes
chore: add-bundle-size-to-pr
2 parents d8be69f + f95211f commit 5e51d67

3 files changed

Lines changed: 311 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,79 @@ jobs:
8383
uses: marocchino/sticky-pull-request-comment@v2
8484
with:
8585
message: Deployed ${{ github.sha }} to https://ForgeRock.github.io/ping-javascript-sdk/pr-${{ github.event.number }}/${{github.sha}} branch gh-pages in ForgeRock/ping-javascript-sdk
86+
87+
- name: Download baseline bundle sizes
88+
uses: dawidd6/action-download-artifact@v3
89+
with:
90+
workflow: publish.yml
91+
branch: main
92+
name: bundle-size-baseline
93+
path: .
94+
if_no_artifact_found: warn
95+
continue-on-error: true
96+
97+
- name: Calculate bundle sizes
98+
id: bundle-analysis
99+
run: |
100+
# Make script executable
101+
chmod +x ./scripts/bundle-sizes.sh
102+
103+
# Check if baseline exists
104+
if [ -f "previous_sizes.json" ]; then
105+
echo "✅ Using baseline size data from main branch"
106+
echo "Baseline data preview:"
107+
cat previous_sizes.json | head -5
108+
else
109+
echo "⚠️ No baseline size data found - this will be the first measurement"
110+
fi
111+
112+
# Run the bundle size calculation
113+
./scripts/bundle-sizes.sh
114+
115+
- name: Upload current stats as artifact
116+
uses: actions/upload-artifact@v4
117+
with:
118+
name: bundle-size-stats-pr-${{ github.event.pull_request.number }}
119+
path: |
120+
stats.txt
121+
bundle_size_report.md
122+
retention-days: 7
123+
124+
- name: Find bundle size comment
125+
id: find-comment
126+
uses: peter-evans/find-comment@v3
127+
with:
128+
issue-number: ${{ github.event.pull_request.number }}
129+
comment-author: 'github-actions[bot]'
130+
body-includes: <!-- This comment was auto-generated by GitHub Actions to display bundle size statistics -->
131+
132+
- name: Create or update bundle size comment
133+
uses: peter-evans/create-or-update-comment@v4
134+
with:
135+
comment-id: ${{ steps.find-comment.outputs.comment-id }}
136+
issue-number: ${{ github.event.pull_request.number }}
137+
edit-mode: replace
138+
body: |
139+
<!-- This comment was auto-generated by GitHub Actions to display bundle size statistics -->
140+
## 📦 Bundle Size Analysis
141+
142+
${{ steps.bundle-analysis.outputs.stats }}
143+
144+
### Legend
145+
🆕 New package
146+
🔺 Size increased
147+
🔻 Size decreased
148+
➖ No change
149+
150+
<details>
151+
<summary>ℹ️ How bundle sizes are calculated</summary>
152+
153+
- **Current Size**: Total gzipped size of all files in the package's `dist` directory
154+
- **Baseline**: Comparison against the latest build from the `main` branch
155+
- **Files included**: All build outputs except source maps and TypeScript build cache
156+
- **Exclusions**: `.map`, `.tsbuildinfo`, and `.d.ts.map` files
157+
158+
</details>
159+
160+
---
161+
<sub>🔄 Updated automatically on each push to this PR</sub>

.github/workflows/publish.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,25 @@ jobs:
112112
folder: docs
113113
commit-message: 'chore: release-api-docs-beta'
114114
target-folder: 'beta' # we push to beta folder when we are updating "main"
115+
116+
# Calculate and save baseline bundle sizes
117+
- name: Calculate baseline bundle sizes
118+
run: |
119+
chmod +x ./scripts/bundle-sizes.sh
120+
121+
# Don't use any previous baseline for main branch - always create fresh
122+
rm -f previous_sizes.json
123+
124+
echo "📊 Calculating fresh baseline bundle sizes for main branch"
125+
./scripts/bundle-sizes.sh
126+
127+
echo "✅ Baseline bundle sizes calculated"
128+
cat previous_sizes.json
129+
130+
# Save baseline for PR comparisons
131+
- name: Upload baseline bundle sizes
132+
uses: actions/upload-artifact@v4
133+
with:
134+
name: bundle-size-baseline
135+
path: previous_sizes.json
136+
retention-days: 30 # Keep baseline for 30 days

scripts/bundle-sizes.sh

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/bin/bash
2+
3+
# Function to compute bundle size for a package
4+
function compute_bundle_size() {
5+
local package_path="${1}"
6+
local dist_path="${package_path}/dist"
7+
8+
if [[ ! -d "${dist_path}" ]]; then
9+
echo "0"
10+
return
11+
fi
12+
13+
# Calculate total size of relevant files in dist directory
14+
local total_size=0
15+
16+
# Include all files except source maps, TypeScript build info, and declaration maps
17+
while IFS= read -r -d '' file; do
18+
if [[ -f "${file}" ]]; then
19+
# Skip certain file types that aren't part of the actual bundle
20+
if [[ ! "${file}" =~ \.(map|tsbuildinfo|d\.ts\.map)$ ]]; then
21+
local file_size=$(gzip -c "${file}" | wc -c)
22+
total_size=$((total_size + file_size))
23+
fi
24+
fi
25+
done < <(find "${dist_path}" -type f -print0)
26+
27+
echo "${total_size}"
28+
}
29+
30+
# Function to get all package paths from workspace
31+
function get_package_paths() {
32+
local packages=()
33+
34+
# Find all package.json files in packages directory
35+
while IFS= read -r -d '' package_json; do
36+
local package_dir=$(dirname "${package_json}")
37+
if [[ -f "${package_dir}/package.json" ]]; then
38+
packages+=("${package_dir}")
39+
fi
40+
done < <(find packages -name "package.json" -type f -print0)
41+
42+
printf '%s\n' "${packages[@]}"
43+
}
44+
45+
# Function to get package name from package.json
46+
function get_package_name() {
47+
local package_path="${1}"
48+
local package_json="${package_path}/package.json"
49+
50+
if [[ -f "${package_json}" ]]; then
51+
# Extract name field from package.json
52+
node -p "require('./${package_json}').name" 2>/dev/null || basename "${package_path}"
53+
else
54+
basename "${package_path}"
55+
fi
56+
}
57+
58+
# Function to load previous sizes from file
59+
function load_previous_sizes() {
60+
local prev_file="${1:-previous_sizes.json}"
61+
62+
if [[ -f "${prev_file}" ]]; then
63+
cat "${prev_file}"
64+
else
65+
echo "{}"
66+
fi
67+
}
68+
69+
# Function to save current sizes to file
70+
function save_current_sizes() {
71+
local sizes_json="${1}"
72+
local output_file="${2:-previous_sizes.json}"
73+
74+
echo "${sizes_json}" > "${output_file}"
75+
}
76+
77+
# Main function
78+
function main() {
79+
local previous_sizes_file="${1:-previous_sizes.json}"
80+
local output_file="${2:-bundle_size_report.md}"
81+
82+
# Load previous sizes
83+
local previous_sizes=$(load_previous_sizes "${previous_sizes_file}")
84+
85+
# Initialize output with better formatting
86+
local output="## 📦 Bundle Size Analysis\n\n"
87+
88+
# Group packages by size change significance
89+
local significant_changes=""
90+
local minor_changes=""
91+
local no_changes=""
92+
local new_packages=""
93+
94+
# JSON object to store current sizes
95+
local current_sizes="{}"
96+
97+
# Get all package paths
98+
local package_paths=($(get_package_paths))
99+
100+
if [[ ${#package_paths[@]} -eq 0 ]]; then
101+
echo "No packages found in workspace"
102+
exit 1
103+
fi
104+
105+
# Process each package
106+
for package_path in "${package_paths[@]}"; do
107+
local package_name=$(get_package_name "${package_path}")
108+
local current_size=$(compute_bundle_size "${package_path}")
109+
110+
# Get previous size from JSON
111+
local previous_size=$(echo "${previous_sizes}" | node -p "
112+
try {
113+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'));
114+
data['${package_name}'] || 0;
115+
} catch (e) {
116+
0;
117+
}
118+
" 2>/dev/null || echo "0")
119+
120+
# Calculate difference
121+
local diff=$((current_size - previous_size))
122+
local diff_pct="0"
123+
124+
if [[ "${previous_size}" -gt 0 ]]; then
125+
diff_pct=$(awk "BEGIN { printf \"%.1f\", (${diff} / ${previous_size}) * 100 }")
126+
fi
127+
128+
# Format sizes in KB with proper alignment
129+
local current_kb=$(awk "BEGIN { printf \"%.1f\", ${current_size} / 1000 }")
130+
local previous_kb=$(awk "BEGIN { printf \"%.1f\", ${previous_size} / 1000 }")
131+
local diff_kb=$(awk "BEGIN { printf \"%.1f\", ${diff} / 1000 }")
132+
133+
# Calculate percentage for categorization
134+
local abs_diff_pct=$(awk "BEGIN { printf \"%.1f\", ${diff_pct} < 0 ? -${diff_pct} : ${diff_pct} }")
135+
136+
# Create package entry
137+
local package_entry=""
138+
139+
if [[ "${previous_size}" -eq 0 ]]; then
140+
# New package
141+
package_entry="🆕 **${package_name}** - ${current_kb} KB *(new)*"
142+
new_packages+="\n${package_entry}"
143+
elif [[ $(awk "BEGIN { print (${abs_diff_pct} >= 5.0) }") -eq 1 ]] || [[ $(awk "BEGIN { print (${diff} >= 1000 || ${diff} <= -1000) }") -eq 1 ]]; then
144+
# Significant change (>5% or >1KB)
145+
if [[ "${diff}" -gt 0 ]]; then
146+
package_entry="🔺 **${package_name}** - ${current_kb} KB *(+${diff_kb} KB, +${diff_pct}%)*"
147+
else
148+
package_entry="🔻 **${package_name}** - ${current_kb} KB *(${diff_kb} KB, ${diff_pct}%)*"
149+
fi
150+
significant_changes+="\n${package_entry}"
151+
elif [[ "${diff}" -ne 0 ]]; then
152+
# Minor change
153+
if [[ "${diff}" -gt 0 ]]; then
154+
package_entry="📈 **${package_name}** - ${current_kb} KB *(+${diff_kb} KB)*"
155+
else
156+
package_entry="📉 **${package_name}** - ${current_kb} KB *(${diff_kb} KB)*"
157+
fi
158+
minor_changes+="\n${package_entry}"
159+
else
160+
# No change
161+
package_entry="➖ **${package_name}** - ${current_kb} KB"
162+
no_changes+="\n${package_entry}"
163+
fi
164+
165+
# Store current size in JSON
166+
current_sizes=$(echo "${current_sizes}" | node -p "
167+
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'));
168+
data['${package_name}'] = ${current_size};
169+
JSON.stringify(data, null, 2);
170+
")
171+
done
172+
173+
# Build the final output with sections
174+
if [[ -n "${significant_changes}" ]]; then
175+
output+="\n### 🚨 Significant Changes\n${significant_changes}\n"
176+
fi
177+
178+
if [[ -n "${new_packages}" ]]; then
179+
output+="\n### 🆕 New Packages\n${new_packages}\n"
180+
fi
181+
182+
if [[ -n "${minor_changes}" ]]; then
183+
output+="\n<details>\n<summary>📊 Minor Changes</summary>\n${minor_changes}\n</details>\n"
184+
fi
185+
186+
if [[ -n "${no_changes}" ]]; then
187+
output+="\n<details>\n<summary>➖ No Changes</summary>\n${no_changes}\n</details>\n"
188+
fi
189+
190+
# Add summary
191+
local total_packages=${#package_paths[@]}
192+
output+="\n---\n**${total_packages} packages analyzed** • Baseline from latest \`main\` build"
193+
194+
# Write report
195+
echo -e "${output}" > "${output_file}"
196+
197+
# Also write to stats.txt for CI compatibility
198+
echo -e "${output}" > "stats.txt"
199+
200+
# Save current sizes for next run
201+
save_current_sizes "${current_sizes}" "${previous_sizes_file}"
202+
203+
# Output stats for GitHub Actions
204+
echo "stats<<EOF" >> $GITHUB_OUTPUT
205+
echo -e "${output}" >> $GITHUB_OUTPUT
206+
echo "EOF" >> $GITHUB_OUTPUT
207+
208+
echo "Bundle size report generated: ${output_file}"
209+
echo "Current sizes saved to: ${previous_sizes_file}"
210+
}
211+
212+
# Run the script
213+
main "$@"

0 commit comments

Comments
 (0)