From eec6bf9cf4b6510d8557c23e6c4fd9ff4f81afb4 Mon Sep 17 00:00:00 2001 From: ChukwuemekaP1 Date: Sat, 29 Aug 2026 07:29:18 +0100 Subject: [PATCH] feat: add bundle size checks and update size limits for all exports --- .github/workflows/ci.yml | 46 +++++++++++++++++++++ BUNDLE_SIZE.md | 88 ++++++++++++++++++++++++---------------- package.json | 62 +++++++++++++++++++++++++++- pnpm-lock.yaml | 29 +++++++++---- 4 files changed, 182 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0357e18..0a6a931 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,6 +73,52 @@ jobs: if: steps.should-run.outputs.run == 'true' run: pnpm --filter @wraith-protocol/test-vectors differential + bundle-size: + name: Bundle size (size-limit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: filter + if: github.event_name == 'pull_request' + with: + filters: | + src: + - 'src/**' + + - name: Determine whether to run + id: should-run + run: | + if [ "${{ github.event_name }}" != "pull_request" ] || [ "${{ steps.filter.outputs.src }}" = "true" ]; then + echo "run=true" >> "$GITHUB_OUTPUT" + else + echo "run=false" >> "$GITHUB_OUTPUT" + fi + + - uses: pnpm/action-setup@v4 + if: steps.should-run.outputs.run == 'true' + with: + version: 10 + + - uses: actions/setup-node@v4 + if: steps.should-run.outputs.run == 'true' + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + if: steps.should-run.outputs.run == 'true' + run: pnpm install --frozen-lockfile + + - name: Build SDK + if: steps.should-run.outputs.run == 'true' + run: pnpm build + + - name: Check bundle sizes + if: steps.should-run.outputs.run == 'true' + run: pnpm size + slow-tests: name: Property fuzz (nightly) runs-on: ubuntu-latest diff --git a/BUNDLE_SIZE.md b/BUNDLE_SIZE.md index bc1277e..cd7b5c3 100644 --- a/BUNDLE_SIZE.md +++ b/BUNDLE_SIZE.md @@ -1,56 +1,76 @@ -# Bundle Size Baseline — Stellar Entry +# Bundle Size Baselines — All Public Exports -> Last measured: 2026-06-23 +> Last measured: 2026-08-29 > Bundler: tsup (esbuild) via `size-limit` - -## Current Size - -| Format | Size (gzip) | Budget | -| ---------------- | ----------- | ------ | -| ESM (`import *`) | TBD | 20 KB | -| CJS (`require`) | TBD | 20 KB | - -> TBD — run `pnpm build && pnpm size` after installation to populate -> actual measurements, then update this table. - -## Dependency Graph - -Generate a visual treemap of the Stellar entry's dependency graph: +> Sizes are **minified and brotli-compressed**, include all bundled +> dependencies, and are measured by `size-limit` (SI units: 1 KB = 1000 B). + +## Current Sizes and Budgets + +Every budget is the measured baseline **+ 15% headroom**, rounded up. + +| Entry | Format | Baseline | Budget (+15%) | +| ------------------ | ---------------- | --------- | ------------- | +| Root (`.`) | ESM (`import *`) | 31.32 KB | 36.1 KB | +| Root (`.`) | CJS (`require`) | 138.79 KB | 159.7 KB | +| `./chains/evm` | ESM (`import *`) | 23.83 KB | 27.5 KB | +| `./chains/evm` | CJS (`require`) | 126.79 KB | 145.9 KB | +| `./chains/solana` | ESM (`import *`) | 17.15 KB | 19.8 KB | +| `./chains/solana` | CJS (`require`) | 25.89 KB | 29.8 KB | +| `./chains/ckb` | ESM (`import *`) | 20.52 KB | 23.6 KB | +| `./chains/ckb` | CJS (`require`) | 128.76 KB | 148.1 KB | +| `./vault` | ESM (`import *`) | 1.93 KB | 2.3 KB | +| `./vault` | CJS (`require`) | 2.07 KB | 2.4 KB | +| `./chains/stellar` | ESM (`import *`) | 26.48 KB | 30.5 KB | +| `./chains/stellar` | CJS (`require`) | 34.29 KB | 39.5 KB | + +> Stellar note: the original 20 KB-per-format budget predated any actual +> measurement (the baseline had never been populated). The current Stellar +> bundle exceeds 20 KB, so its budget was re-baselined on 2026-08-29 using +> the same baseline + 15% rule as the other entries. Entry paths and options +> are unchanged. + +## Checking Sizes (CI gate) + +`pnpm size` checks **every** entry in the `size-limit` array of +`package.json` and exits non-zero if any budget is exceeded. CI runs it on +every PR that touches `src/` (see the `bundle-size` job in +`.github/workflows/ci.yml`). ```bash -ANALYZE=true pnpm build -# produces stats/ folder with metafile data -npx esbuild-visualizer --metadata stats/metafile-stellar.json --open +pnpm build # size-limit measures dist/ output, so build first +pnpm size ``` -> `esbuild-visualizer` is an optional dev tool — install it globally or -> via `npx` when you need to inspect the graph. - -## Measurement Commands +## Measuring / Debugging -### esbuild (tsup) — via size-limit (CI gate) +Machine-readable output (exact byte counts): ```bash pnpm build -pnpm size +./node_modules/.bin/size-limit --json ``` -### Vite-style bundling — standalone esbuild +Generate a visual treemap of an entry's dependency graph: ```bash -pnpm measure:vite +ANALYZE=true pnpm build +# produces stats/ folder with metafile data +npx esbuild-visualizer --metadata stats/metafile-stellar.json --open ``` -Output written to `stats/vite-measurement.json`. +> `esbuild-visualizer` is an optional dev tool — install it globally or +> via `npx` when you need to inspect the graph. ## Budget Policy -The Stellar entry budget is **20 KB gzipped** for each format (ESM, CJS). - -- If a PR increases the Stellar bundle beyond the budget, CI will fail. -- Reviewers should verify no non-Stellar code was introduced into - `src/chains/stellar/` by checking imports. -- To adjust the budget, update the `size-limit` array in `package.json`. +- Each entry's budget is its measured baseline + 15% headroom (see table). +- If a PR increases any bundle beyond its budget, CI fails. +- Reviewers should verify no cross-chain code was introduced (e.g. nothing + from `evm/`, `solana/`, `ckb/`, or `agent/` leaking into + `src/chains/stellar/`) by checking imports. +- To adjust a budget, update the `size-limit` array in `package.json` and + this table together, and note the reason in the PR description. ## Known Optimizations diff --git a/package.json b/package.json index bbfe495..8dd6578 100644 --- a/package.json +++ b/package.json @@ -59,19 +59,75 @@ "test:fuzz": "FC_RUNS=100000 vitest run test/chains/stellar/properties.test.ts", "test:leak": "vitest run test/leaks/scan-leak.test.ts", "test:heap-leak": "node --expose-gc ./node_modules/vitest/vitest.mjs run test/leaks/heap-snapshot.test.ts --pool=threads", + "size": "size-limit", "api:check": "api-extractor run --config api-extractor.json && api-extractor run --config api-extractor-evm.json && api-extractor run --config api-extractor-stellar.json && api-extractor run --config api-extractor-solana.json && api-extractor run --config api-extractor-ckb.json && api-extractor run --config api-extractor-vault.json" }, "size-limit": [ + { + "name": "Root ESM (import *)", + "path": "dist/index.js", + "import": "*", + "limit": "36.1 KB" + }, + { + "name": "Root CJS (require)", + "path": "dist/index.cjs", + "limit": "159.7 KB" + }, + { + "name": "EVM ESM (import *)", + "path": "dist/chains/evm/index.js", + "import": "*", + "limit": "27.5 KB" + }, + { + "name": "EVM CJS (require)", + "path": "dist/chains/evm/index.cjs", + "limit": "145.9 KB" + }, + { + "name": "Solana ESM (import *)", + "path": "dist/chains/solana/index.js", + "import": "*", + "limit": "19.8 KB" + }, + { + "name": "Solana CJS (require)", + "path": "dist/chains/solana/index.cjs", + "limit": "29.8 KB" + }, + { + "name": "CKB ESM (import *)", + "path": "dist/chains/ckb/index.js", + "import": "*", + "limit": "23.6 KB" + }, + { + "name": "CKB CJS (require)", + "path": "dist/chains/ckb/index.cjs", + "limit": "148.1 KB" + }, + { + "name": "Vault ESM (import *)", + "path": "dist/vault/index.js", + "import": "*", + "limit": "2.3 KB" + }, + { + "name": "Vault CJS (require)", + "path": "dist/vault/index.cjs", + "limit": "2.4 KB" + }, { "name": "Stellar ESM (import *)", "path": "dist/chains/stellar/index.js", "import": "*", - "limit": "20 KB" + "limit": "30.5 KB" }, { "name": "Stellar CJS (require)", "path": "dist/chains/stellar/index.cjs", - "limit": "20 KB" + "limit": "39.5 KB" } ], "dependencies": { @@ -99,6 +155,7 @@ "@commitlint/config-conventional": "^19.6.0", "@microsoft/api-extractor": "^7.58.12", "@size-limit/esbuild": "^11.0.0", + "@size-limit/file": "^11.2.0", "@solana/web3.js": "^1.98.4", "@stellar/stellar-sdk": "^13.1.0", "@types/node": "^20.19.43", @@ -106,6 +163,7 @@ "fast-check": "^4.8.0", "husky": "^9.1.0", "prettier": "^3.4.0", + "size-limit": "^11.0.0", "tinybench": "^2.9.0", "tsup": "^8.4.0", "typedoc": "^0.28.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c0fc2d..1153438 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: '@size-limit/esbuild': specifier: ^11.0.0 version: 11.2.0(size-limit@11.2.0) + '@size-limit/file': + specifier: ^11.2.0 + version: 11.2.0(size-limit@11.2.0) '@solana/web3.js': specifier: ^1.98.4 version: 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) @@ -60,6 +63,9 @@ importers: prettier: specifier: ^3.4.0 version: 3.8.2 + size-limit: + specifier: ^11.0.0 + version: 11.2.0 tinybench: specifier: ^2.9.0 version: 2.9.0 @@ -306,7 +312,7 @@ importers: version: 0.76.9(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7)) '@react-native/metro-config': specifier: ^0.76.0 - version: 0.76.9(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(bufferutil@4.1.0)(utf-8-validate@6.0.6) + version: 0.76.9(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7)) '@react-native/typescript-config': specifier: ^0.76.0 version: 0.76.9 @@ -2147,7 +2153,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.18.31': resolution: {integrity: sha512-v9llw9fT3Uv+TCM6Xllo54t672CuYtinEQZ2LPJ2EJsCwuTc4Cd2gXQaouuIVD21VoeGQnr5JtJuWbF97sBKzQ==} @@ -2811,6 +2817,12 @@ packages: peerDependencies: size-limit: 11.2.0 + '@size-limit/file@11.2.0': + resolution: {integrity: sha512-OZHE3putEkQ/fgzz3Tp/0hSmfVo3wyTpOJSRNm6AmcwX4Nm9YtTfbQQ/hZRwbBFR23S7x2Sd9EbqYzngKwbRoA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + size-limit: 11.2.0 + '@solana/buffer-layout@4.0.1': resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} engines: {node: '>=5.10'} @@ -3215,6 +3227,7 @@ packages: '@xmldom/xmldom@0.9.10': resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} engines: {node: '>=14.6'} + deprecated: this version has critical issues, please update to the latest version JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} @@ -10160,7 +10173,7 @@ snapshots: - '@babel/preset-env' - supports-color - '@react-native/metro-config@0.76.9(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))(bufferutil@4.1.0)(utf-8-validate@6.0.6)': + '@react-native/metro-config@0.76.9(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7))': dependencies: '@react-native/js-polyfills': 0.76.9 '@react-native/metro-babel-transformer': 0.76.9(@babel/core@7.29.7)(@babel/preset-env@7.29.7(@babel/core@7.29.7)) @@ -10169,9 +10182,7 @@ snapshots: transitivePeerDependencies: - '@babel/core' - '@babel/preset-env' - - bufferutil - supports-color - - utf-8-validate '@react-native/normalize-colors@0.72.0': {} @@ -10423,6 +10434,10 @@ snapshots: nanoid: 5.1.16 size-limit: 11.2.0 + '@size-limit/file@11.2.0(size-limit@11.2.0)': + dependencies: + size-limit: 11.2.0 + '@solana/buffer-layout@4.0.1': dependencies: buffer: 6.0.3 @@ -16149,7 +16164,7 @@ snapshots: picomatch: 4.0.4 postcss: 8.5.9 rollup: 4.60.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 20.19.43 fsevents: 2.3.3 @@ -16165,7 +16180,7 @@ snapshots: picomatch: 4.0.4 postcss: 8.5.9 rollup: 4.60.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.6.0 fsevents: 2.3.3