Give Your Coding Agent a Bundle Budget: size-limit Fails the Branch Before You Land It

2026-11-06来源 dev.toEnglish
Give Your Coding Agent a Bundle Budget: size-limit Fails the Branch Before You Land It

You asked your coding agent for a debounce on the search box. It came back with seven lines and green tests. One of those lines was import _ from "lodash", and one line in package.json added lodash to get a single function.

Nothing in your test suite cares. The diff looks small. But the main lodash package has no module field and no type: "module" in its package metadata, and a default import is the whole lodash object. Webpack's tree-shaking guide puts the rule plainly: dead-code elimination "relies on the static structure of ES2015 module syntax". So the bundler keeps all of it, and every visitor downloads a utility belt to run one function.

Nobody notices until the bundle has grown by a few of these. The fix is to make bundle size a check that fails, the same way a test fails, so the agent hears about it before you do.

Step 1: put a budget on each entry point

Size Limit calls itself "a performance budget tool for JavaScript". It bundles a file with all its dependencies, minifies and compresses it (Brotli by default), and "throws an error if the cost exceeds the limit". The current release is 14.3.0, which needs Node.js 22.19+, 24.5+ or 26+. Since 14.0.0 the small-library preset bundles with rolldown (changelog).

Install it with the preset and the plugin that powers --why for that preset:

npm install --save-dev size-limit \ @size-limit/preset-small-lib \ @size-limit/rolldown-why 

Then give every entry point its own budget in package.json. Here the app has a main entry and a search widget that loads on its own:

{ "scripts": { "size": "size-limit", "size:why": "size-limit --why" }, "size-limit": [ { "name": "app", "path": "src/index.js", "limit": "12 kB", "message": "Do not raise this limit. Fix the import." }, { "name": "search", "path": "src/search.js", "limit": "2 kB", "message": "Do not raise this limit. Fix the import." } ] } 

This uses the small-lib preset because it bundles each entry straight from source, with no build step. The README recommends that preset for libraries under 10 kB; for a full app it points to @size-limit/preset-app or @size-limit/file, which measure your built files. The budget-and-gate workflow below is the same with any of them.

The numbers are examples. Run npm run size once and read the current sizes. The README suggests about a kilobyte of headroom for a small library and 25% for an app. For a gate an agent has to pass, go tighter than that: a loose budget lets several heavy imports land before it says anything.

name labels each check in the report. message is, in the README's words, "an optional custom message to display additional information", and it is the line your agent will read when the check fails, so write it for the agent. If you ship a library rather than an app, the import option measures what a consumer pays for one export, for example "import": "{ formatPrice }".

A parcel on a weighing scale with a burnt-orange budget line, as a robotic arm lowers a heavy crate onto it
One budget per entry point, so a heavy import shows up on the check it belongs to.

Step 2: see what grew with --why

A red check tells you a budget broke. npm run size:why tells you why. With @size-limit/rolldown-why installed, size-limit --why analyses the bundle and writes an HTML report, rolldown-why.html, to the current directory. Pass --save-bundle <DIR> to put it somewhere else.

In the report for the search check, look for lodash: a default import brings the whole package along, so expect it to dwarf your few lines of widget code. That is the evidence to put in front of the agent, or in front of yourself before you accept the change.

The fix is one of two edits. A named import from lodash-es, which is published as ES modules with "sideEffects": false (package metadata), so the bundler can drop what you don't call:

import { debounce } from "lodash-es"; 

Or no dependency at all, because a debounce fits in a few lines:

export function debounce(fn, ms) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), ms); }; } 

Either way, run npm run size again.

Step 3: block the push, then block the merge

A check only helps if it runs without anyone remembering to run it. Put it in two places.

First, a pre-push hook. Git runs pre-push before every push, and per the githooks docs, "if this hook exits with a non-zero status, git push will abort without pushing anything". Husky keeps the hook in the repo, so every clone that runs npm install gets it:

npm install --save-dev husky npx husky init echo "npm run size" > .husky/pre-push 

husky init creates a pre-commit hook and a prepare script. Keep them or edit them; the line that matters here is the pre-push file.

