# 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 its `connectedCallback` hasn't run. - That announcement is **gone for good**: `connectedCallback` does 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 Lives in `src/core/globals.ts` (not `src/index.ts`), together with the `HTMLElementEventMap` augmentation for the internal bubbling events. That module is imported only by the npm entry `src/index.npm.ts` and by `test/setup.ts`, and is excluded from the JSR tarball — JSR's "no slow types" check forbids `declare global`. Its `import type { … } from '../elements/index.ts'` is type-only, erased at build, backs no `define()` — order doesn't matter. See [07](./07-tooling-and-build.md). ## 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.define`d 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`.