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)
49 lines
1.5 KiB
TypeScript
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
|
|
}
|