Skip to content

Build1 publisher3 min readPublished

One component, three honest byte counts: why published bundle sizes answer nobody's question

A UI library maintainer measured the same directive three defensible ways and got 6.0 kB, 9.8 kB, and less. The spread is not noise; it is three different questions.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying One component, three honest byte counts: why published bundle sizes answer nobody's question
Generated illustration

What happened

  • The author has been building ShipUI for about 3 years, described as a zoneless signal based Angular UI library with zero runtime dependencies.
  • The author wanted to publish honest per-component sizes on the library's site, such as what a button actually costs you, and found it harder than expected because the question has about three different answers depending on how it is measured.
  • The author states the different measurement answers are not close and are about 3x apart.
  • For one component, a sortable directive, the three measurements were 6.0 kB gzipped, 9.8 kB gzipped, and something smaller again once you already use the library.
  • Method one used Bun.build with minify true, target browser and external set to ['*'], then gzipped the output; the external wildcard means shared Angular code, rxjs and the author's own internal utils are not counted, leaving only the component's own code.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

The maintainer of ShipUI, a zoneless signal-based Angular component library with no runtime dependencies, set out to publish honest per-component byte costs and could not get the measurements to agree [1][2]. The same sortable directive came out at 6.0 kB gzipped under one method and 9.8 kB gzipped under another, with a third method producing something smaller again for developers already using the library [4].

Method one builds each component's own bundle minified with `external: ['*']`, so shared Angular code, rxjs and the library's internal utilities are excluded from the count [5]. The author is explicit that this is the floor: it measures how much code he wrote, not how much your application grows [6]. Method two is the delta test. Build an empty zoneless app, note the gzipped size, add one namespace import of the component, build again, subtract [7]. Every build runs the production pipeline, so AOT, esbuild and tree shaking all apply, and subtracting the baseline removes Angular's fixed runtime cost [7]. The namespace import is pinned alive with a global `__keep` reference specifically so tree shaking cannot drop unused exports and hand back a flattering number [8]. That discipline is the part most vendor size tables skip.

The headline in the original post says the answers are 3x apart [3], but the published pair for the sortable directive is 6.0 versus 9.8, a ratio of about 1.6 [1]. The real spread shows up elsewhere, in the second experiment. Measured alone against an empty app, button-group comes in at roughly 11.5 kB and tabs at roughly 11.2 kB, a naive sum of about 22.7 kB [10]. Built together, the pair costs about 12.0 kB [10]. The overcount is roughly 10.7 kB, meaning the additive reading is about 1.9 times the measured truth [2].

The cause is unremarkable and that is the point. Both components extend a shared selection-group base class handling roving focus, aria wiring and keyboard navigation, which itself pulls in a keybindings service and internal utilities [11]. Measured in isolation, each component is charged for the whole chain; in a real build the chain ships once [11][13]. So the marginal cost of the second component is about 0.5 kB [3], which the author describes as under 1 kB [12], or roughly 4 percent of its own standalone figure [3]. Against tabs measured alone, that is a spread of at least 11x on one component depending on what else is in the bundle [4].

Practically, there are three questions and they do not share an answer. How much code did the author write. What does the first use cost in an otherwise empty app. What does the nth use cost in an app that already carries the shared base. Adding up rows in a size table answers none of them, because it charges shared code once per component instead of once per bundle [13]. The author's inversion holds: the more of the library you adopt, the cheaper each additional component gets, and a per-component table hides exactly that [14].

What to watch: whether any component library ships size numbers labelled with the method and the baseline, and whether the delta-against-empty-app figure gets paired with a marginal figure for an app that already imports a sibling. Until then, treat a single kB badge as a floor and measure your own build before and after. The tooling in this write-up is a few lines of Bun and a subtraction [5][7].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories