From 02fcded00616abc35c1f122abd30aed1106d358c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 17:35:18 +0000 Subject: [PATCH 1/2] Initial plan From a673fc810d7d5adb0cac262e12f5afef99ea001d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 26 Jul 2026 17:41:05 +0000 Subject: [PATCH 2/2] Add page asset size analysis: script and GitHub Actions workflow - scripts/analyze-page-sizes.mjs: walks _site HTML pages, extracts local asset references (CSS, JS, images), sums per-page weights, and emits a Markdown report sorted by decreasing total size with a collapsible detailed breakdown. - .github/workflows/page-asset-sizes.yml: PR workflow that builds the Jekyll site, runs the analysis, and posts/updates a PR comment. - dev.sh: adds `page-sizes` command to run the script locally. Closes #39 --- .github/workflows/page-asset-sizes.yml | 52 ++++++ dev.sh | 10 +- scripts/analyze-page-sizes.mjs | 234 +++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/page-asset-sizes.yml create mode 100644 scripts/analyze-page-sizes.mjs diff --git a/.github/workflows/page-asset-sizes.yml b/.github/workflows/page-asset-sizes.yml new file mode 100644 index 0000000..224602b --- /dev/null +++ b/.github/workflows/page-asset-sizes.yml @@ -0,0 +1,52 @@ +name: Page Asset Size Report + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +jobs: + analyze: + name: Analyze page weights + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" + + - name: Install Jekyll + run: | + gem install jekyll -v 3.9.0 + gem install kramdown-parser-gfm -v 1.1.0 + + - name: Build site + run: jekyll build + env: + JEKYLL_ENV: production + + - name: Analyze page sizes + run: node scripts/analyze-page-sizes.mjs > /tmp/page-size-report.md + + - name: Find existing report comment + id: find-comment + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: github-actions[bot] + body-includes: "📦 Page Asset Size Report" + + - name: Post or update report comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-file: /tmp/page-size-report.md + edit-mode: replace diff --git a/dev.sh b/dev.sh index 7f30539..85b471e 100755 --- a/dev.sh +++ b/dev.sh @@ -9,6 +9,7 @@ # ./dev.sh covers # ./dev.sh social-images [_posts/path/to/post.md ...] # ./dev.sh check-assets +# ./dev.sh page-sizes [--site ] # ./dev.sh git-setup set -euo pipefail @@ -80,6 +81,10 @@ cmd_check_assets() { node scripts/check-assets.mjs "$@" } +cmd_page_sizes() { + node scripts/analyze-page-sizes.mjs "$@" +} + cmd_init() { local script_path script_path="$(cd "$(dirname "$0")" && pwd)/dev.sh" @@ -99,7 +104,7 @@ cmd_init() { cmd_completion() { cat <<'EOF' _dev_sh_completion() { - local commands=(build run draft covers social-images check-assets git-setup) + local commands=(build run draft covers social-images check-assets page-sizes git-setup) local cur="${words[CURRENT]}" if (( CURRENT == 2 )); then compadd -a commands @@ -125,11 +130,12 @@ case "$command" in covers) cmd_covers ;; social-images) cmd_social_images "$@" ;; check-assets) cmd_check_assets "$@" ;; + page-sizes) cmd_page_sizes "$@" ;; git-setup) cmd_git_setup ;; init) cmd_init ;; completion) cmd_completion ;; *) - echo "Unknown command '${command}'. Available: build, run, draft, covers, social-images, check-assets, git-setup" + echo "Unknown command '${command}'. Available: build, run, draft, covers, social-images, check-assets, page-sizes, git-setup" exit 1 ;; esac diff --git a/scripts/analyze-page-sizes.mjs b/scripts/analyze-page-sizes.mjs new file mode 100644 index 0000000..d9ec464 --- /dev/null +++ b/scripts/analyze-page-sizes.mjs @@ -0,0 +1,234 @@ +#!/usr/bin/env node +// Analyze the per-page download weight of the built Jekyll site and print a +// Markdown report sorted by decreasing total page size (HTML + CSS + JS + +// images). +// +// Usage: +// node scripts/analyze-page-sizes.mjs [--site ] +// +// Writes the Markdown report to stdout. + +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { extname, join, resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + +// --------------------------------------------------------------------------- +// CLI argument helper +// --------------------------------------------------------------------------- + +function parseFlag(flag) { + const idx = process.argv.indexOf(flag); + return idx !== -1 ? process.argv[idx + 1] : null; +} + +const siteDir = resolve(parseFlag("--site") ?? join(root, "_site")); + +// --------------------------------------------------------------------------- +// File system helpers +// --------------------------------------------------------------------------- + +function safeStatSize(filePath) { + try { + return statSync(filePath).size; + } catch { + return 0; + } +} + +function walkHtml(dir) { + const results = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...walkHtml(full)); + } else if (entry.name.endsWith(".html")) { + results.push(full); + } + } + return results; +} + +// --------------------------------------------------------------------------- +// Asset URL extraction (no external deps — regex over raw HTML) +// --------------------------------------------------------------------------- + +const EXTERNAL_RE = /^(?:https?:)?\/\//; +const DATA_RE = /^data:/; + +function isLocal(url) { + return url && !EXTERNAL_RE.test(url) && !DATA_RE.test(url); +} + +function cleanUrl(url) { + return url.split("?")[0].split("#")[0]; +} + +function extractAssetUrls(html) { + const urls = new Set(); + + // — stylesheets and preloads + for (const m of html.matchAll(/]*\bhref=["']([^"']+)["'][^>]*>/gi)) { + const u = cleanUrl(m[1]); + if (isLocal(u)) urls.add(u); + } + + //