// Wraps a Phoenix LiveView hook so its implementation module is fetched // over the network only when the hook actually mounts. esbuild's // `--splitting --format=esm` emits the dynamic import as its own chunk, // keeping it out of the core app.js bundle on pages that don't use it. // // Two flavors: // * lazyHook(loader) – plain dynamic-import wrapper. // * lazyMapHook(loader) – also ensures Leaflet is loaded and assigned // to window.L once before the impl mounts, // shared across every map page. type HookImpl = Record type ModuleLoader = () => Promise> interface LazyState { _impl?: HookImpl _pendingUpdate?: boolean _destroyed?: boolean } let leafletLoader: Promise | null = null function loadLeaflet(): Promise { if (!leafletLoader) { leafletLoader = import("../vendor/leaflet/leaflet.js").then((mod) => { // UMD: when imported as ESM, the namespace itself is the L value. // Some bundlers wrap it under .default — handle both. window.L = (mod as any).default ?? mod }) } return leafletLoader } // Pick the first non-lifecycle export from the module as the hook impl, // or use the named export matching `hookName` when given. function pickImpl(mod: Record, hookName?: string): HookImpl { if (hookName && mod[hookName]) return mod[hookName] for (const key of Object.keys(mod)) { if (key === "default") continue const v = mod[key] if (v && typeof v === "object" && ("mounted" in v || "updated" in v || "destroyed" in v)) { return v } } if (mod.default) return mod.default throw new Error("lazyHook: no hook export found in module") } function attach(hook: ViewHook & LazyState, impl: HookImpl) { hook._impl = impl for (const k of Object.keys(impl)) { if (k === "mounted" || k === "updated" || k === "destroyed") continue ;(hook as any)[k] = impl[k] } } export function lazyHook(loader: ModuleLoader, hookName?: string) { return { mounted(this: ViewHook & LazyState) { const hook = this loader().then((mod) => { if (hook._destroyed) return const impl = pickImpl(mod, hookName) attach(hook, impl) impl.mounted?.call(hook) if (hook._pendingUpdate) { impl.updated?.call(hook) hook._pendingUpdate = false } }) }, updated(this: ViewHook & LazyState) { if (this._impl?.updated) this._impl.updated.call(this) else this._pendingUpdate = true }, destroyed(this: ViewHook & LazyState) { this._destroyed = true this._impl?.destroyed?.call(this) }, } } export function lazyMapHook(loader: ModuleLoader, hookName?: string) { return { mounted(this: ViewHook & LazyState) { const hook = this Promise.all([loadLeaflet(), loader()]).then(([, mod]) => { if (hook._destroyed) return const impl = pickImpl(mod, hookName) attach(hook, impl) impl.mounted?.call(hook) if (hook._pendingUpdate) { impl.updated?.call(hook) hook._pendingUpdate = false } }) }, updated(this: ViewHook & LazyState) { if (this._impl?.updated) this._impl.updated.call(this) else this._pendingUpdate = true }, destroyed(this: ViewHook & LazyState) { this._destroyed = true this._impl?.destroyed?.call(this) }, } }