towerops/assets/js/lib/leaflet.ts
Graham McIntire e97748437c build(assets): split coverage hooks into a lazy-loaded chunk
Switch esbuild to ESM with --splitting and load app.js as type=module.
Heavy hooks now ship in their own chunk and only download when their
LiveView mounts.

- assets/js/lib/leaflet.ts: extract ensureLeaflet (vendored Leaflet
  loader) into a shared module with a typed window.L declaration.
- assets/js/hooks/coverage_hooks.ts: extract CoverageMap,
  MultiCoverageMap, and CoverageLocationPicker (~510 lines) with
  proper interface types for hook context and tightened typing on
  payloads / event handlers (CoveragePayload is now a real type).
- app.ts: replace those three inline hook bodies with lazyHook
  stubs that call import('./hooks/coverage_hooks') on first mount,
  forwarding lifecycle hooks once the chunk resolves. Also adds a
  scoped `declare const L: any` so the remaining Leaflet hooks
  type-check.
- root.html.heex: switch the script tag to type=module so the
  emitted ESM bundle can use dynamic import().
- esbuild config: add --splitting --format=esm.

Build output now produces:
  app.js                      1.2 MB
  coverage_hooks-<hash>.js   16  KB  (lazy)
  chunk-<hash>.js             2.4 KB  (shared, lazy)
2026-05-06 16:11:32 -05:00

49 lines
1.5 KiB
TypeScript

// Lazy-loads the vendored Leaflet bundle (priv/static/vendor/leaflet/)
// the first time any Leaflet hook mounts. Per AGENTS.md we can't put
// inline <script> tags in templates, so we inject the <link>/<script>
// from JS instead. Returns a cached promise so concurrent hooks share
// one load attempt.
declare global {
interface Window {
L?: unknown
}
}
let leafletLoad: Promise<void> | null = null
export function ensureLeaflet(): Promise<void> {
if (typeof window.L !== "undefined") return Promise.resolve()
if (leafletLoad) return leafletLoad
leafletLoad = new Promise<void>((resolve, reject) => {
if (!document.getElementById("leaflet-css")) {
const css = document.createElement("link")
css.id = "leaflet-css"
css.rel = "stylesheet"
css.href = "/vendor/leaflet/leaflet.css"
document.head.appendChild(css)
}
const existing = document.getElementById("leaflet-js") as HTMLScriptElement | null
if (existing) {
if (typeof window.L !== "undefined") {
resolve()
} else {
existing.addEventListener("load", () => resolve(), { once: true })
existing.addEventListener("error", (e) => reject(e), { once: true })
}
return
}
const script = document.createElement("script")
script.id = "leaflet-js"
script.src = "/vendor/leaflet/leaflet.js"
script.async = true
script.onload = () => resolve()
script.onerror = (e) => reject(e)
document.head.appendChild(script)
})
return leafletLoad
}