Performance
Granularity is the product's main promise. This page shows what it is measured in and how to verify it yourself.
The promise is short: the bundle grows with what you use, not with what the package contains. Below is what backs it, what it actually costs and how to measure it in your own project without taking anyone’s word.
Two sides, and they do not correlate
A component’s weight is code plus styles, and the two live apart:
| Component | CSS added to the sheet | JS, the component itself |
|---|---|---|
GrCard | 235 B | 2 779 B |
GrDialog | 670 B | 9 989 B |
GrDataTable | 2 911 B | 36 431 B |
GrSelect | 3 037 B | 23 216 B |
A component heavy in code may add almost nothing to the sheet, and the other way round. So “the weight of a component” is not a single number, and any table with one column is lying. The numbers are the same as on the home page: a measuring script takes them from the core, not a person.
Code: a subpath instead of a barrel
// one component and its graph
import { GrButton } from '@feugene/granularity/components/GrButton'
// the whole package — granum 2 rejects this import: ComponentRootImportError
import { GrButton } from '@feugene/granularity'The top line is granularity itself. A subpath’s weight is the gzip of everything
it pulls from dist, nested components included, but without external packages
and without CSS. The lightest subpath is the GrButtonGroup wrapper: on its own
it weighs 1.5 kB, but it is never used without its buttons — GrButton arrives
in it through a slot and is imported from its own subpath. The heaviest is
GrDataTable, at 115.8 kB. The whole package from the root is 655.4 kB.
Subpath weights must not be added up. The shared chunk is counted afresh in every row but paid for once: the sum of the five heaviest rows is 487.1 kB, while together they weigh 251.9 kB. A set is measured as a union rather than a sum — and that is an upper bound: the application’s bundler shakes the tree further and minifies again.
The full table for every subpath is generated from the built dist on every
release — it ships inside the package and appears on each component’s page in the
catalogue.
Styles: you pay for what you selected
CSS is not imported as files — granum assembles it for exactly the components you
listed in components and for their dependencies. What each component needs — its
classes, its own styles.css, its tokens — is recorded in the package manifest,
and the sheet is built from the manifests rather than from node_modules. The
spending breaks down like this:
- The first component brings the foundation — the tokens, the base layer, the preflight, the theme. That is a one-off.
- Each one after it adds its own few hundred bytes — from two hundred for a card to three kilobytes for a select.
- The second theme costs about one and a half kilobytes, not double: themes differ in role values, not in rules.
Not listing components at all is a working option: every component of the
provider then reaches the sheet. The difference between “everything” and “one” on
the shared sheet is roughly twofold rather than a hundredfold, because the
foundation is shared. Granular styles are worth having, but the effect is more
modest than it is for code, and saying so honestly is cheaper than explaining it
later.
Runtime
- Zero runtime dependencies in the core. The only mandatory external library
is
@floating-ui/dom, and it is declared as a peer dependency of the application so that it never arrives as a second copy. - A component’s CSS arrives through the manifest, not through the chunk. The
package’s JS chunks import no styles: granum takes the component’s
styles.cssfrom the manifest and places it in thegranum.componentslayer — only for the selected components. There is no separate style import, and that is not a convenience but a condition: otherwise granularity would break on the first forgotten import. - The token reference and the test helpers are separate subpaths
(
/tokens,/testing), and the core barrel does not import them. Code highlighting is not in the core at all: it lives in@feugene/granularity-code/highlight.
How to measure it yourself
- Build the application with one component and measure the gzip of the emitted CSS and JS.
- Add a second component and measure again. The difference is its real cost in your project.
- Compare against the barrel: granum will not let a root import through, so switch the guard off for the measurement (
js: { guard: 'off' }) and repeat. The difference is what the subpath spares you.
The numbers on this site are taken by scripts rather than by building an
application. In the table at the top of the page, granum builds the sheet from
the core’s manifest with no application sources, and esbuild builds the code from
the component’s subpath with vue, @floating-ui/dom and @feugene/fint-i18n
left external. So treat them as an order of magnitude: your result depends on the
bundler version, the minifier settings and on what else is in the graph.
What will speed nothing up
importStyle: trueon the auto-import resolver. The core does not need it: granum has already taken the components’ styles from the manifests, and the core publishes nocomponents/<Name>/styles.csssubpaths — the option, once enabled, would break the build.- Manual
styles/*.cssimports on top of the granum sheet. They add weight rather than removing it: granum has already placed the tokens, the base and the themes in its layers, and a file imported outside the layers will also override them in the cascade. - Narrowing
themes.namesto one theme to save space. The second theme costs about one and a half kilobytes — this is not the place to give up theme switching.