@withl5e/l5e 1.1.0-alpha.0 → 1.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,3 +22,32 @@ export default defineConfig({
22
22
  ```
23
23
 
24
24
  See the root README and `examples/basic` for a full app.
25
+
26
+ Compact chunking is experimental and opt-in:
27
+
28
+ ```ts
29
+ coreVite({
30
+ chunking: {
31
+ mode: 'compact',
32
+ shared: [{ name: 'state', modules: ['~/stores/session.ts'] }],
33
+ islandRuntime: {
34
+ packages: ['@tanstack/react-query'],
35
+ modules: ['~/client/query-client.ts'],
36
+ },
37
+ },
38
+ });
39
+ ```
40
+
41
+ Compact mode limits the initial shell and each later island/action activation to
42
+ at most three new JavaScript assets. `load` and already-visible islands activate
43
+ immediately after the shell, so the cumulative cold request count can be higher.
44
+ The runtime starts every known static dependency request in parallel, waits for
45
+ the island CSS, then evaluates and mounts or hydrates. Dynamic descendants remain
46
+ lazy. List state that must survive page-bundle swaps in `shared`; list React
47
+ contexts or client caches that multiple islands must share in `islandRuntime`.
48
+ Only the configured static closure is included, and dynamic imports are not
49
+ promoted. Compact mode requires native import-map support.
50
+
51
+ The alpha renderer bridge expects Vite to emit one entry facade and one canonical
52
+ renderer export chunk. A different renderer shape is rejected during server setup
53
+ because merging multiple minified export namespaces can make aliases ambiguous.
package/dist/index.js CHANGED
@@ -4,6 +4,6 @@ import { a as j, i as M, n as N, o as P, r as F, t as I } from "./exceptions-D4O
4
4
  import { render as L } from "./entry-server.js";
5
5
  import { n as R, t as z } from "./middleware-CnDqZobD.js";
6
6
  import { t as B } from "./defineMiddleware-D017PWDc.js";
7
- import { a as V, n as H } from "./server-CvsOU9cg.js";
8
- import { t as U } from "./vite-plugin-D9qTnleZ.js";
7
+ import { a as V, n as H } from "./server-C35BtVu8.js";
8
+ import { t as U } from "./vite-plugin-BOuzxmb2.js";
9
9
  export { I as BadRequestException, T as Fragment, _ as HEAD_PRIORITY, b as Head, N as HttpException, F as InternalServerErrorException, M as NotFoundException, j as RedirectException, P as ServiceUnavailableException, C as addCacheTag, g as cloneElement, U as coreVite, z as createContext, H as createServer, B as defineMiddleware, r as escapeHTML, t as escapeProp, l as getCacheTags, x as getClientJsEntries, w as getCssEntries, d as getHeadContent, v as getIslandEntries, E as getIslandProps, f as getSchemas, p as getSsrIslands, S as isIslandPropsExternalized, y as isValidElement, h as jsxFactory, m as pushIslandProps, c as pushMetadata, D as pushSchema, A as registerIsland, u as registerSsrIsland, L as render, n as renderJsxToHtmlString, e as renderToRenderedNode, k as resolveMetadata, o as runInRenderContext, R as sequence, i as setViewName, V as startServer, O as useClientJs, s as useCss, a as useRequest };
@@ -0,0 +1,98 @@
1
+ import { n as e, t } from "../strategy-registry-wmHnsHQb.js";
2
+ import "virtual:l5e-island-strategies";
3
+ t("load", (e) => void e()), t("idle", (e) => {
4
+ "requestIdleCallback" in window ? requestIdleCallback(() => e()) : setTimeout(() => e(), 200);
5
+ }), t("visible", (e, t, n) => {
6
+ let r = new IntersectionObserver((t) => {
7
+ t.some((e) => e.isIntersecting) && (r.disconnect(), e());
8
+ }, { rootMargin: t || "200px" });
9
+ return r.observe(n), () => r.disconnect();
10
+ }), t("media", (e, t) => {
11
+ if (!t) {
12
+ console.error("[l5e-island] Strategy \"media\" requires mountOpts (media query)");
13
+ return;
14
+ }
15
+ let n = window.matchMedia(t);
16
+ if (n.matches) e();
17
+ else {
18
+ let t = (t) => t.matches && e();
19
+ return n.addEventListener("change", t, { once: !0 }), () => n.removeEventListener("change", t);
20
+ }
21
+ }), t("none", () => {});
22
+ var n = window.__L5E_ISLANDS__ ||= {}, r = null, i = /* @__PURE__ */ new WeakSet();
23
+ function a(e) {
24
+ let t = e.getAttribute("data-island-idx");
25
+ if (t === null) return JSON.parse(e.getAttribute("data-island-props") || "{}");
26
+ if (!r) {
27
+ let e = document.getElementById("_l5e_data_");
28
+ try {
29
+ let t = JSON.parse(e?.textContent || "[]");
30
+ r = Array.isArray(t) ? t : [];
31
+ } catch (e) {
32
+ console.error("[l5e-island] Failed to parse _l5e_data_ props store:", e), r = [];
33
+ }
34
+ }
35
+ return r[Number(t)] || {};
36
+ }
37
+ function o() {
38
+ return Array.from(document.querySelectorAll("[data-island]")).map((e) => ({
39
+ element: e,
40
+ registryKey: e.getAttribute("data-island"),
41
+ exportName: e.getAttribute("data-island-name"),
42
+ props: a(e),
43
+ mount: e.getAttribute("data-island-mount") || "load",
44
+ mountOpts: e.getAttribute("data-island-opts") || void 0,
45
+ ssr: e.hasAttribute("data-island-ssr")
46
+ }));
47
+ }
48
+ function s(e) {
49
+ let t = !1;
50
+ return async () => {
51
+ if (t) return;
52
+ t = !0;
53
+ let r = n[e.registryKey];
54
+ if (!r) {
55
+ console.error(`[l5e-island] Component "${e.registryKey}" not found in page registry.`);
56
+ return;
57
+ }
58
+ try {
59
+ for (let e of r.js) {
60
+ if (document.querySelector(`link[rel="modulepreload"][href="${CSS.escape(e)}"]`)) continue;
61
+ let t = document.createElement("link");
62
+ t.rel = "modulepreload", t.crossOrigin = "anonymous", t.href = e, document.head.append(t);
63
+ }
64
+ await Promise.all(r.css.map((e) => new Promise((t, n) => {
65
+ let r = document.querySelector(`link[rel="stylesheet"][href="${CSS.escape(e)}"]`);
66
+ if (r?.sheet) return t();
67
+ let i = r || document.createElement("link");
68
+ i.rel = "stylesheet", i.crossOrigin = "anonymous", i.href = e, i.addEventListener("load", () => t(), { once: !0 }), i.addEventListener("error", () => n(/* @__PURE__ */ Error(`Failed to load ${e}`)), { once: !0 }), r || document.head.append(i);
69
+ })));
70
+ let [t, n] = await Promise.all([import(
71
+ /* @vite-ignore */
72
+ r.renderer
73
+ ), import(
74
+ /* @vite-ignore */
75
+ r.module
76
+ )]), { reactDomClient: i, createElement: a } = t, o = n.default || n[e.exportName];
77
+ if (!o) throw Error(`No export "default" or "${e.exportName}" in module`);
78
+ e.ssr ? i.hydrateRoot(e.element, a(o, e.props)) : i.createRoot(e.element).render(a(o, e.props));
79
+ } catch (t) {
80
+ console.error(`[l5e-island] Failed to mount "${e.registryKey}":`, t);
81
+ }
82
+ };
83
+ }
84
+ function c() {
85
+ for (let t of o()) {
86
+ if (i.has(t.element) || !n[t.registryKey]) continue;
87
+ let r = e.get(t.mount);
88
+ if (!r) {
89
+ console.error(`[l5e-island] Strategy "${t.mount}" not found.`);
90
+ continue;
91
+ }
92
+ i.add(t.element), r(s(t), t.mountOpts, t.element);
93
+ }
94
+ }
95
+ globalThis.__L5E_BOOT_ISLANDS__ = c, document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", c) : c();
96
+ //#endregion
97
+
98
+ //# sourceMappingURL=compact-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-runtime.js","names":[],"sources":["../../src/island/compact-runtime.ts"],"sourcesContent":["import type { IslandMeta } from './types';\nimport { strategies, registerMountStrategy } from './strategy-registry';\n\nregisterMountStrategy('load', (mount) => void mount());\nregisterMountStrategy('idle', (mount) => {\n if ('requestIdleCallback' in window) requestIdleCallback(() => mount());\n else setTimeout(() => mount(), 200);\n});\nregisterMountStrategy('visible', (mount, opts, el) => {\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n observer.disconnect();\n mount();\n }\n },\n { rootMargin: opts || '200px' },\n );\n observer.observe(el);\n return () => observer.disconnect();\n});\nregisterMountStrategy('media', (mount, opts) => {\n if (!opts) {\n console.error('[l5e-island] Strategy \"media\" requires mountOpts (media query)');\n return;\n }\n const query = window.matchMedia(opts);\n if (query.matches) mount();\n else {\n const handler = (event: MediaQueryListEvent) => event.matches && mount();\n query.addEventListener('change', handler, { once: true });\n return () => query.removeEventListener('change', handler);\n }\n});\nregisterMountStrategy('none', () => {});\n\nimport 'virtual:l5e-island-strategies';\n\ntype CompactIslandModule = {\n reactDomClient: typeof import('react-dom/client');\n createElement: typeof import('react').createElement;\n module: Record<string, any>;\n};\ntype CompactIslandPlan = { module: string; renderer: string; js: string[]; css: string[] };\nconst registry: Record<string, CompactIslandPlan> = ((window as any).__L5E_ISLANDS__ ||= {});\nlet islandData: unknown[] | null = null;\nconst scheduled = new WeakSet<Element>();\n\nfunction props(element: Element): Record<string, unknown> {\n const index = element.getAttribute('data-island-idx');\n if (index === null) return JSON.parse(element.getAttribute('data-island-props') || '{}');\n if (!islandData) {\n const store = document.getElementById('_l5e_data_');\n try {\n const parsed = JSON.parse(store?.textContent || '[]');\n islandData = Array.isArray(parsed) ? parsed : [];\n } catch (error) {\n console.error('[l5e-island] Failed to parse _l5e_data_ props store:', error);\n islandData = [];\n }\n }\n return (islandData[Number(index)] as Record<string, unknown>) || {};\n}\n\nfunction discover(): IslandMeta[] {\n return Array.from(document.querySelectorAll('[data-island]')).map((element) => ({\n element: element as HTMLElement,\n registryKey: element.getAttribute('data-island')!,\n exportName: element.getAttribute('data-island-name')!,\n props: props(element),\n mount: element.getAttribute('data-island-mount') || 'load',\n mountOpts: element.getAttribute('data-island-opts') || undefined,\n ssr: element.hasAttribute('data-island-ssr'),\n }));\n}\n\nfunction mount(island: IslandMeta) {\n let mounted = false;\n return async () => {\n if (mounted) return;\n mounted = true;\n const plan = registry[island.registryKey];\n if (!plan) {\n console.error(`[l5e-island] Component \"${island.registryKey}\" not found in page registry.`);\n return;\n }\n try {\n // Schedule the complete known static closure before awaiting any response.\n // Modulepreload fetches without evaluating; native import retains ESM order.\n for (const href of plan.js) {\n if (document.querySelector(`link[rel=\"modulepreload\"][href=\"${CSS.escape(href)}\"]`))\n continue;\n const link = document.createElement('link');\n link.rel = 'modulepreload';\n link.crossOrigin = 'anonymous';\n link.href = href;\n document.head.append(link);\n }\n await Promise.all(\n plan.css.map(\n (href) =>\n new Promise<void>((resolve, reject) => {\n const existing = document.querySelector<HTMLLinkElement>(\n `link[rel=\"stylesheet\"][href=\"${CSS.escape(href)}\"]`,\n );\n if (existing?.sheet) return resolve();\n const link = existing || document.createElement('link');\n link.rel = 'stylesheet';\n link.crossOrigin = 'anonymous';\n link.href = href;\n link.addEventListener('load', () => resolve(), { once: true });\n link.addEventListener('error', () => reject(new Error(`Failed to load ${href}`)), {\n once: true,\n });\n if (!existing) document.head.append(link);\n }),\n ),\n );\n const [renderer, module] = await Promise.all([\n import(/* @vite-ignore */ plan.renderer),\n import(/* @vite-ignore */ plan.module),\n ]);\n const { reactDomClient, createElement } = renderer as Omit<\n CompactIslandModule,\n 'module'\n >;\n const Component = module.default || module[island.exportName];\n if (!Component) throw new Error(`No export \"default\" or \"${island.exportName}\" in module`);\n if (island.ssr) {\n reactDomClient.hydrateRoot(island.element, createElement(Component, island.props));\n } else {\n const root = reactDomClient.createRoot(island.element);\n root.render(createElement(Component, island.props));\n }\n } catch (error) {\n console.error(`[l5e-island] Failed to mount \"${island.registryKey}\":`, error);\n }\n };\n}\n\nfunction boot() {\n for (const island of discover()) {\n if (scheduled.has(island.element)) continue;\n // A fragment swap can expose an element before its activation plan is installed.\n if (!registry[island.registryKey]) continue;\n const strategy = strategies.get(island.mount);\n if (!strategy) {\n console.error(`[l5e-island] Strategy \"${island.mount}\" not found.`);\n continue;\n }\n scheduled.add(island.element);\n strategy(mount(island), island.mountOpts, island.element);\n }\n}\n\n(globalThis as any).__L5E_BOOT_ISLANDS__ = boot;\nif (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);\nelse boot();\n"],"mappings":"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"}
@@ -25,7 +25,7 @@ t("load", (e) => {
25
25
  return n.addEventListener("change", t, { once: !0 }), () => n.removeEventListener("change", t);
26
26
  }
27
27
  }), t("none", () => {});
