|
| 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