@pyreon/server 0.5.0 → 0.5.1

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/lib/client.js ADDED
@@ -0,0 +1,149 @@
1
+ import { h } from "@pyreon/core";
2
+ import { RouterProvider, createRouter, hydrateLoaderData } from "@pyreon/router";
3
+ import { hydrateRoot, mount } from "@pyreon/runtime-dom";
4
+
5
+ //#region src/client.ts
6
+ /**
7
+ * Hydrate a server-rendered Pyreon app on the client.
8
+ *
9
+ * Handles:
10
+ * - Router creation (history mode)
11
+ * - Loader data hydration from `window.__PYREON_LOADER_DATA__`
12
+ * - Hydration if container has SSR content, fresh mount otherwise
13
+ *
14
+ * Returns a cleanup function that unmounts the app.
15
+ */
16
+ function startClient(options) {
17
+ const { App, routes, container = "#app" } = options;
18
+ const el = typeof container === "string" ? document.querySelector(container) : container;
19
+ if (!el) throw new Error(`[pyreon/client] Container "${container}" not found`);
20
+ const router = createRouter({
21
+ routes,
22
+ mode: "history"
23
+ });
24
+ const loaderData = window.__PYREON_LOADER_DATA__;
25
+ if (loaderData && typeof loaderData === "object") hydrateLoaderData(router, loaderData);
26
+ const app = h(RouterProvider, { router }, h(App, null));
27
+ if (el.childNodes.length > 0) return hydrateRoot(el, app);
28
+ return mount(app, el);
29
+ }
30
+ /**
31
+ * Hydrate all `<pyreon-island>` elements on the page.
32
+ *
33
+ * Only loads JavaScript for components that are actually present in the HTML.
34
+ * Respects hydration strategies (load, idle, visible, media, never).
35
+ *
36
+ * @example
37
+ * hydrateIslands({
38
+ * Counter: () => import("./Counter"),
39
+ * Search: () => import("./Search"),
40
+ * })
41
+ */
42
+ /**
43
+ * Hydrate all `<pyreon-island>` elements on the page.
44
+ * Returns a cleanup function that disconnects any pending observers/listeners.
45
+ */
46
+ function hydrateIslands(registry) {
47
+ const islands = document.querySelectorAll("pyreon-island");
48
+ const cleanups = [];
49
+ for (const el of islands) {
50
+ const componentId = el.getAttribute("data-component");
51
+ if (!componentId) continue;
52
+ const loader = registry[componentId];
53
+ if (!loader) {
54
+ console.warn(`No loader registered for island "${componentId}"`);
55
+ continue;
56
+ }
57
+ const strategy = el.getAttribute("data-hydrate") ?? "load";
58
+ const cleanup = scheduleHydration(el, loader, el.getAttribute("data-props") ?? "{}", strategy);
59
+ if (cleanup) cleanups.push(cleanup);
60
+ }
61
+ return () => {
62
+ for (const fn of cleanups) fn();
63
+ };
64
+ }
65
+ function scheduleHydration(el, loader, propsJson, strategy) {
66
+ let cancelled = false;
67
+ const hydrate = () => {
68
+ if (!cancelled) hydrateIsland(el, loader, propsJson);
69
+ };
70
+ switch (strategy) {
71
+ case "load":
72
+ hydrate();
73
+ return null;
74
+ case "idle": {
75
+ if ("requestIdleCallback" in window) {
76
+ const id = requestIdleCallback(hydrate);
77
+ return () => {
78
+ cancelled = true;
79
+ cancelIdleCallback(id);
80
+ };
81
+ }
82
+ const id = setTimeout(hydrate, 200);
83
+ return () => {
84
+ cancelled = true;
85
+ clearTimeout(id);
86
+ };
87
+ }
88
+ case "visible": return observeVisibility(el, hydrate);
89
+ case "never": return null;
90
+ default:
91
+ if (strategy.startsWith("media(")) {
92
+ const query = strategy.slice(6, -1);
93
+ const mql = window.matchMedia(query);
94
+ if (mql.matches) {
95
+ hydrate();
96
+ return null;
97
+ }
98
+ const onChange = (e) => {
99
+ if (e.matches) {
100
+ mql.removeEventListener("change", onChange);
101
+ hydrate();
102
+ }
103
+ };
104
+ mql.addEventListener("change", onChange);
105
+ return () => {
106
+ cancelled = true;
107
+ mql.removeEventListener("change", onChange);
108
+ };
109
+ }
110
+ hydrate();
111
+ return null;
112
+ }
113
+ }
114
+ async function hydrateIsland(el, loader, propsJson) {
115
+ const name = el.getAttribute("data-component") ?? "unknown";
116
+ try {
117
+ let props;
118
+ try {
119
+ props = JSON.parse(propsJson);
120
+ if (typeof props !== "object" || props === null || Array.isArray(props)) throw new TypeError("Expected object");
121
+ } catch (parseErr) {
122
+ console.error(`Invalid island props JSON for "${name}"`, parseErr);
123
+ return;
124
+ }
125
+ const mod = await loader();
126
+ hydrateRoot(el, h(typeof mod === "function" ? mod : mod.default, props));
127
+ } catch (err) {
128
+ console.error(`Failed to hydrate island "${name}"`, err);
129
+ }
130
+ }
131
+ function observeVisibility(el, callback) {
132
+ if (!("IntersectionObserver" in window)) {
133
+ callback();
134
+ return null;
135
+ }
136
+ const observer = new IntersectionObserver((entries) => {
137
+ for (const entry of entries) if (entry.isIntersecting) {
138
+ observer.disconnect();
139
+ callback();
140
+ return;
141
+ }
142
+ }, { rootMargin: "200px" });
143
+ observer.observe(el);
144
+ return () => observer.disconnect();
145
+ }
146
+
147
+ //#endregion
148
+ export { hydrateIslands, startClient };
149
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * Client-side entry helpers for Pyreon SSR/SSG apps.\n *\n * ## Full app hydration\n *\n * ```ts\n * // entry-client.ts\n * import { startClient } from \"@pyreon/server/client\"\n * import { App } from \"./App\"\n * import { routes } from \"./routes\"\n *\n * startClient({ App, routes })\n * ```\n *\n * ## Island hydration (partial)\n *\n * ```ts\n * // entry-client.ts\n * import { hydrateIslands } from \"@pyreon/server/client\"\n *\n * hydrateIslands({\n * Counter: () => import(\"./Counter\"),\n * Search: () => import(\"./Search\"),\n * })\n * ```\n */\n\nimport type { ComponentFn } from \"@pyreon/core\"\nimport { h } from \"@pyreon/core\"\nimport { createRouter, hydrateLoaderData, type RouteRecord, RouterProvider } from \"@pyreon/router\"\nimport { hydrateRoot, mount } from \"@pyreon/runtime-dom\"\nimport type { HydrationStrategy } from \"./island\"\n\n// ─── Full app hydration ──────────────────────────────────────────────────────\n\nexport interface StartClientOptions {\n /** Root application component */\n App: ComponentFn\n /** Route definitions (same as server) */\n routes: RouteRecord[]\n /** CSS selector or element for the app container (default: \"#app\") */\n container?: string | Element\n}\n\n/**\n * Hydrate a server-rendered Pyreon app on the client.\n *\n * Handles:\n * - Router creation (history mode)\n * - Loader data hydration from `window.__PYREON_LOADER_DATA__`\n * - Hydration if container has SSR content, fresh mount otherwise\n *\n * Returns a cleanup function that unmounts the app.\n */\nexport function startClient(options: StartClientOptions): () => void {\n const { App, routes, container = \"#app\" } = options\n\n const el = typeof container === \"string\" ? document.querySelector(container) : container\n\n if (!el) {\n throw new Error(`[pyreon/client] Container \"${container}\" not found`)\n }\n\n // Create client-side router (history mode to match SSR)\n const router = createRouter({ routes, mode: \"history\" })\n\n // Hydrate loader data from SSR (avoids re-fetching on initial render)\n const loaderData = (window as unknown as Record<string, unknown>).__PYREON_LOADER_DATA__\n if (loaderData && typeof loaderData === \"object\") {\n hydrateLoaderData(router as never, loaderData as Record<string, unknown>)\n }\n\n // Build app tree\n const app = h(RouterProvider, { router }, h(App, null))\n\n // Hydrate if container has SSR content, mount fresh otherwise\n if (el.childNodes.length > 0) {\n return hydrateRoot(el, app)\n }\n return mount(app, el as HTMLElement)\n}\n\n// ─── Island hydration ────────────────────────────────────────────────────────\n\ntype IslandLoader = () => Promise<{ default: ComponentFn } | ComponentFn>\n\n/**\n * Hydrate all `<pyreon-island>` elements on the page.\n *\n * Only loads JavaScript for components that are actually present in the HTML.\n * Respects hydration strategies (load, idle, visible, media, never).\n *\n * @example\n * hydrateIslands({\n * Counter: () => import(\"./Counter\"),\n * Search: () => import(\"./Search\"),\n * })\n */\n/**\n * Hydrate all `<pyreon-island>` elements on the page.\n * Returns a cleanup function that disconnects any pending observers/listeners.\n */\nexport function hydrateIslands(registry: Record<string, IslandLoader>): () => void {\n const islands = document.querySelectorAll(\"pyreon-island\")\n const cleanups: (() => void)[] = []\n\n for (const el of islands) {\n const componentId = el.getAttribute(\"data-component\")\n if (!componentId) continue\n\n const loader = registry[componentId]\n if (!loader) {\n console.warn(`No loader registered for island \"${componentId}\"`)\n continue\n }\n\n const strategy = (el.getAttribute(\"data-hydrate\") ?? \"load\") as HydrationStrategy\n const propsJson = el.getAttribute(\"data-props\") ?? \"{}\"\n\n const cleanup = scheduleHydration(el as HTMLElement, loader, propsJson, strategy)\n if (cleanup) cleanups.push(cleanup)\n }\n\n return () => {\n for (const fn of cleanups) fn()\n }\n}\n\nfunction scheduleHydration(\n el: HTMLElement,\n loader: IslandLoader,\n propsJson: string,\n strategy: HydrationStrategy,\n): (() => void) | null {\n let cancelled = false\n const hydrate = () => {\n if (!cancelled) hydrateIsland(el, loader, propsJson)\n }\n\n switch (strategy) {\n case \"load\":\n hydrate()\n return null\n\n case \"idle\": {\n if (\"requestIdleCallback\" in window) {\n const id = requestIdleCallback(hydrate)\n return () => {\n cancelled = true\n cancelIdleCallback(id)\n }\n }\n const id = setTimeout(hydrate, 200)\n return () => {\n cancelled = true\n clearTimeout(id)\n }\n }\n\n case \"visible\":\n return observeVisibility(el, hydrate)\n\n case \"never\":\n return null\n\n default:\n // media(query)\n if (strategy.startsWith(\"media(\")) {\n const query = strategy.slice(6, -1)\n const mql = window.matchMedia(query)\n if (mql.matches) {\n hydrate()\n return null\n }\n const onChange = (e: MediaQueryListEvent) => {\n if (e.matches) {\n mql.removeEventListener(\"change\", onChange)\n hydrate()\n }\n }\n mql.addEventListener(\"change\", onChange)\n return () => {\n cancelled = true\n mql.removeEventListener(\"change\", onChange)\n }\n }\n hydrate()\n return null\n }\n}\n\nasync function hydrateIsland(\n el: HTMLElement,\n loader: IslandLoader,\n propsJson: string,\n): Promise<void> {\n const name = el.getAttribute(\"data-component\") ?? \"unknown\"\n try {\n let props: Record<string, unknown>\n try {\n props = JSON.parse(propsJson)\n if (typeof props !== \"object\" || props === null || Array.isArray(props)) {\n throw new TypeError(\"Expected object\")\n }\n } catch (parseErr) {\n console.error(`Invalid island props JSON for \"${name}\"`, parseErr)\n return\n }\n\n const mod = await loader()\n const Comp = typeof mod === \"function\" ? mod : mod.default\n hydrateRoot(el, h(Comp, props))\n } catch (err) {\n console.error(`Failed to hydrate island \"${name}\"`, err)\n }\n}\n\nfunction observeVisibility(el: HTMLElement, callback: () => void): (() => void) | null {\n if (!(\"IntersectionObserver\" in window)) {\n callback()\n return null\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n observer.disconnect()\n callback()\n return\n }\n }\n },\n { rootMargin: \"200px\" },\n )\n\n observer.observe(el)\n return () => observer.disconnect()\n}\n"],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ import { h } from "@pyreon/core";
2
+ import { RouterProvider, createRouter, hydrateLoaderData } from "@pyreon/router";
3
+ import { hydrateRoot, mount } from "@pyreon/runtime-dom";
4
+
5
+ //#region src/client.ts
6
+ /**
7
+ * Hydrate a server-rendered Pyreon app on the client.
8
+ *
9
+ * Handles:
10
+ * - Router creation (history mode)
11
+ * - Loader data hydration from `window.__PYREON_LOADER_DATA__`
12
+ * - Hydration if container has SSR content, fresh mount otherwise
13
+ *
14
+ * Returns a cleanup function that unmounts the app.
15
+ */
16
+ function startClient(options) {
17
+ const {
18
+ App,
19
+ routes,
20
+ container = "#app"
21
+ } = options;
22
+ const el = typeof container === "string" ? document.querySelector(container) : container;
23
+ if (!el) throw new Error(`[pyreon/client] Container "${container}" not found`);
24
+ const router = createRouter({
25
+ routes,
26
+ mode: "history"
27
+ });
28
+ const loaderData = window.__PYREON_LOADER_DATA__;
29
+ if (loaderData && typeof loaderData === "object") hydrateLoaderData(router, loaderData);
30
+ const app = h(RouterProvider, {
31
+ router
32
+ }, h(App, null));
33
+ if (el.childNodes.length > 0) return hydrateRoot(el, app);
34
+ return mount(app, el);
35
+ }
36
+ /**
37
+ * Hydrate all `<pyreon-island>` elements on the page.
38
+ *
39
+ * Only loads JavaScript for components that are actually present in the HTML.
40
+ * Respects hydration strategies (load, idle, visible, media, never).
41
+ *
42
+ * @example
43
+ * hydrateIslands({
44
+ * Counter: () => import("./Counter"),
45
+ * Search: () => import("./Search"),
46
+ * })
47
+ */
48
+ /**
49
+ * Hydrate all `<pyreon-island>` elements on the page.
50
+ * Returns a cleanup function that disconnects any pending observers/listeners.
51
+ */
52
+ function hydrateIslands(registry) {
53
+ const islands = document.querySelectorAll("pyreon-island");
54
+ const cleanups = [];
55
+ for (const el of islands) {
56
+ const componentId = el.getAttribute("data-component");
57
+ if (!componentId) continue;
58
+ const loader = registry[componentId];
59
+ if (!loader) {
60
+ console.warn(`No loader registered for island "${componentId}"`);
61
+ continue;
62
+ }
63
+ const strategy = el.getAttribute("data-hydrate") ?? "load";
64
+ const cleanup = scheduleHydration(el, loader, el.getAttribute("data-props") ?? "{}", strategy);
65
+ if (cleanup) cleanups.push(cleanup);
66
+ }
67
+ return () => {
68
+ for (const fn of cleanups) fn();
69
+ };
70
+ }
71
+ function scheduleHydration(el, loader, propsJson, strategy) {
72
+ let cancelled = false;
73
+ const hydrate = () => {
74
+ if (!cancelled) hydrateIsland(el, loader, propsJson);
75
+ };
76
+ switch (strategy) {
77
+ case "load":
78
+ hydrate();
79
+ return null;
80
+ case "idle":
81
+ {
82
+ if ("requestIdleCallback" in window) {
83
+ const id = requestIdleCallback(hydrate);
84
+ return () => {
85
+ cancelled = true;
86
+ cancelIdleCallback(id);
87
+ };
88
+ }
89
+ const id = setTimeout(hydrate, 200);
90
+ return () => {
91
+ cancelled = true;
92
+ clearTimeout(id);
93
+ };
94
+ }
95
+ case "visible":
96
+ return observeVisibility(el, hydrate);
97
+ case "never":
98
+ return null;
99
+ default:
100
+ if (strategy.startsWith("media(")) {
101
+ const query = strategy.slice(6, -1);
102
+ const mql = window.matchMedia(query);
103
+ if (mql.matches) {
104
+ hydrate();
105
+ return null;
106
+ }
107
+ const onChange = e => {
108
+ if (e.matches) {
109
+ mql.removeEventListener("change", onChange);
110
+ hydrate();
111
+ }
112
+ };
113
+ mql.addEventListener("change", onChange);
114
+ return () => {
115
+ cancelled = true;
116
+ mql.removeEventListener("change", onChange);
117
+ };
118
+ }
119
+ hydrate();
120
+ return null;
121
+ }
122
+ }
123
+ async function hydrateIsland(el, loader, propsJson) {
124
+ const name = el.getAttribute("data-component") ?? "unknown";
125
+ try {
126
+ let props;
127
+ try {
128
+ props = JSON.parse(propsJson);
129
+ if (typeof props !== "object" || props === null || Array.isArray(props)) throw new TypeError("Expected object");
130
+ } catch (parseErr) {
131
+ console.error(`Invalid island props JSON for "${name}"`, parseErr);
132
+ return;
133
+ }
134
+ const mod = await loader();
135
+ hydrateRoot(el, h(typeof mod === "function" ? mod : mod.default, props));
136
+ } catch (err) {
137
+ console.error(`Failed to hydrate island "${name}"`, err);
138
+ }
139
+ }
140
+ function observeVisibility(el, callback) {
141
+ if (!("IntersectionObserver" in window)) {
142
+ callback();
143
+ return null;
144
+ }
145
+ const observer = new IntersectionObserver(entries => {
146
+ for (const entry of entries) if (entry.isIntersecting) {
147
+ observer.disconnect();
148
+ callback();
149
+ return;
150
+ }
151
+ }, {
152
+ rootMargin: "200px"
153
+ });
154
+ observer.observe(el);
155
+ return () => observer.disconnect();
156
+ }
157
+
158
+ //#endregion
159
+ export { hydrateIslands, startClient };
160
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/client.ts"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { ComponentFn } from "@pyreon/core";
2
+ import { RouteRecord } from "@pyreon/router";
3
+
4
+ //#region src/client.d.ts
5
+ interface StartClientOptions {
6
+ /** Root application component */
7
+ App: ComponentFn;
8
+ /** Route definitions (same as server) */
9
+ routes: RouteRecord[];
10
+ /** CSS selector or element for the app container (default: "#app") */
11
+ container?: string | Element;
12
+ }
13
+ /**
14
+ * Hydrate a server-rendered Pyreon app on the client.
15
+ *
16
+ * Handles:
17
+ * - Router creation (history mode)
18
+ * - Loader data hydration from `window.__PYREON_LOADER_DATA__`
19
+ * - Hydration if container has SSR content, fresh mount otherwise
20
+ *
21
+ * Returns a cleanup function that unmounts the app.
22
+ */
23
+ declare function startClient(options: StartClientOptions): () => void;
24
+ type IslandLoader = () => Promise<{
25
+ default: ComponentFn;
26
+ } | ComponentFn>;
27
+ /**
28
+ * Hydrate all `<pyreon-island>` elements on the page.
29
+ *
30
+ * Only loads JavaScript for components that are actually present in the HTML.
31
+ * Respects hydration strategies (load, idle, visible, media, never).
32
+ *
33
+ * @example
34
+ * hydrateIslands({
35
+ * Counter: () => import("./Counter"),
36
+ * Search: () => import("./Search"),
37
+ * })
38
+ */
39
+ /**
40
+ * Hydrate all `<pyreon-island>` elements on the page.
41
+ * Returns a cleanup function that disconnects any pending observers/listeners.
42
+ */
43
+ declare function hydrateIslands(registry: Record<string, IslandLoader>): () => void;
44
+ //#endregion
45
+ export { StartClientOptions, hydrateIslands, startClient };
46
+ //# sourceMappingURL=client2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client2.d.ts","names":[],"sources":["../../src/client.ts"],"mappings":";;;;UAmCiB,kBAAA;EAiD4C;EA/C3D,GAAA,EAAK,WAAA;EA+C0B;EA7C/B,MAAA,EAAQ,WAAA;EA6CgB;EA3CxB,SAAA,YAAqB,OAAA;AAAA;;;;AA6DvB;;;;;;;iBAhDgB,WAAA,CAAY,OAAA,EAAS,kBAAA;AAAA,KA8BhC,YAAA,SAAqB,OAAA;EAAU,OAAA,EAAS,WAAA;AAAA,IAAgB,WAAA;;;;;;;;;;;;;;;;;iBAkB7C,cAAA,CAAe,QAAA,EAAU,MAAA,SAAe,YAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/server",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "SSR handler, SSG prerender, and island architecture for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,12 +44,12 @@
44
44
  "prepublishOnly": "bun run build"
45
45
  },
46
46
  "dependencies": {
47
- "@pyreon/core": "^0.5.0",
48
- "@pyreon/head": "^0.5.0",
49
- "@pyreon/reactivity": "^0.5.0",
50
- "@pyreon/router": "^0.5.0",
51
- "@pyreon/runtime-dom": "^0.5.0",
52
- "@pyreon/runtime-server": "^0.5.0"
47
+ "@pyreon/core": "^0.5.1",
48
+ "@pyreon/head": "^0.5.1",
49
+ "@pyreon/reactivity": "^0.5.1",
50
+ "@pyreon/router": "^0.5.1",
51
+ "@pyreon/runtime-dom": "^0.5.1",
52
+ "@pyreon/runtime-server": "^0.5.1"
53
53
  },
54
54
  "publishConfig": {
55
55
  "access": "public"