Skip to content

Reduce the bundle size without changing behavior - #121

Merged
MattCCC merged 3 commits into
masterfrom
bundle-size
Sep 27, 2026
Merged

MattCCC merged 3 commits into
masterfrom
bundle-size

Conversation

@MattCCC

@MattCCC MattCCC commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

This pull request trims the bundle by removing duplicated code and compiler helpers, without changing what the library does. The bundles get 0.1–0.2 KB smaller (2–4%), whichever bundler the app uses.

✨ Features & Improvements

  • Error classes without the class field helper: FetchError declared its properties as class fields, which the ES2018 build compiles into a defineProperty helper and one call per field. They're now only declared, and set in the constructor in the same order, so errors have exactly the same properties.
  • Cache key headers as one pattern: the 20 headers that go into cache keys are matched by one case-insensitive regex instead of a Set of names plus lowercasing. It matches exactly the same names, which was checked for every name in several cases and for near misses like accept-charset or x-api-keys.
  • Less duplicated code:
    • The retry loop shares one wait-and-backoff step between retries of failed and successful responses.
    • Interceptors are merged in a loop with [].concat().
    • Revalidator entries are written once, whether they're new or updated in place.
    • The common part of cache keys is built once, and cache expiry is scheduled by one helper.
    • The File check next to the Blob check is dropped, as a File is a Blob.
  • No transpiled optional chaining or redundant promises: ?. compiles to longer code in the ES2018 build, so it's replaced with plain checks where the result is identical. Async functions return values directly instead of wrapping them in Promise.resolve() or Promise.reject().

A benchmark later showed that one of these changes, extending custom fetcher responses with one Object.assign(), made requests with interceptors 2.4% slower. #123 undoes it. Cache key generation got faster (+3% to +11% across runs), and fetchf() and buildConfig() were within the noise.

🔍 Tried and Left Out

  • Terser: the published files get 0.4 KB smaller (gzip), but 0.2 KB bigger once apps re-minify them with esbuild, as Vite and size-limit do, and the same with webpack's terser.
  • ES2020 build target: it would save 0.1 KB (gzip), but the README promises ES2018 support.
  • Skipping tsup's Rollup tree-shaking: the ESM files get 0.2 KB smaller, but the IIFE and CommonJS files 0.15–0.5 KB bigger, and nothing changes after re-minification.

🧪 Tests

  • All 895 tests pass on Node.js 18 and 22 with 100% coverage, and the generated type declarations are identical.
  • The bundles built before and after this pull request give identical results in ESM and CommonJS for the same scenarios: responses and their methods, errors and the order of their properties, retries (including POST not being retried), the interceptor order of createApiFetcher(), cache keys, ETag revalidation, mutate() and merged default headers.

📦 Bundle Size

Bundle (size-limit, brotli) master this PR
browser/index.mjs 8.14 kB 7.97 kB
browser/index.global.js 8.34 kB 8.16 kB
node/index.js 8.23 kB 8.14 kB
react/index.mjs 11.30 kB 11.16 kB
react/index.js 12.15 kB 12.05 kB

Minified, the core goes from 21.8 kB to 21.0 kB (−3.8%), and gzipped from 9.2 kB to 9.0 kB (−2.4%). Measured like in the README comparison, fetchff goes from 9.1 KB to 8.9 KB, and with its React hook from 9.9 KB to 9.7 KB. #120 has the rebuilt dist and the new numbers in the README.

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
dist/browser/index.mjs 7.78 KB (-2.13% 🔽)
dist/browser/index.global.js 7.97 KB (-2.08% 🔽)
dist/node/index.js 7.95 KB (-1.05% 🔽)
dist/react/index.mjs 10.9 KB (-1.24% 🔽)
dist/react/index.js 11.77 KB (-0.83% 🔽)

@MattCCC MattCCC mentioned this pull request Sep 27, 2026
@MattCCC
MattCCC merged commit 9c3b444 into master Sep 27, 2026
14 checks passed
@MattCCC
MattCCC deleted the bundle-size branch September 27, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant