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.
81 lines
3.6 KiB
Markdown
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`.
|