Skip to content
HELiX

Bundle Size & Tree-Shaking

apps/docs/src/content/docs/guides/bundle-size Click to copy
Copied! apps/docs/src/content/docs/guides/bundle-size

HELiX is designed for tree-shaking. Import only the components you need and your bundler will exclude the rest.

The recommended approach is per-component imports:

// Only includes hx-button and hx-icon — everything else is tree-shaken
import '@helixui/library/components/hx-button';
import '@helixui/library/components/hx-icon';

This uses the package exports map to resolve each component to its individual entry point.

If you need all components (e.g., a CMS where any component may be used):

// Imports all components and registers all custom elements
import '@helixui/library';

This is larger but ensures all components are available without explicit imports.

HELiX maintains two complementary budget files at the repo root, each driving a different script:

FileDefault per-componentTotal libraryUsed by
.bundle-budget.json5 KB (gzipped)50 KB (gzipped)scripts/check-bundle-size.mjs (pnpm run check:bundle)
bundle-budgets.json16 KB (gzipped)200 KB (gzipped)scripts/bundle-size-report.js (pnpm run bundle-size:ci)

The 16 KB / 200 KB budget is the relaxed CI gate adopted in 3.2.1 to unblock the token-cascade campaign; the 5 KB / 50 KB budget remains as the long-term aspirational target. New work should aim at the aspirational budget; today’s CI floor is the relaxed budget.

Both files carry per-component overrides documenting reasons for individual components exceeding the default. The subset below shows the largest exceptions:

Component.bundle-budget.jsonbundle-budgets.jsonReason
hx-date-picker8 KB16 KBCalendar grid, date parsing, keyboard nav, localization
hx-color-picker7 KB16 KBGradient canvas, sliders, format conversion, swatches
hx-combobox7 KB16 KBTypeahead, multi-select chips, async loading
hx-select6.5 KB16 KBCustom listbox, keyboard nav, form participation
hx-time-picker6 KB16 KBTime parsing, 12/24h format, step intervals
hx-file-upload5.75 KB16 KBDrag-and-drop, file validation, progress tracking
hx-data-table8 KB(uses default)Sorting, row selection, keyboard grid navigation
hx-carousel(uses default)16 KBSlide management + accessibility plumbing

See the source files for the complete override list and rationale.

The @helixui/library package declares sideEffects in package.json so bundlers know which files must not be tree-shaken:

{
"sideEffects": [
"./dist/index.js",
"./dist/utilities/document-token-adoption.js",
"./dist/components/*/index.js",
"**/*.css"
]
}
  • Index barrel (./dist/index.js) re-exports every component module — importing it registers every custom element via the re-exported @customElement('hx-…') calls.
  • Token adoption (./dist/utilities/document-token-adoption.js) injects design tokens into document.adoptedStyleSheets.
  • Per-component entry points (./dist/components/*/index.js) are explicitly side-effectful because importing one registers that component’s custom element. This is what makes import '@helixui/library/components/hx-button' work — the bundler must keep the registration side effect even though the import binding is unused.
  • CSS files are always side-effectful.

Design tokens are adopted into document.adoptedStyleSheets automatically when any HELiX component module is imported. The first component import runs token adoption once; subsequent imports are no-ops. The overhead is shared across all components — it is not duplicated per-component.

ensureDocumentTokens is exported from the package root (@helixui/library) for callers that need to invoke it explicitly (for example, to pre-adopt tokens before a component upgrades, or to re-adopt into a new realm). It is not something per-component imports can opt out of — every component import already triggers it.

import { HelixButton } from '@helixui/library';
import { ensureDocumentTokens } from '@helixui/library';
// Idempotent — running a second time is a no-op
ensureDocumentTokens();
Terminal window
# Measure a specific component
node scripts/measure-component-size.js hx-button
# Full bundle analysis
node scripts/bundle-size-report.js
# Check against budgets
node scripts/check-bundle-size.mjs

No special configuration is needed for Vite. The exports map and sideEffects declaration work automatically.

For Webpack 5+, ensure sideEffects is respected (it is by default in production mode):

module.exports = {
optimization: {
sideEffects: true, // default in production
},
};

For non-bundled usage (CDN, script tags), pin a specific version and point at the published library entry. The library is ESM with bare imports, so the browser needs an import map for lit, @helixui/tokens, etc. — see Installation → CDN for the full pattern:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/css/helix-all.css" />
<script type="importmap">
{
"imports": {
"@helixui/library": "https://cdn.jsdelivr.net/npm/@helixui/library@3.9.0/dist/index.js",
"@helixui/tokens": "https://cdn.jsdelivr.net/npm/@helixui/tokens@3.9.0/dist/index.js",
"@helixui/icons": "https://cdn.jsdelivr.net/npm/@helixui/icons@1.0.0/dist/index.js",
"@floating-ui/dom": "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.7.6/+esm",
"lit": "https://cdn.jsdelivr.net/npm/lit@3/+esm",
"lit/": "https://cdn.jsdelivr.net/npm/lit@3/"
}
}
</script>
<script type="module">import '@helixui/library';</script>