Nothing ships that the page doesn't use. The AssetLoader pipeline is what enforces
that: you hand it a list of { name, selector, loader } descriptors, and for each one
it downloads the component's chunk only if the selector matches an element on the
page, then registers and instantiates it. This is presence-gated lazy loading,
and it runs over Vite's per-component code-splitting so every component is its own
dynamically-imported chunk.
The descriptor
You describe a component with three things:
processor.convert({
name: 'Intro',
selector: '.intro',
loader: () => import('../../modules/Intro/Intro.js'),
})
Each field maps to one job the pipeline does on your behalf — you supply the three values and never touch the machinery between them:
selectoris the presence gate. The pipeline only downloads the chunk ifdocument.querySelector(selector)finds an element. No match, no load.loaderis the dynamic import. It runs at most once; a component listed in several places on the page still downloads a single time.nameis the registration key — the class exported undername(exports[name]) is registered in Globals under it once the chunk resolves. Passnullfor a style-only entry (below).
You queue as many descriptors as you like, then call .load() once to drain them
together.
Why it dedups so carefully. Resolving a chunk takes several turns of the event
loop, and a sibling module may read the registry mid-flight. The pipeline registers a
harmless placeholder under the name the moment loading starts, then swaps the real
class in when the import resolves — so a consumer that looks up the component
mid-load gets a benign no-op instead of a missing entry. You never see this: you just
get the guarantee that one selector matched many times still loads and registers
once.
Style-only entries (name: null)
Pass name: null and the pipeline runs the loader — so the chunk downloads and its
imported CSS applies — but registers nothing and instantiates nothing:
processor.convert({
name: null, // no class to register
selector: '.intro',
loader: () => import('../../modules/Intro/Intro.js'),
})
This is the lever two-phase loading pulls on the critical path: get a module's JS and CSS ready while withholding the instantiation that would trigger reflow.
A style-only entry (name: null) is not the same as a CSS-only component
(an Instantiable with only a selector). The first runs a loader for its CSS and
registers nothing; the second is a real class whose lifecycle happens to be empty.
Registering a component in a Global or host
You add a component to the pipeline by adding a convert(...) call — either to a
Global's list (Crucial / Deferred) for a
top-level Module, or, for a nested Component, by having the host mix the matching
HasX trait whose bootXComponents() queues the same descriptor on the host's shared
processor (see Sub-components & HasX). Either
way the shape is identical: a name, a selector, a loader.
Related
- Two-phase loading — the two Globals that drive the top-level processors.
- Sub-components &
HasX— how a host queues its nested components. - The Globals registry — where
nameregisters, and the export-alias rule.