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

72 lines
3.2 KiB
Markdown

# 06 — Load order
## The rule
**Export order in `src/index.ts` is load-bearing.** A component that listens
for a bubbling announcement from another tag must be exported — and therefore
`customElements.define()`d — **before** that other tag.
## 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:
- 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):
```
LeafletMap ← nothing can register before the root exists
LeafletControlLayers, LeafletLayerGroup,
LeafletFeatureGroup ← listen for leaflet-register from arbitrary layers
every concrete layer type (Marker, Circle,
Polygon, TileLayer, …)
LeafletLine ← child of Polygon/Polyline
LeafletPopup, LeafletTooltip ← child of any layer
LeafletIcon, LeafletDivIcon ← child of Marker
```
Standalone controls (`LeafletControlZoom`, `LeafletControlAttribution`,
`LeafletControlScale`) 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`) come first and carry no
`customElements.define`, so their order is free.
## The `HTMLElementTagNameMap` augmentation
At the bottom of `src/index.ts`. Because `export { X } from '…'` re-exports
`X` without binding it locally, the tag map needs its **own** `import type` of
every component class alongside the re-export. Those imports are type-only,
erased at build, back no `define()` — their 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`.