diff --git a/.github/scripts/bundle-size-report.js b/.github/scripts/bundle-size-report.js
new file mode 100644
index 00000000..d349c1c1
--- /dev/null
+++ b/.github/scripts/bundle-size-report.js
@@ -0,0 +1,376 @@
+/**
+ * Generates a markdown bundle size report from measurement JSON data.
+ *
+ * Usage:
+ * node bundle-size-report.js --pr pr-size.json [--base base-size.json]
+ *
+ * When --base is omitted, generates a report showing current sizes only (no diff).
+ * When --base is provided, generates a comparison report with diffs and status icons.
+ *
+ * Reads JSON arrays of { name, size, type } entries produced by bundle-size.js.
+ */
+
+import { readFileSync } from 'node:fs';
+
+const ESC = '\x1b[';
+const ansi = {
+ bold: (s) => `${ESC}1m${s}${ESC}22m`,
+ dim: (s) => `${ESC}2m${s}${ESC}22m`,
+ cyan: (s) => `${ESC}36m${s}${ESC}39m`,
+ yellow: (s) => `${ESC}33m${s}${ESC}39m`,
+ white: (s) => `${ESC}37m${s}${ESC}39m`,
+ green: (s) => `${ESC}32m${s}${ESC}39m`,
+};
+
+function formatBytes(bytes) {
+ if (bytes < 1024) return `${bytes} B`;
+ return `${(bytes / 1024).toFixed(2)} kB`;
+}
+
+function formatDelta(current, previous) {
+ if (previous === undefined) return { bytes: 'โ', pct: '' };
+ const diff = current - previous;
+ 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) {
+ if (previous === undefined) return '๐';
+ const diff = current - previous;
+ if (diff === 0) return 'โ
';
+ if (diff < 0) return '๐ฝ';
+ const pct = (diff / previous) * 100;
+ return pct > 10 ? '๐ด' : '๐บ';
+}
+
+function deltaBar(current, previous, maxAbsPct) {
+ const width = 8;
+ if (previous === undefined || maxAbsPct === 0) return 'โ'.repeat(width);
+ const pct = Math.abs(((current - previous) / previous) * 100);
+ const filled = Math.round((pct / maxAbsPct) * width);
+ return 'โ'.repeat(filled) + 'โ'.repeat(width - filled);
+}
+
+/** Group entries by package: @videojs/utils/* -> utils, @videojs/store/* -> store */
+function groupByPackage(entries) {
+ const groups = new Map();
+ for (const entry of entries) {
+ const match = entry.name.match(/^@videojs\/([^/]+)/);
+ const pkg = match ? match[1] : 'other';
+ if (!groups.has(pkg)) groups.set(pkg, []);
+ groups.get(pkg).push(entry);
+ }
+ return groups;
+}
+
+function computePackageData(groups, baseMap) {
+ const pkgData = [];
+ let grandTotalCurrent = 0;
+ let grandTotalBase = 0;
+
+ for (const [pkg, entries] of groups) {
+ const rootEntries = entries.filter((e) => e.type === 'root');
+ const subEntries = entries.filter((e) => e.type === 'subpath');
+
+ const pkgTotalCurrent =
+ rootEntries.reduce((s, e) => s + e.size, 0) +
+ subEntries.reduce((s, e) => s + e.size, 0);
+
+ const pkgTotalBase =
+ rootEntries.reduce((s, e) => s + (baseMap[e.name] ?? 0), 0) +
+ subEntries.reduce((s, e) => s + (baseMap[e.name] ?? 0), 0);
+
+ const hasBase = entries.some((e) => baseMap[e.name] !== undefined);
+ grandTotalCurrent += pkgTotalCurrent;
+ grandTotalBase += pkgTotalBase;
+
+ pkgData.push({
+ pkg,
+ entries,
+ rootEntries,
+ subEntries,
+ pkgTotalCurrent,
+ pkgTotalBase,
+ hasBase,
+ });
+ }
+
+ return { pkgData, grandTotalCurrent, grandTotalBase };
+}
+
+/** Generate a comparison report (PR vs base). */
+function generateComparisonReport(current, base) {
+ const baseMap = Object.fromEntries(base.map((e) => [e.name, e.size]));
+ const groups = groupByPackage(current);
+ const { pkgData, grandTotalCurrent, grandTotalBase } = computePackageData(
+ groups,
+ baseMap,
+ );
+
+ const maxAbsPct = Math.max(
+ ...pkgData.map((p) => {
+ if (!p.hasBase || p.pkgTotalBase === 0) return 0;
+ return Math.abs(
+ ((p.pkgTotalCurrent - p.pkgTotalBase) / p.pkgTotalBase) * 100,
+ );
+ }),
+ );
+
+ // Overview table
+ const overview = [];
+ overview.push('| Package | Size | Diff | | % | |');
+ overview.push('|---|--:|--:|---|--:|:-:|');
+
+ for (const p of pkgData) {
+ const d = formatDelta(
+ p.pkgTotalCurrent,
+ p.hasBase ? p.pkgTotalBase : undefined,
+ );
+ const icon = p.hasBase
+ ? statusIcon(p.pkgTotalCurrent, p.pkgTotalBase)
+ : '';
+ const bar = `\`${deltaBar(p.pkgTotalCurrent, p.hasBase ? p.pkgTotalBase : undefined, maxAbsPct)}\``;
+ overview.push(
+ `| **@videojs/${p.pkg}** | **${formatBytes(p.pkgTotalCurrent)}** | ${p.hasBase ? d.bytes : 'โ'} | ${bar} | ${p.hasBase ? d.pct : ''} | ${icon} |`,
+ );
+ }
+
+ // Detail sections โ only for packages with multiple entries
+ const details = [];
+ const pkgsWithSubs = pkgData.filter((p) => p.entries.length > 1);
+
+ if (pkgsWithSubs.length > 0) {
+ details.push('#### Entry Breakdown');
+ details.push('');
+ details.push(
+ 'Subpath sizes are the additional bytes on top of the root entry point, measured by bundling root + subpath together and subtracting the root-only size.',
+ );
+ details.push('');
+
+ for (const p of pkgsWithSubs) {
+ const {
+ pkg,
+ rootEntries,
+ subEntries,
+ pkgTotalCurrent,
+ pkgTotalBase,
+ hasBase,
+ } = p;
+
+ details.push('');
+ details.push(`@videojs/${pkg}
`);
+ details.push('');
+ details.push('| Entry | Base | PR | Diff | % | |');
+ details.push('|---|--:|--:|--:|--:|:-:|');
+
+ for (const entry of [...rootEntries, ...subEntries]) {
+ const displayName =
+ entry.name.replace(`@videojs/${pkg}`, '') || '.';
+ const label = displayName === '.' ? displayName : `.${displayName}`;
+ const prev = baseMap[entry.name];
+ const d = formatDelta(entry.size, prev);
+ details.push(
+ `| \`${label}\` | ${prev !== undefined ? formatBytes(prev) : 'โ'} | **${formatBytes(entry.size)}** | ${d.bytes} | ${d.pct} | ${statusIcon(entry.size, prev)} |`,
+ );
+ }
+
+ if (rootEntries.length + subEntries.length > 1) {
+ const d = formatDelta(
+ pkgTotalCurrent,
+ hasBase ? pkgTotalBase : undefined,
+ );
+ details.push(
+ `| **total** | **${hasBase ? formatBytes(pkgTotalBase) : 'โ'}** | **${formatBytes(pkgTotalCurrent)}** | **${hasBase ? d.bytes : 'โ'}** | **${hasBase ? d.pct : ''}** | |`,
+ );
+ }
+
+ details.push('');
+ details.push(' ');
+ details.push('');
+ }
+ }
+
+ const grandDelta = formatDelta(grandTotalCurrent, grandTotalBase);
+
+ const marker = '';
+ return [
+ marker,
+ '### ๐ฆ Bundle Size Report',
+ '',
+ ...overview,
+ '',
+ `**Total: ${formatBytes(grandTotalCurrent)}**${grandTotalBase ? ` ยท ${grandDelta.bytes} ยท ${grandDelta.pct}` : ''}`,
+ '',
+ '---',
+ '',
+ ...details,
+ '---',
+ '',
+ '',
+ 'โน๏ธ How to interpret
',
+ '',
+ 'Sizes are minified + brotli, measured with esbuild.',
+ 'Package totals are computed as root size + marginal subpath costs.',
+ 'Subpath marginal cost = (root + subpath bundled together) โ root alone.',
+ '',
+ '| Icon | Meaning |',
+ '|---|---|',
+ '| โ
| No change |',
+ '| ๐บ | Increased โค 10% |',
+ '| ๐ด | Increased > 10% |',
+ '| ๐ฝ | Decreased |',
+ '| ๐ | New (no baseline) |',
+ '',
+ 'Run `pnpm size` locally to check current sizes.',
+ ' ',
+ ].join('\n');
+}
+
+/**
+ * Render rows as a padded, aligned table for terminal output.
+ *
+ * Each row is an array of cell values. Cells can be plain strings or
+ * `{ text, style }` objects where `style` is a ansi chain (e.g. ansi.bold).
+ * Alignment and padding use the plain text width; ANSI codes are ignored.
+ *
+ * The first row is treated as a dim header.
+ */
+function printTable(rows) {
+ if (rows.length === 0) return '';
+
+ const text = (cell) => (typeof cell === 'string' ? cell : cell.text);
+ const style = (cell) =>
+ typeof cell === 'string' ? (s) => s : cell.style ?? ((s) => s);
+
+ const cols = rows[0].length;
+ const widths = Array.from({ length: cols }, () => 0);
+ for (const row of rows) {
+ for (let i = 0; i < cols; i++) {
+ widths[i] = Math.max(widths[i], text(row[i]).length);
+ }
+ }
+
+ const sep = ansi.dim(
+ `โ${widths.map((w) => 'โ'.repeat(w)).join('โโผโ')}โ`,
+ );
+ const lines = [];
+
+ for (let r = 0; r < rows.length; r++) {
+ const cells = rows[r].map((cell, i) => {
+ const t = text(cell);
+ const padded =
+ i === cols - 1 ? t.padStart(widths[i]) : t.padEnd(widths[i]);
+ // Header row is dim, data rows use their own style
+ return r === 0 ? ansi.dim(padded) : style(cell)(padded);
+ });
+ lines.push(` ${cells.join(ansi.dim(' โ '))} `);
+ if (r === 0) lines.push(sep);
+ }
+
+ return lines.join('\n');
+}
+
+/** Color a size value based on magnitude. */
+function colorSize(bytes) {
+ const text = formatBytes(bytes);
+ if (bytes >= 5 * 1024) return { text, style: ansi.yellow };
+ if (bytes >= 1024) return { text, style: ansi.white };
+ return { text, style: ansi.green };
+}
+
+/** Generate a local-only report (no base comparison). */
+function generateLocalReport(current) {
+ const groups = groupByPackage(current);
+
+ const overviewRows = [['Package', 'Size']];
+ let grandTotal = 0;
+ const pkgsWithSubs = [];
+
+ for (const [pkg, entries] of groups) {
+ const rootEntries = entries.filter((e) => e.type === 'root');
+ const subEntries = entries.filter((e) => e.type === 'subpath');
+
+ const pkgTotal =
+ rootEntries.reduce((s, e) => s + e.size, 0) +
+ subEntries.reduce((s, e) => s + e.size, 0);
+
+ grandTotal += pkgTotal;
+ overviewRows.push([
+ { text: `@videojs/${pkg}`, style: ansi.bold },
+ colorSize(pkgTotal),
+ ]);
+
+ if (entries.length > 1) {
+ pkgsWithSubs.push({ pkg, rootEntries, subEntries, pkgTotal });
+ }
+ }
+
+ const lines = [];
+ lines.push('');
+ lines.push(printTable(overviewRows));
+ lines.push('');
+ lines.push(ansi.bold(`Total: ${formatBytes(grandTotal)}`));
+
+ if (pkgsWithSubs.length > 0) {
+ for (const { pkg, rootEntries, subEntries, pkgTotal } of pkgsWithSubs) {
+ lines.push('');
+ lines.push(ansi.bold(`@videojs/${pkg}`));
+
+ const rows = [['Entry', 'Size']];
+ for (const entry of [...rootEntries, ...subEntries]) {
+ const displayName =
+ entry.name.replace(`@videojs/${pkg}`, '') || '.';
+ const label = displayName === '.' ? displayName : `.${displayName}`;
+ rows.push([
+ { text: label, style: ansi.cyan },
+ colorSize(entry.size),
+ ]);
+ }
+ rows.push([
+ { text: 'total', style: ansi.bold },
+ { text: formatBytes(pkgTotal), style: ansi.bold },
+ ]);
+
+ lines.push(printTable(rows));
+ }
+ }
+
+ lines.push('');
+ lines.push(ansi.dim('Sizes are minified + brotli.'));
+ lines.push('');
+
+ return lines.join('\n');
+}
+
+function main() {
+ const args = process.argv.slice(2);
+
+ const prIndex = args.indexOf('--pr');
+ const baseIndex = args.indexOf('--base');
+
+ if (prIndex === -1) {
+ // Read from stdin (piped from bundle-size.js)
+ const input = readFileSync('/dev/stdin', 'utf8');
+ const current = JSON.parse(input);
+ console.log(generateLocalReport(current));
+ return;
+ }
+
+ const prPath = args[prIndex + 1];
+ const current = JSON.parse(readFileSync(prPath, 'utf8'));
+
+ if (baseIndex !== -1) {
+ const basePath = args[baseIndex + 1];
+ const base = JSON.parse(readFileSync(basePath, 'utf8'));
+ console.log(generateComparisonReport(current, base));
+ } else {
+ console.log(generateLocalReport(current));
+ }
+}
+
+main();
diff --git a/.github/scripts/bundle-size.js b/.github/scripts/bundle-size.js
new file mode 100644
index 00000000..f5c45d5f
--- /dev/null
+++ b/.github/scripts/bundle-size.js
@@ -0,0 +1,177 @@
+/**
+ * Measures bundle sizes for all packages, computing marginal subpath costs.
+ *
+ * Auto-discovers packages from `packages/`, reads their `exports` field to find
+ * entry points, and externalizes `peerDependencies`.
+ *
+ * For packages with subpaths, each subpath is bundled together with the root
+ * entry point. The marginal cost is: (root + subpath) - root. This captures
+ * shared minification and compression, avoiding the inflated totals you get
+ * from summing independently-measured subpaths.
+ *
+ * Usage: node .github/scripts/bundle-size.js [--json output.json]
+ */
+
+import { build } from 'esbuild';
+import { brotliCompressSync, constants } from 'node:zlib';
+import { readFileSync, readdirSync, writeFileSync, existsSync } from 'node:fs';
+import { resolve, dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const ROOT = resolve(__dirname, '../..');
+const PACKAGES_DIR = join(ROOT, 'packages');
+
+const SKIP_PACKAGES = new Set(['sandbox', '__tech-preview__', 'react-native']);
+
+/**
+ * @typedef {object} SizeEntry
+ * @property {string} name
+ * @property {number} size - Root: brotli size. Subpath: marginal cost over root.
+ * @property {'root' | 'subpath'} type
+ */
+
+/** Bundle entry points with esbuild and return the minified + brotli size. */
+async function measure(entryPoints, external = []) {
+ const result = await build({
+ entryPoints,
+ bundle: true,
+ minify: true,
+ treeShaking: true,
+ format: 'esm',
+ write: false,
+ metafile: true,
+ outdir: '/tmp/bundle-size-out',
+ external,
+ logLevel: 'silent',
+ });
+
+ const code = result.outputFiles.map((f) => f.text).join('');
+ const compressed = brotliCompressSync(Buffer.from(code), {
+ params: {
+ [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
+ },
+ });
+
+ return compressed.length;
+}
+
+/**
+ * Resolve the `default` condition from an export value.
+ * Handles both `{ default: "./dist/..." }` objects and plain string values.
+ */
+function resolveDefault(exportValue) {
+ if (typeof exportValue === 'string') return exportValue;
+ if (typeof exportValue === 'object' && exportValue !== null) {
+ return exportValue.default ?? null;
+ }
+ return null;
+}
+
+/** Discover packages and their entry points from the filesystem. */
+function discoverPackages() {
+ const packages = [];
+
+ for (const dirName of readdirSync(PACKAGES_DIR).sort()) {
+ if (SKIP_PACKAGES.has(dirName)) continue;
+
+ const pkgJsonPath = join(PACKAGES_DIR, dirName, 'package.json');
+ if (!existsSync(pkgJsonPath)) continue;
+
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
+ if (!pkgJson.exports) continue;
+
+ const pkgName = pkgJson.name;
+ const pkgDir = join(PACKAGES_DIR, dirName);
+ const external = pkgJson.peerDependencies
+ ? Object.keys(pkgJson.peerDependencies)
+ : [];
+
+ let rootPath = null;
+ const subpaths = [];
+
+ for (const [key, value] of Object.entries(pkgJson.exports)) {
+ // Skip wildcard exports (side-effect registration files)
+ if (key.includes('*')) continue;
+
+ const defaultPath = resolveDefault(value);
+ if (!defaultPath) continue;
+
+ // Skip types-only exports (no runtime code)
+ if (defaultPath.endsWith('.d.ts')) continue;
+
+ const absolutePath = resolve(pkgDir, defaultPath);
+ if (!existsSync(absolutePath)) continue;
+
+ // Skip empty files (types-only exports with no runtime code)
+ if (readFileSync(absolutePath, 'utf8').trim().length === 0) continue;
+
+ if (key === '.') {
+ rootPath = absolutePath;
+ } else {
+ subpaths.push({
+ name: `${pkgName}${key.slice(1)}`,
+ path: absolutePath,
+ });
+ }
+ }
+
+ if (rootPath) {
+ // Package with a root export (and optional subpaths)
+ packages.push({ name: pkgName, rootPath, external, subpaths });
+ } else if (subpaths.length > 0) {
+ // Package with only subpath exports (e.g., @videojs/utils)
+ // Each subpath is measured as an independent root
+ for (const sub of subpaths) {
+ packages.push({
+ name: sub.name,
+ rootPath: sub.path,
+ external,
+ subpaths: [],
+ });
+ }
+ }
+ }
+
+ return packages;
+}
+
+async function main() {
+ const packages = discoverPackages();
+
+ /** @type {SizeEntry[]} */
+ const results = [];
+
+ for (const pkg of packages) {
+ const rootSize = await measure([pkg.rootPath], pkg.external);
+ results.push({ name: pkg.name, size: rootSize, type: 'root' });
+
+ for (const sub of pkg.subpaths) {
+ const combinedSize = await measure(
+ [pkg.rootPath, sub.path],
+ pkg.external,
+ );
+ const marginal = combinedSize - rootSize;
+
+ results.push({ name: sub.name, size: marginal, type: 'subpath' });
+ }
+ }
+
+ // Parse --json flag
+ const jsonIndex = process.argv.indexOf('--json');
+ const outputPath = jsonIndex !== -1 ? process.argv[jsonIndex + 1] : null;
+
+ const output = JSON.stringify(results, null, 2);
+
+ if (outputPath) {
+ writeFileSync(outputPath, output);
+ console.log(`Written to ${outputPath}`);
+ } else {
+ console.log(output);
+ }
+}
+
+main().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml
index b56c0876..434ca8fd 100644
--- a/.github/workflows/bundle-size.yml
+++ b/.github/workflows/bundle-size.yml
@@ -9,7 +9,7 @@ permissions:
pull-requests: write
jobs:
- size:
+ pr-size:
runs-on: ubuntu-latest
steps:
@@ -25,191 +25,96 @@ jobs:
node-version: 22
cache: pnpm
- - name: Install and build PR
+ - name: Cache turbo build setup
+ uses: actions/cache@v5
+ with:
+ path: .turbo
+ key: ${{ runner.os }}-turbo-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-turbo-
+
+ - name: Install and build
run: pnpm install && pnpm build:packages
- - name: Measure PR bundle size
- run: pnpm -w exec size-limit --json > /tmp/pr-size.json
+ - name: Measure bundle size
+ run: node .github/scripts/bundle-size.js --json pr-size.json
- - name: Measure base bundle size
+ - name: Upload size data
+ uses: actions/upload-artifact@v4
+ with:
+ name: pr-size
+ path: pr-size.json
+
+ base-size:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout base
+ uses: actions/checkout@v5
+ with:
+ ref: ${{ github.base_ref }}
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v5
+ with:
+ node-version: 22
+ cache: pnpm
+
+ - name: Cache turbo build setup
+ uses: actions/cache@v5
+ with:
+ path: .turbo
+ key: ${{ runner.os }}-turbo-${{ github.event.pull_request.base.sha }}
+ restore-keys: |
+ ${{ runner.os }}-turbo-
+
+ - name: Install and build
run: |
- git fetch origin ${{ github.base_ref }} --depth=1
- git checkout -f FETCH_HEAD
-
pnpm install
- if [ -f .size-limit.json ]; then
+ if [ -f .github/scripts/bundle-size.js ]; then
pnpm build:packages
- pnpm -w exec size-limit --json > /tmp/base-size.json
+ node .github/scripts/bundle-size.js --json base-size.json
else
- echo '[]' > /tmp/base-size.json
+ echo '[]' > base-size.json
fi
- - name: Report
+ - name: Upload size data
+ uses: actions/upload-artifact@v4
+ with:
+ name: base-size
+ path: base-size.json
+
+ report:
+ runs-on: ubuntu-latest
+ needs: [pr-size, base-size]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v5
+
+ - name: Download size data
+ uses: actions/download-artifact@v4
+ with:
+ path: artifacts
+
+ - name: Generate report
+ run: >
+ node .github/scripts/bundle-size-report.js
+ --pr artifacts/pr-size/pr-size.json
+ --base artifacts/base-size/base-size.json
+ > report.md
+
+ - name: Post comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
-
- const pr = JSON.parse(fs.readFileSync('/tmp/pr-size.json', 'utf8'));
- const base = JSON.parse(fs.readFileSync('/tmp/base-size.json', 'utf8'));
-
- const baseMap = Object.fromEntries(base.map(e => [e.name, e.size]));
-
- function formatBytes(bytes) {
- if (bytes < 1024) return `${bytes} B`;
- return `${(bytes / 1024).toFixed(2)} kB`;
- }
-
- function formatDelta(current, previous) {
- if (previous === undefined) return { bytes: 'โ', pct: '' };
- const diff = current - previous;
- 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) {
- if (previous === undefined) return '๐';
- const diff = current - previous;
- if (diff === 0) return 'โ
';
- if (diff < 0) return '๐ฝ';
- const pct = (diff / previous) * 100;
- return pct > 10 ? '๐ด' : '๐บ';
- }
-
- // Delta bar: visualizes % change relative to the largest change
- function deltaBar(current, previous, maxAbsPct) {
- const width = 8;
- if (previous === undefined || maxAbsPct === 0) return 'โ'.repeat(width);
- const pct = Math.abs(((current - previous) / previous) * 100);
- const filled = Math.round((pct / maxAbsPct) * width);
- return 'โ'.repeat(filled) + 'โ'.repeat(width - filled);
- }
-
- // 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 maxAbsPct = Math.max(
- ...pkgData.map(p => {
- if (!p.hasBase || p.pkgPrev === 0) return 0;
- return Math.abs(((p.pkgCur - p.pkgPrev) / p.pkgPrev) * 100);
- })
- );
-
- // 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 = `\`${deltaBar(p.pkgCur, p.pkgPrev, maxAbsPct)}\``;
- overview.push(
- `| **@videojs/${p.pkg}** | **${formatBytes(p.pkgCur)}** | ${p.hasBase ? d.bytes : 'โ'} | ${bar} | ${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 body = fs.readFileSync('report.md', 'utf8');
const marker = '';
- const body = [
- marker,
- '### ๐ฆ Bundle Size Report',
- '',
- ...overview,
- '',
- `**Total: ${formatBytes(grandTotalPr)}**${grandTotalBase ? ` ยท ${grandDelta.bytes} ยท ${grandDelta.pct}` : ''}`,
- '',
- '---',
- '',
- ...details,
- '---',
- '',
- '',
- 'โน๏ธ How to interpret
',
- '',
- '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 |',
- '| ๐บ | Increased โค 10% |',
- '| ๐ด | Increased > 10% |',
- '| ๐ฝ | Decreased |',
- '| ๐ | New (no baseline) |',
- '',
- 'Run `pnpm size` locally to check current sizes.',
- ' ',
- ].join('\n');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 5dee1da3..3af1a0a1 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -55,6 +55,15 @@ jobs:
if: ${{ steps.release.outputs.releases_created == 'true' }}
run: pnpm i --frozen-lockfile
+ - name: Cache turbo build setup
+ if: ${{ steps.release.outputs.releases_created == 'true' }}
+ uses: actions/cache@v5
+ with:
+ path: .turbo
+ key: ${{ runner.os }}-turbo-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-turbo-
+
- name: Clean
if: ${{ steps.release.outputs.releases_created == 'true' }}
run: pnpm clean
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2daff849..77b634e8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,5 +28,13 @@ jobs:
- name: Install dependencies
run: pnpm install
+ - name: Cache turbo build setup
+ uses: actions/cache@v5
+ with:
+ path: .turbo
+ key: ${{ runner.os }}-turbo-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-turbo-
+
- name: Build
run: pnpm build:packages
diff --git a/.github/workflows/website-tests.yml b/.github/workflows/website-tests.yml
index bb300fa7..c6600c43 100644
--- a/.github/workflows/website-tests.yml
+++ b/.github/workflows/website-tests.yml
@@ -30,5 +30,13 @@ jobs:
- name: Install dependencies
run: pnpm install
+ - name: Cache turbo build setup
+ uses: actions/cache@v5
+ with:
+ path: .turbo
+ key: ${{ runner.os }}-turbo-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-turbo-
+
- name: Run website tests
run: pnpm --filter site test
diff --git a/.size-limit.json b/.size-limit.json
deleted file mode 100644
index 2f505b5e..00000000
--- a/.size-limit.json
+++ /dev/null
@@ -1,87 +0,0 @@
-[
- {
- "name": "@videojs/element",
- "path": "packages/element/dist/default/index.js",
- "import": "*"
- },
- {
- "name": "@videojs/element/context",
- "path": "packages/element/dist/default/context.js",
- "import": "*"
- },
- {
- "name": "@videojs/store",
- "path": "packages/store/dist/default/index.js",
- "import": "*"
- },
- {
- "name": "@videojs/store/html",
- "path": "packages/store/dist/default/html.js",
- "import": "*",
- "ignore": ["@videojs/utils", "@videojs/element"]
- },
- {
- "name": "@videojs/store/react",
- "path": "packages/store/dist/default/react.js",
- "import": "*",
- "ignore": ["@videojs/utils", "react"]
- },
- {
- "name": "@videojs/core",
- "path": "packages/core/dist/default/index.js",
- "import": "*",
- "ignore": ["@videojs/utils", "@videojs/store"]
- },
- {
- "name": "@videojs/core/dom",
- "path": "packages/core/dist/default/dom.js",
- "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 6e4ea38d..e84bdc71 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"lint:fix:file": "biome check --write",
"link:opencode": "[ -e .opencode ] || ln -s .claude .opencode",
"link:agents": "[ -e AGENTS.md ] || ln -s CLAUDE.md AGENTS.md; [ -e agents ] || ln -s .claude agents",
- "size": "size-limit",
+ "size": "node .github/scripts/bundle-size.js | node .github/scripts/bundle-size-report.js",
"test": "turbo run test",
"typecheck": "tsc --build"
},
@@ -39,15 +39,13 @@
"@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",
+ "esbuild": "^0.27.3",
"lint-staged": "^16.2.3",
"react": "^18.0.0",
"react-compiler-runtime": "^1.0.0",
"react-dom": "^18.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/pnpm-lock.yaml b/pnpm-lock.yaml
index 55f36b26..cd627781 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,15 +20,12 @@ importers:
'@commitlint/format':
specifier: ^20.0.0
version: 20.2.0
- '@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':
specifier: ^22.18.6
version: 22.19.3
+ esbuild:
+ specifier: ^0.27.3
+ version: 0.27.3
lint-staged:
specifier: ^16.2.3
version: 16.2.7
@@ -44,9 +41,6 @@ importers:
simple-git-hooks:
specifier: ^2.13.1
version: 2.13.1
- size-limit:
- specifier: ^12.0.0
- version: 12.0.0(jiti@2.6.1)
tsx:
specifier: ^4.21.0
version: 4.21.0
@@ -1009,6 +1003,12 @@ packages:
cpu: [ppc64]
os: [aix]
+ '@esbuild/aix-ppc64@0.27.3':
+ resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
'@esbuild/android-arm64@0.25.12':
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
engines: {node: '>=18'}
@@ -1021,6 +1021,12 @@ packages:
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm64@0.27.3':
+ resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm@0.25.12':
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
engines: {node: '>=18'}
@@ -1033,6 +1039,12 @@ packages:
cpu: [arm]
os: [android]
+ '@esbuild/android-arm@0.27.3':
+ resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-x64@0.25.12':
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
engines: {node: '>=18'}
@@ -1045,6 +1057,12 @@ packages:
cpu: [x64]
os: [android]
+ '@esbuild/android-x64@0.27.3':
+ resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/darwin-arm64@0.25.12':
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
engines: {node: '>=18'}
@@ -1057,6 +1075,12 @@ packages:
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-arm64@0.27.3':
+ resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.25.12':
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
engines: {node: '>=18'}
@@ -1069,6 +1093,12 @@ packages:
cpu: [x64]
os: [darwin]
+ '@esbuild/darwin-x64@0.27.3':
+ resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/freebsd-arm64@0.25.12':
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
engines: {node: '>=18'}
@@ -1081,6 +1111,12 @@ packages:
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-arm64@0.27.3':
+ resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.25.12':
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
engines: {node: '>=18'}
@@ -1093,6 +1129,12 @@ packages:
cpu: [x64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.27.3':
+ resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/linux-arm64@0.25.12':
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
engines: {node: '>=18'}
@@ -1105,6 +1147,12 @@ packages:
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm64@0.27.3':
+ resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm@0.25.12':
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
engines: {node: '>=18'}
@@ -1117,6 +1165,12 @@ packages:
cpu: [arm]
os: [linux]
+ '@esbuild/linux-arm@0.27.3':
+ resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-ia32@0.25.12':
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
engines: {node: '>=18'}
@@ -1129,6 +1183,12 @@ packages:
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-ia32@0.27.3':
+ resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-loong64@0.25.12':
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
engines: {node: '>=18'}
@@ -1141,6 +1201,12 @@ packages:
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-loong64@0.27.3':
+ resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.25.12':
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
engines: {node: '>=18'}
@@ -1153,6 +1219,12 @@ packages:
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-mips64el@0.27.3':
+ resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.25.12':
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
engines: {node: '>=18'}
@@ -1165,6 +1237,12 @@ packages:
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-ppc64@0.27.3':
+ resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.25.12':
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
engines: {node: '>=18'}
@@ -1177,6 +1255,12 @@ packages:
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-riscv64@0.27.3':
+ resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-s390x@0.25.12':
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
engines: {node: '>=18'}
@@ -1189,6 +1273,12 @@ packages:
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-s390x@0.27.3':
+ resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-x64@0.25.12':
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
engines: {node: '>=18'}
@@ -1201,6 +1291,12 @@ packages:
cpu: [x64]
os: [linux]
+ '@esbuild/linux-x64@0.27.3':
+ resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/netbsd-arm64@0.25.12':
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
engines: {node: '>=18'}
@@ -1213,6 +1309,12 @@ packages:
cpu: [arm64]
os: [netbsd]
+ '@esbuild/netbsd-arm64@0.27.3':
+ resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
'@esbuild/netbsd-x64@0.25.12':
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
engines: {node: '>=18'}
@@ -1225,6 +1327,12 @@ packages:
cpu: [x64]
os: [netbsd]
+ '@esbuild/netbsd-x64@0.27.3':
+ resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
'@esbuild/openbsd-arm64@0.25.12':
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
engines: {node: '>=18'}
@@ -1237,6 +1345,12 @@ packages:
cpu: [arm64]
os: [openbsd]
+ '@esbuild/openbsd-arm64@0.27.3':
+ resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.25.12':
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
engines: {node: '>=18'}
@@ -1249,6 +1363,12 @@ packages:
cpu: [x64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.27.3':
+ resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/openharmony-arm64@0.25.12':
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
engines: {node: '>=18'}
@@ -1261,6 +1381,12 @@ packages:
cpu: [arm64]
os: [openharmony]
+ '@esbuild/openharmony-arm64@0.27.3':
+ resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
'@esbuild/sunos-x64@0.25.12':
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
engines: {node: '>=18'}
@@ -1273,6 +1399,12 @@ packages:
cpu: [x64]
os: [sunos]
+ '@esbuild/sunos-x64@0.27.3':
+ resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/win32-arm64@0.25.12':
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
engines: {node: '>=18'}
@@ -1285,6 +1417,12 @@ packages:
cpu: [arm64]
os: [win32]
+ '@esbuild/win32-arm64@0.27.3':
+ resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
'@esbuild/win32-ia32@0.25.12':
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'}
@@ -1297,6 +1435,12 @@ packages:
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-ia32@0.27.3':
+ resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
'@esbuild/win32-x64@0.25.12':
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
engines: {node: '>=18'}
@@ -1309,6 +1453,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@esbuild/win32-x64@0.27.3':
+ resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@fastify/accept-negotiator@2.0.1':
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
@@ -2522,18 +2672,6 @@ packages:
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
- '@size-limit/esbuild@12.0.0':
- resolution: {integrity: sha512-r9i+HrtunIu7wAPtqD3t4DqfYin3kxPoMAv8cidkzlCS69IYCe3EG2UbQa10AdvQyaHTEK+MPkr9ifUd3W29og==}
- engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
- peerDependencies:
- size-limit: 12.0.0
-
- '@size-limit/file@12.0.0':
- resolution: {integrity: sha512-OzKYpDzWJ2jo6cAIzVsaPuvzZTmMLDoVCViEvsctmImxpXzwJZcuBEpPohFKKdgVdZuNTU8WstmvywPq55Njdw==}
- engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
- peerDependencies:
- size-limit: 12.0.0
-
'@so-ric/colorspace@1.1.6':
resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
@@ -3308,10 +3446,6 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
- bytes-iec@3.1.1:
- resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==}
- engines: {node: '>= 0.8'}
-
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@@ -3928,6 +4062,11 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ esbuild@0.27.3:
+ resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -4744,10 +4883,6 @@ packages:
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
engines: {node: '>= 12.0.0'}
- lilconfig@3.1.3:
- resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
- engines: {node: '>=14'}
-
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -5172,14 +5307,6 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- nanoid@5.1.6:
- resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
- engines: {node: ^18 || >=20}
- hasBin: true
-
- nanospinner@1.2.2:
- resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==}
-
nanostores@1.1.0:
resolution: {integrity: sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==}
engines: {node: ^20.0.0 || >=22.0.0}
@@ -5929,16 +6056,6 @@ packages:
engines: {node: '>=14.0.0', npm: '>=6.0.0'}
hasBin: true
- size-limit@12.0.0:
- resolution: {integrity: sha512-JBG8dioIs0m2kHOhs9jD6E/tZKD08vmbf2bfqj/rJyNWqJxk/ZcakixjhYtsqdbi+AKVbfPkt3g2RRZiKaizYA==}
- engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
- hasBin: true
- peerDependencies:
- jiti: ^2.0.0
- peerDependenciesMeta:
- jiti:
- optional: true
-
slashes@3.0.12:
resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==}
@@ -7626,156 +7743,234 @@ snapshots:
'@esbuild/aix-ppc64@0.27.2':
optional: true
+ '@esbuild/aix-ppc64@0.27.3':
+ optional: true
+
'@esbuild/android-arm64@0.25.12':
optional: true
'@esbuild/android-arm64@0.27.2':
optional: true
+ '@esbuild/android-arm64@0.27.3':
+ optional: true
+
'@esbuild/android-arm@0.25.12':
optional: true
'@esbuild/android-arm@0.27.2':
optional: true
+ '@esbuild/android-arm@0.27.3':
+ optional: true
+
'@esbuild/android-x64@0.25.12':
optional: true
'@esbuild/android-x64@0.27.2':
optional: true
+ '@esbuild/android-x64@0.27.3':
+ optional: true
+
'@esbuild/darwin-arm64@0.25.12':
optional: true
'@esbuild/darwin-arm64@0.27.2':
optional: true
+ '@esbuild/darwin-arm64@0.27.3':
+ optional: true
+
'@esbuild/darwin-x64@0.25.12':
optional: true
'@esbuild/darwin-x64@0.27.2':
optional: true
+ '@esbuild/darwin-x64@0.27.3':
+ optional: true
+
'@esbuild/freebsd-arm64@0.25.12':
optional: true
'@esbuild/freebsd-arm64@0.27.2':
optional: true
+ '@esbuild/freebsd-arm64@0.27.3':
+ optional: true
+
'@esbuild/freebsd-x64@0.25.12':
optional: true
'@esbuild/freebsd-x64@0.27.2':
optional: true
+ '@esbuild/freebsd-x64@0.27.3':
+ optional: true
+
'@esbuild/linux-arm64@0.25.12':
optional: true
'@esbuild/linux-arm64@0.27.2':
optional: true
+ '@esbuild/linux-arm64@0.27.3':
+ optional: true
+
'@esbuild/linux-arm@0.25.12':
optional: true
'@esbuild/linux-arm@0.27.2':
optional: true
+ '@esbuild/linux-arm@0.27.3':
+ optional: true
+
'@esbuild/linux-ia32@0.25.12':
optional: true
'@esbuild/linux-ia32@0.27.2':
optional: true
+ '@esbuild/linux-ia32@0.27.3':
+ optional: true
+
'@esbuild/linux-loong64@0.25.12':
optional: true
'@esbuild/linux-loong64@0.27.2':
optional: true
+ '@esbuild/linux-loong64@0.27.3':
+ optional: true
+
'@esbuild/linux-mips64el@0.25.12':
optional: true
'@esbuild/linux-mips64el@0.27.2':
optional: true
+ '@esbuild/linux-mips64el@0.27.3':
+ optional: true
+
'@esbuild/linux-ppc64@0.25.12':
optional: true
'@esbuild/linux-ppc64@0.27.2':
optional: true
+ '@esbuild/linux-ppc64@0.27.3':
+ optional: true
+
'@esbuild/linux-riscv64@0.25.12':
optional: true
'@esbuild/linux-riscv64@0.27.2':
optional: true
+ '@esbuild/linux-riscv64@0.27.3':
+ optional: true
+
'@esbuild/linux-s390x@0.25.12':
optional: true
'@esbuild/linux-s390x@0.27.2':
optional: true
+ '@esbuild/linux-s390x@0.27.3':
+ optional: true
+
'@esbuild/linux-x64@0.25.12':
optional: true
'@esbuild/linux-x64@0.27.2':
optional: true
+ '@esbuild/linux-x64@0.27.3':
+ optional: true
+
'@esbuild/netbsd-arm64@0.25.12':
optional: true
'@esbuild/netbsd-arm64@0.27.2':
optional: true
+ '@esbuild/netbsd-arm64@0.27.3':
+ optional: true
+
'@esbuild/netbsd-x64@0.25.12':
optional: true
'@esbuild/netbsd-x64@0.27.2':
optional: true
+ '@esbuild/netbsd-x64@0.27.3':
+ optional: true
+
'@esbuild/openbsd-arm64@0.25.12':
optional: true
'@esbuild/openbsd-arm64@0.27.2':
optional: true
+ '@esbuild/openbsd-arm64@0.27.3':
+ optional: true
+
'@esbuild/openbsd-x64@0.25.12':
optional: true
'@esbuild/openbsd-x64@0.27.2':
optional: true
+ '@esbuild/openbsd-x64@0.27.3':
+ optional: true
+
'@esbuild/openharmony-arm64@0.25.12':
optional: true
'@esbuild/openharmony-arm64@0.27.2':
optional: true
+ '@esbuild/openharmony-arm64@0.27.3':
+ optional: true
+
'@esbuild/sunos-x64@0.25.12':
optional: true
'@esbuild/sunos-x64@0.27.2':
optional: true
+ '@esbuild/sunos-x64@0.27.3':
+ optional: true
+
'@esbuild/win32-arm64@0.25.12':
optional: true
'@esbuild/win32-arm64@0.27.2':
optional: true
+ '@esbuild/win32-arm64@0.27.3':
+ optional: true
+
'@esbuild/win32-ia32@0.25.12':
optional: true
'@esbuild/win32-ia32@0.27.2':
optional: true
+ '@esbuild/win32-ia32@0.27.3':
+ optional: true
+
'@esbuild/win32-x64@0.25.12':
optional: true
'@esbuild/win32-x64@0.27.2':
optional: true
+ '@esbuild/win32-x64@0.27.3':
+ optional: true
+
'@fastify/accept-negotiator@2.0.1': {}
'@fastify/busboy@3.2.0': {}
@@ -9135,16 +9330,6 @@ snapshots:
'@shikijs/vscode-textmate@10.0.2': {}
- '@size-limit/esbuild@12.0.0(size-limit@12.0.0(jiti@2.6.1))':
- dependencies:
- esbuild: 0.27.2
- nanoid: 5.1.6
- size-limit: 12.0.0(jiti@2.6.1)
-
- '@size-limit/file@12.0.0(size-limit@12.0.0(jiti@2.6.1))':
- dependencies:
- size-limit: 12.0.0(jiti@2.6.1)
-
'@so-ric/colorspace@1.1.6':
dependencies:
color: 5.0.3
@@ -9714,7 +9899,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:
@@ -10165,8 +10350,6 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
- bytes-iec@3.1.1: {}
-
cac@6.7.14: {}
call-bind-apply-helpers@1.0.2:
@@ -10768,6 +10951,35 @@ snapshots:
'@esbuild/win32-ia32': 0.27.2
'@esbuild/win32-x64': 0.27.2
+ esbuild@0.27.3:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.3
+ '@esbuild/android-arm': 0.27.3
+ '@esbuild/android-arm64': 0.27.3
+ '@esbuild/android-x64': 0.27.3
+ '@esbuild/darwin-arm64': 0.27.3
+ '@esbuild/darwin-x64': 0.27.3
+ '@esbuild/freebsd-arm64': 0.27.3
+ '@esbuild/freebsd-x64': 0.27.3
+ '@esbuild/linux-arm': 0.27.3
+ '@esbuild/linux-arm64': 0.27.3
+ '@esbuild/linux-ia32': 0.27.3
+ '@esbuild/linux-loong64': 0.27.3
+ '@esbuild/linux-mips64el': 0.27.3
+ '@esbuild/linux-ppc64': 0.27.3
+ '@esbuild/linux-riscv64': 0.27.3
+ '@esbuild/linux-s390x': 0.27.3
+ '@esbuild/linux-x64': 0.27.3
+ '@esbuild/netbsd-arm64': 0.27.3
+ '@esbuild/netbsd-x64': 0.27.3
+ '@esbuild/openbsd-arm64': 0.27.3
+ '@esbuild/openbsd-x64': 0.27.3
+ '@esbuild/openharmony-arm64': 0.27.3
+ '@esbuild/sunos-x64': 0.27.3
+ '@esbuild/win32-arm64': 0.27.3
+ '@esbuild/win32-ia32': 0.27.3
+ '@esbuild/win32-x64': 0.27.3
+
escalade@3.2.0: {}
escape-string-regexp@5.0.0: {}
@@ -11688,8 +11900,6 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.2
lightningcss-win32-x64-msvc: 1.30.2
- lilconfig@3.1.3: {}
-
lines-and-columns@1.2.4: {}
lint-staged@16.2.7:
@@ -12371,12 +12581,6 @@ snapshots:
nanoid@3.3.11: {}
- nanoid@5.1.6: {}
-
- nanospinner@1.2.2:
- dependencies:
- picocolors: 1.1.1
-
nanostores@1.1.0: {}
neotraverse@0.6.18: {}
@@ -13243,16 +13447,6 @@ snapshots:
arg: 5.0.2
sax: 1.4.3
- size-limit@12.0.0(jiti@2.6.1):
- dependencies:
- bytes-iec: 3.1.1
- lilconfig: 3.1.3
- nanospinner: 1.2.2
- picocolors: 1.1.1
- tinyglobby: 0.2.15
- optionalDependencies:
- jiti: 2.6.1
-
slashes@3.0.12: {}
slice-ansi@7.1.2:
@@ -13600,7 +13794,7 @@ snapshots:
tsx@4.21.0:
dependencies:
- esbuild: 0.27.2
+ esbuild: 0.27.3
get-tsconfig: 4.13.0
optionalDependencies:
fsevents: 2.3.3