Performance & weight
All numbers on this page are measured, not estimated, and you can reproduce them:
- bundle costs with
npm run bundle:budget(Vite probes built against the packed library). The tables below are rendered from its JSON output,docs/data/bundle-sizes.json; - file sizes and the comparison with
node scripts/docs-compare.ts.
What it adds to your app
Each row is the difference against an empty app (React rows against an empty React app,
219.5 kB min / 67.7 kB gzip). Sizes aremin / gzip / brotli.
| You use | Entry JS | Lazy JS files ¹ |
|---|---|---|
pennants/art/1x1/ar + renderFlag (pennant) | +4.6 kB / +1.7 kB / +1.6 kB | 0 |
pennants/art/4x3/ar + renderFlag (rect) | +4.5 kB / +1.7 kB / +1.5 kB | 0 |
Both ar art sets + renderFlag (all four shapes) | +7.0 kB / +2.0 kB / +1.8 kB | 0 |
getPennantName | +1.8 kB / +1.0 kB / +785 B | 0 |
pennantUrl | +1.7 kB / +899 B / +661 B | 0 |
fetchPennant (pennants/fetch) | +4.1 kB / +2.1 kB / +1.7 kB | 0 |
loadPennant | +5.1 kB / +2.5 kB / +2.1 kB | 544 |
loadFlag, circle + rect | +5.1 kB / +2.5 kB / +2.1 kB | 544 |
pennants/element/register | +11.9 kB / +5.2 kB / +4.4 kB | 545 |
React <Pennant> | +10.0 kB / +4.7 kB / +3.9 kB | 545 |
React <Pennant shape="circle"> | +10.0 kB / +4.7 kB / +3.8 kB | 545 |
React <PennantSvg> + one art module | +8.1 kB / +3.7 kB / +2.9 kB | 0 |
React <PennantSvg shape="circle"> + one art module | +8.1 kB / +3.7 kB / +2.8 kB | 0 |
Vite 8.3.5 · min / gzip / brotli · 2026-10-22
¹ Emitted into your build, but downloaded only when needed: one chunk per flag and artwork set plus the loader maps. Fetch mode from pennants/fetch bundles its sanitizer and emits no lazy files.
Stylesheets
| You use | CSS | Lazy JS files ¹ |
|---|---|---|
CSS pennants/css, assetsInlineLimit set | +15.7 kB / +3.8 kB / +2.8 kB | 0 |
CSS pennants/css/cdn | +26.3 kB / +2.0 kB / +1.2 kB | 0 |
CSS pennants/css/rect, assetsInlineLimit set | +17.0 kB / +3.8 kB / +2.8 kB | 0 |
CSS pennants/css/rect/cdn | +26.7 kB / +1.9 kB / +1.1 kB | 0 |
CSS pennants/css/square, assetsInlineLimit set | +17.5 kB / +3.8 kB / +2.8 kB | 0 |
CSS pennants/css/circle, assetsInlineLimit set | +17.5 kB / +3.8 kB / +2.8 kB | 0 |
CSS pennants/css + pennants/css/circle on one page, assetsInlineLimit set | +33.1 kB / +6.9 kB / +5.3 kB | 0 |
CSS pennants/css, Vite defaults (inlined flags) | +376.2 kB / +53.0 kB / +43.8 kB | 0 |
Vite 8.3.5 · min / gzip / brotli · 2026-10-22
Each stylesheet also adds 27 bytes of entry JS. See FAQ: bundler asset imports for assetsInlineLimit; Next.js and Angular do not inline the flags.
What these numbers mean in practice:
- A static artwork import plus
renderFlagcosts 1.7 kB gzip for the first flag; more shapes of the same flag reuse its artwork. loadFlagand the components carry the loader maps up front (2.5 kB gzip forloadFlag) before any flag is shown. For a handful of known flags, static imports are cheaper;pennants/fetch(2.1 kB gzip, sanitizer included) ships no map at all. Fetch mode in<Pennant>,<pennant-flag>and the rootfetchPennantloads the sanitizer as a lazy chunk on the first fetch.- In a Next.js Server Component,
<PennantSvg>adds no client JavaScript: it renders to HTML.
Flag files
Measured on the 0.1.0 build with node scripts/docs-compare.ts (brotli is the sum of individually compressed files).
| Set | Raw | Brotli |
|---|---|---|
| All 271 pennant SVGs | 1.16 MB | 362 kB |
| All 271 rect / square / circle SVGs | 1.10 / 1.11 / 1.09 MB | 335 / 336 / 332 kB |
| Median SVG (pennant / rect) | — | 525 B / 430 B |
Largest SVG (rs, Serbia) | 62 kB | 15 kB |
Sprite (sprite/pennant.svg) | 1.15 MB | 248 kB |
PNG files average 0.9 kB (pennant 1x), 1.6 kB (pennant 2x) and 0.6–1.0 kB (other shapes, 1x only). Country names cost nothing: ISO names come from the runtime's Intl.DisplayNames; only the 22 non-ISO names (gb-eng, asean, ...) are bundled.
The npm tarball is 2.80 MB (9.29 MB unpacked) because it contains four shapes in every format. What reaches your users depends only on what you import.
How lazy chunks work
loadFlag holds a map of dynamic import() calls, one per flag and artwork set. Bundlers turn each entry into a separate chunk, so:
- your build output contains one small file per flag and artwork set, plus the maps, but
- a visitor downloads only the chunks for the flags actually shown, once, and the browser caches them.
<Pennant> and <pennant-flag> use the same loader and share an in-memory cache, so a flag shown many times is fetched and parsed once.
Keeping bundles minimal
- Import what you use:
import { getPennantName } from 'pennants'does not pull in any flag. - For a fixed, small set of flags, use static per-flag imports. Use
loadFlagor the components when the flag is chosen at runtime and the set is large. - Avoid
import * as flagsor building an object of all flag modules: that bundles all 271. - Do not iterate
codesand callloadFlagfor each one to show a list; use CSS classes or<img>for long lists. - With Vite, SvelteKit or Astro, use a
/cdnstylesheet or setassetsInlineLimitso the CSS does not inline every flag.
CDN or self-host?
jsDelivr (pennantUrl default) | Self-hosted (base option) | |
|---|---|---|
| Setup | None | Copy svg/ and png/ to your host |
| Availability | Third-party dependency | Same as your site |
| Privacy | Requests go to jsDelivr | No third party |
| CSP | Allow cdn.jsdelivr.net in img-src | img-src 'self' |
| Caching | Versioned URLs, cached long-term | Up to your cache headers |
pennantUrl defaults to a version-pinned base (DEFAULT_CDN_BASE), so URLs always match the installed version. Self-host with pennantUrl(code, { base: '/assets/pennants' }).