You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
leaflet-components/docs/06-load-order.md

81 lines
3.6 KiB
Markdown

# 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
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.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`.