28
- var n = window.__L5E_ISLANDS__ || {}, r = null;
28
+ var n = window.__L5E_ISLANDS__ ||= {}, r = null;
29
29
  function i() {
30
30
  if (r) return r;
31
31
  let e = document.getElementById("_l5e_data_");
@@ -64,14 +64,18 @@ function s(e) {
64
64
  return;
65
65
  }
66
66
  try {
67
- let [t, { createElement: n }, i] = await Promise.all([
67
+ let { reactDomClient: t, createElement: n, module: i } = typeof r == "function" ? await r() : await Promise.all([
68
68
  import("react-dom/client"),
69
69
  import("react"),
70
70
  import(
71
71
  /* @vite-ignore */
72
72
  r
73
73
  )
74
- ]), a = i.default || i[e.exportName];
74
+ ]).then(([e, t, n]) => ({
75
+ reactDomClient: e,
76
+ createElement: t.createElement,
77
+ module: n
78
+ })), a = i.default || i[e.exportName];
75
79
  if (!a) {
76
80
  console.error(`[l5e-island] No export "default" or "${e.exportName}" in module`);
77
81
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","names":[],"sources":["../../src/island/runtime.ts"],"sourcesContent":["import type { IslandMeta } from './types';\nimport { strategies, registerMountStrategy } from './strategy-registry';\n\n// ============================================================\n// PART 1: Built-in strategies\n// ============================================================\n\nregisterMountStrategy('load', (mount) => {\n mount();\n});\n\nregisterMountStrategy('idle', (mount) => {\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => mount());\n } else {\n setTimeout(() => mount(), 200);\n }\n});\n\nregisterMountStrategy('visible', (mount, opts, el) => {\n const rootMargin = opts || '200px';\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n observer.disconnect();\n mount();\n }\n });\n },\n { rootMargin },\n );\n observer.observe(el);\n return () => observer.disconnect();\n});\n\nregisterMountStrategy('media', (mount, opts) => {\n if (!opts) {\n console.error('[l5e-island] Strategy \"media\" requires mountOpts (media query)');\n return;\n }\n const mql = window.matchMedia(opts);\n if (mql.matches) {\n mount();\n } else {\n const handler = (e: MediaQueryListEvent) => {\n if (e.matches) mount();\n };\n mql.addEventListener('change', handler, { once: true });\n return () => mql.removeEventListener('change', handler);\n }\n});\n\nregisterMountStrategy('none', () => {\n // Don't mount - keep placeholder as-is\n});\n\n// ============================================================\n// PART 2: Import custom strategies (if any)\n// ============================================================\n\nimport 'virtual:l5e-island-strategies';\n\n// ============================================================\n// PART 3: Island discovery + mount logic\n// ============================================================\n\n// Per-page island registry injected by server.ts as inline script\n// Format: { \"Counter_a3f2\": \"/assets/Counter-Abc123.js\" }\nconst islandRegistry: Record<string, string> = (window as any).__L5E_ISLANDS__ || {};\n\n// Props for islands rendered in externalized mode live in a single\n// `<script type=\"application/json\" id=\"_l5e_data_\">` at the end of the document\n// (keeps the SSR body lean for crawlers). Parsed once, lazily.\nlet islandDataCache: unknown[] | null = null;\nfunction getIslandData(): unknown[] {\n if (islandDataCache) return islandDataCache;\n const el = document.getElementById('_l5e_data_');\n if (!el || !el.textContent) return (islandDataCache = []);\n try {\n const parsed = JSON.parse(el.textContent);\n islandDataCache = Array.isArray(parsed) ? parsed : [];\n } catch (err) {\n console.error('[l5e-island] Failed to parse _l5e_data_ props store:', err);\n islandDataCache = [];\n }\n return islandDataCache;\n}\n\nfunction readProps(el: Element): Record<string, unknown> {\n const idx = el.getAttribute('data-island-idx');\n if (idx !== null) {\n return (getIslandData()[Number(idx)] as Record<string, unknown>) || {};\n }\n return JSON.parse(el.getAttribute('data-island-props') || '{}');\n}\n\nfunction discoverIslands(): IslandMeta[] {\n return Array.from(document.querySelectorAll('[data-island]')).map((el) => ({\n element: el as HTMLElement,\n registryKey: el.getAttribute('data-island')!,\n exportName: el.getAttribute('data-island-name')!,\n props: readProps(el),\n mount: el.getAttribute('data-island-mount') || 'load',\n mountOpts: el.getAttribute('data-island-opts') || undefined,\n ssr: el.hasAttribute('data-island-ssr'),\n }));\n}\n\nfunction createMountFn(island: IslandMeta): () => Promise<void> {\n let mounted = false;\n return async () => {\n if (mounted) return;\n mounted = true;\n\n // Look up component URL from per-page registry\n const url = islandRegistry[island.registryKey];\n if (!url) {\n console.error(\n `[l5e-island] Component \"${island.registryKey}\" not found in page registry.`,\n `Available: ${Object.keys(islandRegistry).join(', ')}`,\n );\n return;\n }\n\n try {\n const [reactDomClient, { createElement }, mod] = await Promise.all([\n import('react-dom/client'),\n import('react'),\n import(/* @vite-ignore */ url),\n ]);\n\n const Component = mod.default || mod[island.exportName];\n if (!Component) {\n console.error(`[l5e-island] No export \"default\" or \"${island.exportName}\" in module`);\n return;\n }\n\n if (island.ssr) {\n // Server already rendered this component into the element → hydrate it.\n reactDomClient.hydrateRoot(island.element, createElement(Component, island.props));\n } else {\n // Client-only mount (default): fresh render into an empty placeholder.\n const root = reactDomClient.createRoot(island.element);\n root.render(createElement(Component, island.props));\n }\n } catch (error) {\n console.error(`[l5e-island] Failed to mount \"${island.registryKey}\":`, error);\n }\n };\n}\n\nfunction scheduleMount(island: IslandMeta) {\n const strategy = strategies.get(island.mount);\n\n if (!strategy) {\n console.error(\n `[l5e-island] Strategy \"${island.mount}\" not found.`,\n `Available: ${[...strategies.keys()].join(', ')}`,\n `\\nDid you forget to registerMountStrategy(\"${island.mount}\", ...)?`,\n );\n return;\n }\n\n const mountFn = createMountFn(island);\n strategy(mountFn, island.mountOpts, island.element);\n}\n\n// ============================================================\n// PART 4: Boot\n// ============================================================\n\nfunction boot() {\n const islands = discoverIslands();\n islands.forEach(scheduleMount);\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', boot);\n} else {\n boot();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runtime.js","names":[],"sources":["../../src/island/runtime.ts"],"sourcesContent":["import type { IslandMeta } from './types';\nimport { strategies, registerMountStrategy } from './strategy-registry';\n\n// ============================================================\n// PART 1: Built-in strategies\n// ============================================================\n\nregisterMountStrategy('load', (mount) => {\n mount();\n});\n\nregisterMountStrategy('idle', (mount) => {\n if ('requestIdleCallback' in window) {\n requestIdleCallback(() => mount());\n } else {\n setTimeout(() => mount(), 200);\n }\n});\n\nregisterMountStrategy('visible', (mount, opts, el) => {\n const rootMargin = opts || '200px';\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n observer.disconnect();\n mount();\n }\n });\n },\n { rootMargin },\n );\n observer.observe(el);\n return () => observer.disconnect();\n});\n\nregisterMountStrategy('media', (mount, opts) => {\n if (!opts) {\n console.error('[l5e-island] Strategy \"media\" requires mountOpts (media query)');\n return;\n }\n const mql = window.matchMedia(opts);\n if (mql.matches) {\n mount();\n } else {\n const handler = (e: MediaQueryListEvent) => {\n if (e.matches) mount();\n };\n mql.addEventListener('change', handler, { once: true });\n return () => mql.removeEventListener('change', handler);\n }\n});\n\nregisterMountStrategy('none', () => {\n // Don't mount - keep placeholder as-is\n});\n\n// ============================================================\n// PART 2: Import custom strategies (if any)\n// ============================================================\n\nimport 'virtual:l5e-island-strategies';\n\n// ============================================================\n// PART 3: Island discovery + mount logic\n// ============================================================\n\n// Per-page island registry injected by server.ts as inline script\n// Format: { \"Counter_a3f2\": \"/assets/Counter-Abc123.js\" }\ntype CompactIslandModule = {\n reactDomClient: typeof import('react-dom/client');\n createElement: typeof import('react').createElement;\n module: Record<string, any>;\n};\ntype IslandLoader = string | (() => Promise<CompactIslandModule>);\nconst islandRegistry: Record<string, IslandLoader> = ((window as any).__L5E_ISLANDS__ ||= {});\n\n// Props for islands rendered in externalized mode live in a single\n// `<script type=\"application/json\" id=\"_l5e_data_\">` at the end of the document\n// (keeps the SSR body lean for crawlers). Parsed once, lazily.\nlet islandDataCache: unknown[] | null = null;\nfunction getIslandData(): unknown[] {\n if (islandDataCache) return islandDataCache;\n const el = document.getElementById('_l5e_data_');\n if (!el || !el.textContent) return (islandDataCache = []);\n try {\n const parsed = JSON.parse(el.textContent);\n islandDataCache = Array.isArray(parsed) ? parsed : [];\n } catch (err) {\n console.error('[l5e-island] Failed to parse _l5e_data_ props store:', err);\n islandDataCache = [];\n }\n return islandDataCache;\n}\n\nfunction readProps(el: Element): Record<string, unknown> {\n const idx = el.getAttribute('data-island-idx');\n if (idx !== null) {\n return (getIslandData()[Number(idx)] as Record<string, unknown>) || {};\n }\n return JSON.parse(el.getAttribute('data-island-props') || '{}');\n}\n\nfunction discoverIslands(): IslandMeta[] {\n return Array.from(document.querySelectorAll('[data-island]')).map((el) => ({\n element: el as HTMLElement,\n registryKey: el.getAttribute('data-island')!,\n exportName: el.getAttribute('data-island-name')!,\n props: readProps(el),\n mount: el.getAttribute('data-island-mount') || 'load',\n mountOpts: el.getAttribute('data-island-opts') || undefined,\n ssr: el.hasAttribute('data-island-ssr'),\n }));\n}\n\nfunction createMountFn(island: IslandMeta): () => Promise<void> {\n let mounted = false;\n return async () => {\n if (mounted) return;\n mounted = true;\n\n // Look up component URL from per-page registry\n const loader = islandRegistry[island.registryKey];\n if (!loader) {\n console.error(\n `[l5e-island] Component \"${island.registryKey}\" not found in page registry.`,\n `Available: ${Object.keys(islandRegistry).join(', ')}`,\n );\n return;\n }\n\n try {\n const loaded =\n typeof loader === 'function'\n ? await loader()\n : await Promise.all([\n import('react-dom/client'),\n import('react'),\n import(/* @vite-ignore */ loader),\n ]).then(([reactDomClient, react, module]) => ({\n reactDomClient,\n createElement: react.createElement,\n module,\n }));\n const { reactDomClient, createElement, module: mod } = loaded;\n\n const Component = mod.default || mod[island.exportName];\n if (!Component) {\n console.error(`[l5e-island] No export \"default\" or \"${island.exportName}\" in module`);\n return;\n }\n\n if (island.ssr) {\n // Server already rendered this component into the element → hydrate it.\n reactDomClient.hydrateRoot(island.element, createElement(Component, island.props));\n } else {\n // Client-only mount (default): fresh render into an empty placeholder.\n const root = reactDomClient.createRoot(island.element);\n root.render(createElement(Component, island.props));\n }\n } catch (error) {\n console.error(`[l5e-island] Failed to mount \"${island.registryKey}\":`, error);\n }\n };\n}\n\nfunction scheduleMount(island: IslandMeta) {\n const strategy = strategies.get(island.mount);\n\n if (!strategy) {\n console.error(\n `[l5e-island] Strategy \"${island.mount}\" not found.`,\n `Available: ${[...strategies.keys()].join(', ')}`,\n `\\nDid you forget to registerMountStrategy(\"${island.mount}\", ...)?`,\n );\n return;\n }\n\n const mountFn = createMountFn(island);\n strategy(mountFn, island.mountOpts, island.element);\n}\n\n// ============================================================\n// PART 4: Boot\n// ============================================================\n\nfunction boot() {\n const islands = discoverIslands();\n islands.forEach(scheduleMount);\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', boot);\n} else {\n boot();\n}\n"],"mappings":"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"}