The hook runs on the machine that pushes, and anyone can skip it. The git-push docs say that with --no-verify, "the hook is bypassed completely". An agent with shell access can type that flag as easily as you can. So the second place is CI, where the check runs whether or not anyone remembers it:

# .github/workflows/size.yml name: size on: pull_request: push: branches: [main] jobs: size: runs-on: ubuntu-latest env: HUSKY: 0 steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: 24 - run: npm ci - run: npm run size 

HUSKY: 0 follows Husky's advice to keep hooks from installing on CI servers. The version tags match the current setup-node README. If you want the size change posted on each pull request as a comment, andresz1/size-limit-action does that and fails the check when a limit is exceeded; its example workflow grants pull-requests: write. To actually block the merge, mark the size job as a required status check in your branch protection rules. Without that, a red check is only a warning.

Three gates in a row on one path: a turn checkpoint, a push checkpoint and a CI checkpoint, with a heavy crate stopped at the first
The same check at three gates: after the agent's turn, before the push, and in CI.

Step 4: hand the failure back to the agent

The pre-push hook stops the push, but by then the agent has finished and moved on. The cheaper moment is the end of the agent's turn, while it still has the change in context.

Without any tooling, that is a habit: after each task, run npm run size, and if it fails, paste the output back with a prompt that closes the easy exit:

npm run size fails on the "search" check. Do not change the size-limit section in package.json. Run npm run size:why, find the import that grew, and switch to a named ES-module import or drop the dependency. Run npm run size again until it passes. 

Put the "never raise a limit" rule in the instructions file your agent reads, too. The message field repeats it in the failure output.

In Hermes IDE, your own checks can decide when a task is done, so npm test and npm run size can both sit on that list. I build Hermes, so weigh this with that in mind. The per-repository setup lives in a worktree recipe; the recipe docs show how to list the checks. Whichever agent you run, Claude Code or another, the prompt above is what you send back when the size check fails.

Then read the change before it lands. The Review Desk (⌘G, Ctrl+Shift+G on Windows and Linux) shows everything the agent changed, by turn or by file, with risk flags. A new dependency shows up as a package.json and package-lock.json change in the turn that added it, and the package.json line shows exactly which package came in. If the agent quietly raised a limit instead of fixing the import, that is one line in the same diff. A line comment there goes back to the agent that wrote the line.

The Hermes Review Desk in By turn view, with two turns, a diff of src/db.ts and a drafted commit message
The Review Desk (⌘G) in By turn view: two turns listed, turn 1's diff of src/db.ts, the changed-files list, and a commit message drafted from the turns.

Once the size check and the tests pass, Land commits, opens a pull request or merges locally in one step, into the branch the task started from, and it can be undone. CI still runs the same npm run size on the pull request.

What a budget won't catch

A size budget measures bytes. It won't tell you a dependency is unmaintained, has a bad license or pulls in a postinstall script; those need their own review, which is why reading the lockfile change matters as much as the number.

It also only measures what you point it at. If an agent adds a new entry point, it has no budget until you add one, so add "new entry point means a new size-limit section" to the same instructions file.

Hermes IDE is free to download, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It isn't affiliated with Nous Research, Anthropic, OpenAI or any agent vendor. Download it, and if it helps you catch a heavy import before it lands, star it on GitHub.

If this was useful

A bundle budget is a small version of a bigger habit: decide what "done" means before work starts, and make a check enforce it. If your agents end up running as workers off a queue instead of in your terminal, the Event-Driven Architecture Pocket Guide covers the retries, idempotency and outbox patterns that keep that kind of system honest. It is one of 8 short books in the Pocket Guides for Developers series, on agents, RAG, prompts, observability, events, databases and system design: the whole series is here.

Event-Driven Architecture Pocket Guide: Saga, CQRS, Outbox, and the Traps Nobody Warns You About

原文:https://www.a8q9.cn/en/article/give-your-coding-agent-a-bundle-budget-size-limit-fails-the-branch-before-you-la-e61efa.html

相关文章