From f2efa2456bb06716454de73fc3f6d962c3b5b027 Mon Sep 17 00:00:00 2001 From: rahim Date: Thu, 12 Feb 2026 22:45:06 +1100 Subject: [PATCH] chore(ci): fix bundle size measurement and format report (#512) --- .github/workflows/bundle-size.yml | 130 +++++++++++++++++++++++++----- .size-limit.json | 58 ++++++++++--- package.json | 5 +- packages/html/package.json | 34 ++++---- packages/react/package.json | 29 +++---- packages/store/package.json | 27 ++++--- packages/utils/package.json | 13 +-- pnpm-lock.yaml | 20 ++--- 8 files changed, 221 insertions(+), 95 deletions(-) diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index df1c8388..064c8d2b 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -62,12 +62,12 @@ jobs: } function formatDelta(current, previous) { - if (previous === undefined) return 'โ€”'; + if (previous === undefined) return { bytes: 'โ€”', pct: '' }; const diff = current - previous; - if (diff === 0) return '0 B'; - const sign = diff > 0 ? '+' : ''; - const pct = ((diff / previous) * 100).toFixed(1); - return `${sign}${formatBytes(diff)} (${sign}${pct}%)`; + if (diff === 0) return { bytes: '0 B', pct: '0%' }; + const sign = diff > 0 ? '+' : '-'; + const pct = Math.abs((diff / previous) * 100).toFixed(1); + return { bytes: `${sign}${formatBytes(Math.abs(diff))}`, pct: `${sign}${pct}%` }; } function statusIcon(current, previous) { @@ -79,36 +79,126 @@ jobs: return pct > 10 ? '๐Ÿ”ด' : '๐Ÿ”บ'; } - const rows = pr.map(entry => { - const prev = baseMap[entry.name]; - return `| ${entry.name} | ${prev !== undefined ? formatBytes(prev) : 'โ€”'} | ${formatBytes(entry.size)} | ${formatDelta(entry.size, prev)} | ${statusIcon(entry.size, prev)} |`; - }); + // Size bar: proportional block characters + function sizeBar(bytes, maxBytes) { + const width = 8; + const filled = Math.max(1, Math.round((bytes / maxBytes) * width)); + return 'โ–ˆ'.repeat(filled) + 'โ–‘'.repeat(width - filled); + } - const total = pr.reduce((sum, e) => sum + e.size, 0); - const baseTotal = base.reduce((sum, e) => sum + e.size, 0); + // Group entries by package: @videojs/utils/* -> utils, @videojs/store/* -> store, etc. + const groups = new Map(); + for (const entry of pr) { + const match = entry.name.match(/^@videojs\/([^/]+)/); + const pkg = match ? match[1] : 'other'; + if (!groups.has(pkg)) groups.set(pkg, []); + groups.get(pkg).push(entry); + } + + // First pass: compute per-package totals + const pkgData = []; + let grandTotalPr = 0; + let grandTotalBase = 0; + + for (const [pkg, entries] of groups) { + const pkgTotalPr = entries.reduce((s, e) => s + e.size, 0); + const pkgTotalBase = entries.reduce((s, e) => s + (baseMap[e.name] ?? 0), 0); + const hasBase = entries.some(e => baseMap[e.name] !== undefined); + grandTotalPr += pkgTotalPr; + grandTotalBase += pkgTotalBase; + + const pkgPrev = entries.length > 1 ? pkgTotalBase : baseMap[entries[0].name]; + const pkgCur = entries.length > 1 ? pkgTotalPr : entries[0].size; + const changed = hasBase && pkgCur !== pkgPrev; + + pkgData.push({ pkg, entries, pkgTotalPr, pkgTotalBase, hasBase, pkgPrev, pkgCur, changed }); + } + + const maxPkgSize = Math.max(...pkgData.map(p => p.pkgCur)); + + // Overview table + const overview = []; + overview.push('| Package | Size | | Diff | % | |'); + overview.push('|---|--:|---|--:|--:|:-:|'); + + for (const p of pkgData) { + const d = formatDelta(p.pkgCur, p.pkgPrev); + const icon = p.hasBase ? statusIcon(p.pkgCur, p.pkgPrev) : ''; + const bar = `\`${sizeBar(p.pkgCur, maxPkgSize)}\``; + overview.push( + `| **@videojs/${p.pkg}** | **${formatBytes(p.pkgCur)}** | ${bar} | ${p.hasBase ? d.bytes : 'โ€”'} | ${p.hasBase ? d.pct : ''} | ${icon} |` + ); + } + + // Detail sections + const details = []; + details.push('#### Subpath Breakdown'); + details.push(''); + + for (const p of pkgData) { + const { pkg, entries, pkgTotalPr, pkgTotalBase, hasBase } = p; + + // Subpath display: @videojs/utils/dom -> ./dom, @videojs/store -> . + const displayName = (name) => { + const sub = name.replace(`@videojs/${pkg}`, ''); + return sub ? `.${sub}` : '.'; + }; + + details.push(`
`); + details.push(`@videojs/${pkg}`); + details.push(''); + details.push('| Subpath | Base | PR | Diff | % | |'); + details.push('|---|--:|--:|--:|--:|:-:|'); + + for (const entry of entries) { + const prev = baseMap[entry.name]; + const d = formatDelta(entry.size, prev); + details.push( + `| \`${displayName(entry.name)}\` | ${prev !== undefined ? formatBytes(prev) : 'โ€”'} | **${formatBytes(entry.size)}** | ${d.bytes} | ${d.pct} | ${statusIcon(entry.size, prev)} |` + ); + } + + if (entries.length > 1) { + const d = formatDelta(pkgTotalPr, pkgTotalBase); + details.push( + `| **total** | **${hasBase ? formatBytes(pkgTotalBase) : 'โ€”'}** | **${formatBytes(pkgTotalPr)}** | **${hasBase ? d.bytes : 'โ€”'}** | **${hasBase ? d.pct : ''}** | |` + ); + } + + details.push(''); + details.push('
'); + details.push(''); + } + + const grandDelta = formatDelta(grandTotalPr, grandTotalBase); const marker = ''; const body = [ marker, '### ๐Ÿ“ฆ Bundle Size Report', '', - '| Package | Base | PR | Delta | |', - '|---|---|---|---|---|', - ...rows, - `| **Total** | **${base.length ? formatBytes(baseTotal) : 'โ€”'}** | **${formatBytes(total)}** | **${base.length ? formatDelta(total, baseTotal) : 'โ€”'}** | |`, + ...overview, + '', + `**Total: ${formatBytes(grandTotalPr)}**${grandTotalBase ? ` ยท ${grandDelta.bytes} ยท ${grandDelta.pct}` : ''}`, + '', + '---', + '', + ...details, + '---', '', '
', 'โ„น๏ธ How to interpret', '', - 'Sizes are minified + brotli compressed, measured via [size-limit](https://github.com/ai/size-limit) with esbuild tree-shaking.', + 'Each package shows its own code size with workspace and peer dependencies externalized.', + 'Sizes are minified + brotli, measured via [size-limit](https://github.com/ai/size-limit) with esbuild.', '', '| Icon | Meaning |', '|---|---|', '| โœ… | No change |', - '| ๐Ÿ”บ | Size increased โ‰ค 10% |', - '| ๐Ÿ”ด | Size increased > 10% |', - '| ๐Ÿ”ฝ | Size decreased |', - '| ๐Ÿ†• | New entry (no baseline) |', + '| ๐Ÿ”บ | Increased โ‰ค 10% |', + '| ๐Ÿ”ด | Increased > 10% |', + '| ๐Ÿ”ฝ | Decreased |', + '| ๐Ÿ†• | New (no baseline) |', '', 'Run `pnpm size` locally to check current sizes.', '
', diff --git a/.size-limit.json b/.size-limit.json index 9f36a8bd..a84b2329 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -7,31 +7,71 @@ { "name": "@videojs/store/lit", "path": "packages/store/dist/default/lit.js", - "import": "*" + "import": "*", + "ignore": ["@videojs/utils", "@lit/reactive-element", "@lit/context"] }, { "name": "@videojs/store/react", "path": "packages/store/dist/default/react.js", - "import": "*" + "import": "*", + "ignore": ["@videojs/utils", "react"] }, { "name": "@videojs/core", "path": "packages/core/dist/default/index.js", - "import": "*" + "import": "*", + "ignore": ["@videojs/utils", "@videojs/store"] }, { "name": "@videojs/core/dom", "path": "packages/core/dist/default/dom.js", - "import": "*" - }, - { - "name": "@videojs/react", - "path": "packages/react/dist/default/index.js", - "import": "*" + "import": "*", + "ignore": ["@videojs/utils", "@videojs/store"] }, { "name": "@videojs/html", "path": "packages/html/dist/default/index.js", "import": "*" + }, + { + "name": "@videojs/react", + "path": "packages/react/dist/default/index.js", + "import": "*", + "ignore": ["react"] + }, + { + "name": "@videojs/utils/array", + "path": "packages/utils/dist/array.js", + "import": "*" + }, + { + "name": "@videojs/utils/dom", + "path": "packages/utils/dist/dom.js", + "import": "*" + }, + { + "name": "@videojs/utils/events", + "path": "packages/utils/dist/events.js", + "import": "*" + }, + { + "name": "@videojs/utils/function", + "path": "packages/utils/dist/function.js", + "import": "*" + }, + { + "name": "@videojs/utils/object", + "path": "packages/utils/dist/object.js", + "import": "*" + }, + { + "name": "@videojs/utils/predicate", + "path": "packages/utils/dist/predicate.js", + "import": "*" + }, + { + "name": "@videojs/utils/time", + "path": "packages/utils/dist/time.js", + "import": "*" } ] diff --git a/package.json b/package.json index f5b72a57..6e4ea38d 100644 --- a/package.json +++ b/package.json @@ -35,18 +35,19 @@ "typecheck": "tsc --build" }, "devDependencies": { - "@size-limit/preset-small-lib": "^12.0.0", "@biomejs/biome": "^2.3.11", "@commitlint/cli": "^20.1.0", "@commitlint/config-conventional": "^20.0.0", "@commitlint/format": "^20.0.0", + "@size-limit/esbuild": "^12.0.0", + "@size-limit/file": "^12.0.0", "@types/node": "^22.18.6", "lint-staged": "^16.2.3", "react": "^18.0.0", "react-compiler-runtime": "^1.0.0", "react-dom": "^18.0.0", - "size-limit": "^12.0.0", "simple-git-hooks": "^2.13.1", + "size-limit": "^12.0.0", "tsx": "^4.21.0", "turbo": "^2.5.8", "typescript": "^5.9.3" diff --git a/packages/html/package.json b/packages/html/package.json index 9299f322..a9273dcd 100644 --- a/packages/html/package.json +++ b/packages/html/package.json @@ -4,13 +4,14 @@ "version": "0.1.0-alpha.1", "description": "HTML library for building media players", "license": "Apache-2.0", - "keywords": [ - "media", - "player", - "html", - "ui", - "components", - "videojs" + "main": "dist/default/index.js", + "module": "dist/default/index.js", + "types": "dist/dev/index.d.ts", + "sideEffects": [ + "./dist/*/define/**/*.js" + ], + "files": [ + "dist" ], "exports": { ".": { @@ -49,15 +50,6 @@ "default": "./dist/default/define/media/*.js" } }, - "sideEffects": [ - "./dist/*/define/**/*.js" - ], - "main": "dist/default/index.js", - "module": "dist/default/index.js", - "types": "dist/dev/index.d.ts", - "files": [ - "dist" - ], "scripts": { "build": "tsdown", "build:watch": "tsdown --watch ./src --no-clean", @@ -81,5 +73,13 @@ }, "publishConfig": { "access": "public" - } + }, + "keywords": [ + "media", + "player", + "html", + "ui", + "components", + "videojs" + ] } diff --git a/packages/react/package.json b/packages/react/package.json index 477750c7..97265e36 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -4,13 +4,12 @@ "version": "0.1.0-alpha.1", "description": "React library for building media players", "license": "Apache-2.0", - "keywords": [ - "media", - "player", - "react", - "components", - "hooks", - "videojs" + "main": "dist/default/index.js", + "module": "dist/default/index.js", + "types": "dist/dev/index.d.ts", + "sideEffects": false, + "files": [ + "dist" ], "exports": { ".": { @@ -19,12 +18,6 @@ "default": "./dist/default/index.js" } }, - "main": "dist/default/index.js", - "module": "dist/default/index.js", - "types": "dist/dev/index.d.ts", - "files": [ - "dist" - ], "scripts": { "build": "tsdown", "build:watch": "tsdown --watch ./src --no-clean", @@ -53,5 +46,13 @@ }, "publishConfig": { "access": "public" - } + }, + "keywords": [ + "media", + "player", + "react", + "components", + "hooks", + "videojs" + ] } diff --git a/packages/store/package.json b/packages/store/package.json index e3d34cde..8e31219d 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -4,12 +4,12 @@ "version": "0.1.0-alpha.1", "description": "Reactive state management for external systems.", "license": "Apache-2.0", - "keywords": [ - "store", - "media", - "player", - "videojs", - "reactive" + "main": "dist/default/index.js", + "module": "dist/default/index.js", + "types": "dist/dev/index.d.ts", + "sideEffects": false, + "files": [ + "dist" ], "exports": { ".": { @@ -28,12 +28,6 @@ "default": "./dist/default/react.js" } }, - "main": "dist/default/index.js", - "module": "dist/default/index.js", - "types": "dist/dev/index.d.ts", - "files": [ - "dist" - ], "scripts": { "build": "tsdown", "build:watch": "tsdown --watch ./src --no-clean", @@ -75,5 +69,12 @@ }, "publishConfig": { "access": "public" - } + }, + "keywords": [ + "store", + "media", + "player", + "videojs", + "reactive" + ] } diff --git a/packages/utils/package.json b/packages/utils/package.json index 72b97f91..5e25dbaf 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -4,11 +4,7 @@ "version": "0.1.0-alpha.1", "description": "Utility functions and helpers for Video.js", "license": "Apache-2.0", - "keywords": [ - "media", - "utils", - "videojs" - ], + "sideEffects": false, "exports": { "./array": { "types": "./dist/array.d.ts", @@ -61,5 +57,10 @@ }, "publishConfig": { "access": "public" - } + }, + "keywords": [ + "media", + "utils", + "videojs" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 555245d4..96282fd1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,7 +20,10 @@ importers: '@commitlint/format': specifier: ^20.0.0 version: 20.2.0 - '@size-limit/preset-small-lib': + '@size-limit/esbuild': + specifier: ^12.0.0 + version: 12.0.0(size-limit@12.0.0(jiti@2.6.1)) + '@size-limit/file': specifier: ^12.0.0 version: 12.0.0(size-limit@12.0.0(jiti@2.6.1)) '@types/node': @@ -2518,11 +2521,6 @@ packages: peerDependencies: size-limit: 12.0.0 - '@size-limit/preset-small-lib@12.0.0': - resolution: {integrity: sha512-HHHVQjZmj+8vg7qsHs1dd3Hmn8ygUsE5O2CfxnbCbHOGyUw7VodZGERh/+5ogVrF2DYza/DIo2PnCJZZETdTRA==} - peerDependencies: - size-limit: 12.0.0 - '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} @@ -6095,7 +6093,7 @@ packages: tar@7.5.2: resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@7.5.7: resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} @@ -9134,12 +9132,6 @@ snapshots: dependencies: size-limit: 12.0.0(jiti@2.6.1) - '@size-limit/preset-small-lib@12.0.0(size-limit@12.0.0(jiti@2.6.1))': - dependencies: - '@size-limit/esbuild': 12.0.0(size-limit@12.0.0(jiti@2.6.1)) - '@size-limit/file': 12.0.0(size-limit@12.0.0(jiti@2.6.1)) - size-limit: 12.0.0(jiti@2.6.1) - '@so-ric/colorspace@1.1.6': dependencies: color: 5.0.3 @@ -9709,7 +9701,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.3)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) '@vitest/utils@3.2.4': dependencies: