Claude Fable 5.1 & GPT-6 Astra packages are live

Bundle Size

Free

Keeping JavaScript small — measuring before cutting, dependency discipline, tree shaking that actually works, and budgets enforced in CI.

202 lines8.2 KB Mistral Performance
targetModels
Mistral Medium 3.5Mistral Large 3Mistral Small 4Mistral FamilyFuture Mistral Models
name
bundle-size
category
Performance
description
Keeping JavaScript small — measuring before cutting, dependency discipline, tree shaking that actually works, and budgets enforced in CI.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Mistral: scripts/model-profiles.json -->

#How to apply this file

Each section opens with one imperative line; apply every rule in the section it introduces. Do not summarise or skip a section.


#Purpose

Rules for controlling shipped JavaScript. A byte of JavaScript costs far more than a byte of image: it must be downloaded, parsed, compiled and executed on the main thread, on a device you do not control.

Two rules govern everything: measure before cutting, and enforce a budget, because bundles grow one innocuous pull request at a time.


#Budget first, in CI

[INST] Apply every rule in this section: Budget first, in CI. [/INST]

json
// .size-limit.json
[
  { "path": "dist/assets/index-*.js", "limit": "160 KB" },
  { "path": "dist/assets/vendor-*.js", "limit": "120 KB" }
]
  • Measure compressed size (Brotli/gzip) — that is what users download — and track uncompressed too, because parse and execute cost scales with the uncompressed bytes.
  • Fail the build on a regression. A warning is ignored; a failing check is discussed.
  • Report the delta on every pull request, so the cost of a dependency is visible at the moment someone proposes it.

Reasonable starting targets for an application shell: < 160 KB compressed initial JavaScript, and < 100 KB for a content site. Treat them as budgets to defend, not achievements to reach once.


#Analyse before optimising

[INST] Apply every rule in this section: Analyse before optimising. [/INST]

bash
npx vite-bundle-visualizer                 # Vite/Rollup
ANALYZE=true next build                    # Next.js
npx source-map-explorer 'dist/**/*.js'     # any bundler with source maps

It is almost always one or two dependencies, not a hundred small things. Look for:

  • The single largest module.
  • Duplicate copies of one library at different versions — check with npm ls <pkg> and deduplicate in the lockfile.
  • Anything in the initial chunk not needed for first paint.
  • Polyfills for browsers you no longer support: check the browserslist target, which frequently still says something from years ago.

#Dependency discipline

[INST] Apply every rule in this section: Dependency discipline. [/INST]

The highest-value habit: check the cost before adding, not after.

Instead ofUse
moment (~70 KB)Intl.DateTimeFormat, or date-fns/dayjs
lodash (whole)Named imports, or the three lines you need
axiosfetch with a small wrapper
uuidcrypto.randomUUID()
A deep-clone packagestructuredClone()
A charting suite for one sparklineInline SVG
ts
import _ from "lodash";               // ❌ pulls the whole library
import debounce from "lodash/debounce";   // ✅ one function
import { debounce } from "lodash-es";     // ✅ tree-shakeable build

The platform has absorbed most small utilities. Before adding a dependency, check bundlephobia.com for its cost including transitive dependencies, and check whether a standard API already does it.

Never ship a library for one function. A 70 KB dependency imported for debounce is the most common single avoidable regression.


#Make tree shaking work

[INST] Apply every rule in this section: Make tree shaking work. [/INST]

Tree shaking removes unused exports — but only when the bundler can prove removal is safe. It silently fails to shake when:

  • The package ships CommonJS only. require() is dynamic, so nothing can be proven. Prefer ESM builds.
  • The package has side effects at module scope and no "sideEffects": false in its package.json.
  • You import * as x and then index dynamically.
  • A barrel file (index.ts re-exporting everything) pulls in a module chain the bundler cannot prune. Barrel files are a common and invisible cause.
json
// In your own package: tell bundlers it is safe to drop unused modules
{ "sideEffects": ["*.css"] }

Verify rather than assume: build, then search the output for a symbol you believe was removed. Tree shaking is frequently believed to be working when it is not.


#What else to cut

[INST] Apply every rule in this section: What else to cut. [/INST]

  • Polyfills: target modern browsers and let older ones get a separate legacy bundle, rather than serving everyone the polyfills the oldest needs.
  • Locale and timezone data: import the active locale, not all forty.
  • Source maps: generate them, upload them to your error tracker, and do not serve them publicly.
  • Development-only code: assert that NODE_ENV is production so development branches are eliminated.
  • Duplicated framework runtimes: two versions of React in one bundle is both a size problem and a runtime bug.
  • Third-party scripts are not in your bundle but are on your critical path — analytics and tag managers are frequently the largest script on the page and nobody owns them. Inventory and defer them. → Frontend/performance

Then split what remains, so the initial download is only what the first screen needs. → Frontend/code-splitting


#Anti-patterns

[INST] Apply every rule in this section: Anti-patterns. [/INST]

Anti-patternWhy it failsFix
No size budget in CIGrowth is invisible until it is largesize-limit gate
Measuring uncompressed onlyNot what users downloadTrack compressed
Cutting before analysingEffort on the wrong modulesBundle analysis first
Adding a library for one utilityTens of KB for a few linesPlatform API or inline
Default-importing lodashWhole library includedNamed or lodash-es
Assuming tree shaking worksCommonJS and side effects silently prevent itVerify the output
Barrel files re-exporting everythingPulls in unprunable chainsImport directly
No sideEffects fieldBundler cannot drop unused modulesDeclare it
Stale browserslistPolyfills for browsers nobody usesUpdate the target
All locales bundledUsers download forty languages to read oneLoad the active locale
Duplicate library versionsSame code shipped twiceDeduplicate the lockfile
Source maps served publiclySource disclosureUpload to the error tracker only
Development code in production buildsDead branches shippedNODE_ENV=production
Unaudited third-party scriptsOften the largest script on the pageInventory and defer
One giant vendor chunkAny update invalidates all of itGroup by change frequency

#Checklist

  • Verify: A compressed-size budget is enforced in CI and fails the build
  • Verify: Uncompressed size is tracked for parse and execute cost
  • Verify: Pull requests report the bundle-size delta
  • Verify: The bundle has been analysed and the largest modules identified
  • Verify: No duplicate copies of a library exist at different versions
  • Verify: Dependency cost is checked before adding, not after
  • Verify: No dependency is included for a single small utility
  • Verify: Imports are named, from tree-shakeable ESM builds
  • Verify: sideEffects is declared in first-party packages
  • Verify: Tree shaking is verified against the built output, not assumed
  • Verify: Barrel files are not on hot import paths
  • Verify: browserslist reflects actually supported browsers
  • Verify: Only the active locale's data is bundled
  • Verify: Source maps are uploaded to error tracking, not served publicly
  • Verify: Development-only code is eliminated in production builds
  • Verify: Third-party scripts are inventoried, deferred and owned
  • Verify: Remaining code is split so the initial chunk serves the first screen only