3.6 KiB
06 — Load order
The rule
The order of the ./components/* side-effect imports in src/index.ts is
load-bearing. A component that listens for a bubbling announcement from
another tag must be imported — and therefore customElements.define()d —
before that other tag.
(Only the ./components/* modules call customElements.define. The element
classes themselves live in src/elements/* and the src/elements/index.ts
barrel; those carry no side effect, so their order is always free.)
Why
customElements.define(name, Class) upgrades every matching element already
in the document immediately and synchronously, running
connectedCallback right then for elements that are already connected.
On a real static-HTML page the markup is fully parsed before the deferred
module script runs, so at define() time the elements already exist. The
first define() for a given tag wins the race, and that ordering is fixed by
the sequence of import './components/leaflet-*.ts' statements in
src/index.ts:
- If a child tag is defined before its listening parent tag, every
instance of that child in the page upgrades and fires its one-shot
connect-time announcement (
leaflet-register,icon-changed,leaflet-line-sync,leaflet-crs-changed) into a parent element that has no listener attached yet — because the parent class isn't defined, so itsconnectedCallbackhasn't run. - That announcement is gone for good:
connectedCallbackdoes not fire again for an element that stays connected.
So parents must be defined first.
The ordering in src/index.ts
Root to leaf (this is the comment at the top of the file, kept in sync):
LeafletMapElement ← nothing can register before the root exists
LeafletControlLayersElement, LeafletLayerGroupElement,
LeafletFeatureGroupElement ← listen for leaflet-register from arbitrary layers
every concrete layer type (Marker, Circle,
Polygon, TileLayer, …)
LeafletLineElement ← child of Polygon/Polyline
LeafletPopupElement, LeafletTooltipElement ← child of any layer
LeafletIconElement, LeafletDivIconElement ← child of Marker
Standalone controls (LeafletControlZoomElement,
LeafletControlAttributionElement, LeafletControlScaleElement) have no such
relationship and can go anywhere.
The core re-exports (register.ts, props.ts, with-props.ts,
shared-props.ts, event-types.ts) and the export * from './elements/index.ts' come first and carry no customElements.define, so
their order is free.
The HTMLElementTagNameMap augmentation
At the bottom of src/index.ts. Because export * doesn't bind the
re-exported names locally, the tag map needs its own
import type { … } from './elements/index.ts'. That import is type-only,
erased at build, backs no define() — its order doesn't matter.
The test that guards this
test/load-order.test.ts is structurally unlike every other test file on
purpose: it never statically imports a component module, so nothing is
customElements.defined until it dynamically import()s src/index.ts
partway through — after building a DOM tree out of plain, undefined
elements. That's the only test reproducing a real page's actual order (markup
first, module script second).
Every other test file imports components up front, so components are already
defined before any element is created — they structurally cannot catch an
ordering regression. If you add a component with a "parent listens for a
child's announcement" relationship, extend load-order.test.ts.