zero-query 1.1.1 → 1.2.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.
Files changed (156) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -0
  3. package/cli/args.js +33 -33
  4. package/cli/commands/build-api.js +442 -442
  5. package/cli/commands/build.js +253 -247
  6. package/cli/commands/bundle.js +1226 -1224
  7. package/cli/commands/create.js +178 -121
  8. package/cli/commands/dev/devtools/index.js +56 -56
  9. package/cli/commands/dev/devtools/js/components.js +49 -49
  10. package/cli/commands/dev/devtools/js/core.js +423 -423
  11. package/cli/commands/dev/devtools/js/elements.js +421 -421
  12. package/cli/commands/dev/devtools/js/network.js +166 -166
  13. package/cli/commands/dev/devtools/js/performance.js +73 -73
  14. package/cli/commands/dev/devtools/js/router.js +105 -105
  15. package/cli/commands/dev/devtools/js/source.js +132 -132
  16. package/cli/commands/dev/devtools/js/stats.js +35 -35
  17. package/cli/commands/dev/devtools/js/tabs.js +79 -79
  18. package/cli/commands/dev/devtools/panel.html +95 -95
  19. package/cli/commands/dev/devtools/styles.css +244 -244
  20. package/cli/commands/dev/index.js +107 -107
  21. package/cli/commands/dev/logger.js +75 -75
  22. package/cli/commands/dev/overlay.js +858 -858
  23. package/cli/commands/dev/server.js +220 -220
  24. package/cli/commands/dev/validator.js +94 -94
  25. package/cli/commands/dev/watcher.js +172 -172
  26. package/cli/help.js +114 -112
  27. package/cli/index.js +52 -52
  28. package/cli/scaffold/default/LICENSE +21 -21
  29. package/cli/scaffold/default/app/app.js +207 -207
  30. package/cli/scaffold/default/app/components/about.js +201 -201
  31. package/cli/scaffold/default/app/components/api-demo.js +143 -143
  32. package/cli/scaffold/default/app/components/contact-card.js +231 -231
  33. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
  34. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
  35. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
  36. package/cli/scaffold/default/app/components/counter.js +127 -127
  37. package/cli/scaffold/default/app/components/home.js +249 -249
  38. package/cli/scaffold/default/app/components/not-found.js +16 -16
  39. package/cli/scaffold/default/app/components/playground/playground.css +115 -115
  40. package/cli/scaffold/default/app/components/playground/playground.html +161 -161
  41. package/cli/scaffold/default/app/components/playground/playground.js +116 -116
  42. package/cli/scaffold/default/app/components/todos.js +225 -225
  43. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
  44. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
  45. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
  46. package/cli/scaffold/default/app/routes.js +15 -15
  47. package/cli/scaffold/default/app/store.js +101 -101
  48. package/cli/scaffold/default/global.css +552 -552
  49. package/cli/scaffold/default/index.html +99 -99
  50. package/cli/scaffold/minimal/app/app.js +85 -85
  51. package/cli/scaffold/minimal/app/components/about.js +68 -68
  52. package/cli/scaffold/minimal/app/components/counter.js +122 -122
  53. package/cli/scaffold/minimal/app/components/home.js +68 -68
  54. package/cli/scaffold/minimal/app/components/not-found.js +16 -16
  55. package/cli/scaffold/minimal/app/routes.js +9 -9
  56. package/cli/scaffold/minimal/app/store.js +36 -36
  57. package/cli/scaffold/minimal/global.css +300 -300
  58. package/cli/scaffold/minimal/index.html +44 -44
  59. package/cli/scaffold/ssr/app/app.js +41 -41
  60. package/cli/scaffold/ssr/app/components/about.js +55 -55
  61. package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
  62. package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
  63. package/cli/scaffold/ssr/app/components/home.js +37 -37
  64. package/cli/scaffold/ssr/app/components/not-found.js +15 -15
  65. package/cli/scaffold/ssr/app/routes.js +8 -8
  66. package/cli/scaffold/ssr/global.css +228 -228
  67. package/cli/scaffold/ssr/index.html +37 -37
  68. package/cli/scaffold/ssr/package.json +8 -8
  69. package/cli/scaffold/ssr/server/data/posts.js +144 -144
  70. package/cli/scaffold/ssr/server/index.js +213 -213
  71. package/cli/scaffold/webrtc/app/app.js +11 -0
  72. package/cli/scaffold/webrtc/app/components/video-room.js +441 -0
  73. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  74. package/cli/scaffold/webrtc/global.css +250 -0
  75. package/cli/scaffold/webrtc/index.html +21 -0
  76. package/cli/scaffold/webrtc/package.json +16 -0
  77. package/cli/scaffold/webrtc/server/index.js +198 -0
  78. package/cli/utils.js +305 -287
  79. package/dist/API.md +661 -0
  80. package/dist/zquery.dist.zip +0 -0
  81. package/dist/zquery.js +10454 -6614
  82. package/dist/zquery.min.js +8 -631
  83. package/index.d.ts +570 -371
  84. package/index.js +311 -240
  85. package/package.json +77 -70
  86. package/src/component.js +1758 -1691
  87. package/src/core.js +929 -921
  88. package/src/diff.js +497 -497
  89. package/src/errors.js +209 -209
  90. package/src/expression.js +929 -922
  91. package/src/http.js +242 -242
  92. package/src/package.json +1 -1
  93. package/src/reactive.js +271 -255
  94. package/src/router.js +878 -843
  95. package/src/ssr.js +421 -418
  96. package/src/store.js +325 -318
  97. package/src/utils.js +534 -515
  98. package/src/webrtc/e2ee.js +351 -0
  99. package/src/webrtc/errors.js +116 -0
  100. package/src/webrtc/ice.js +301 -0
  101. package/src/webrtc/index.js +131 -0
  102. package/src/webrtc/joinToken.js +119 -0
  103. package/src/webrtc/observe.js +172 -0
  104. package/src/webrtc/peer.js +351 -0
  105. package/src/webrtc/reactive.js +268 -0
  106. package/src/webrtc/room.js +625 -0
  107. package/src/webrtc/sdp.js +302 -0
  108. package/src/webrtc/sfu/index.js +43 -0
  109. package/src/webrtc/sfu/livekit.js +131 -0
  110. package/src/webrtc/sfu/mediasoup.js +150 -0
  111. package/src/webrtc/signaling.js +373 -0
  112. package/src/webrtc/turn.js +237 -0
  113. package/tests/_helpers/webrtcFakes.js +289 -0
  114. package/tests/audit.test.js +4158 -4158
  115. package/tests/bench/render.bench.js +77 -0
  116. package/tests/cli.test.js +1136 -1103
  117. package/tests/compare.test.js +497 -486
  118. package/tests/component.test.js +4076 -3938
  119. package/tests/core.test.js +1926 -1910
  120. package/tests/dev-server.test.js +489 -489
  121. package/tests/diff.test.js +1416 -1416
  122. package/tests/docs.test.js +1664 -1650
  123. package/tests/electron-features.test.js +864 -864
  124. package/tests/errors.test.js +619 -619
  125. package/tests/expression.test.js +1087 -1056
  126. package/tests/http.test.js +693 -648
  127. package/tests/reactive.test.js +844 -819
  128. package/tests/router.test.js +2423 -2327
  129. package/tests/ssr.test.js +916 -870
  130. package/tests/store.test.js +840 -830
  131. package/tests/test-minifier.js +153 -153
  132. package/tests/test-ssr.js +27 -27
  133. package/tests/utils.test.js +1411 -1377
  134. package/tests/webrtc/e2ee.test.js +283 -0
  135. package/tests/webrtc/ice.test.js +202 -0
  136. package/tests/webrtc/joinToken.test.js +89 -0
  137. package/tests/webrtc/observe.test.js +111 -0
  138. package/tests/webrtc/peer.test.js +373 -0
  139. package/tests/webrtc/reactive.test.js +235 -0
  140. package/tests/webrtc/room.test.js +406 -0
  141. package/tests/webrtc/sdp.test.js +151 -0
  142. package/tests/webrtc/sfu-livekit.test.js +119 -0
  143. package/tests/webrtc/sfu.test.js +160 -0
  144. package/tests/webrtc/signaling.test.js +251 -0
  145. package/tests/webrtc/turn.test.js +256 -0
  146. package/types/collection.d.ts +383 -383
  147. package/types/component.d.ts +186 -186
  148. package/types/errors.d.ts +135 -135
  149. package/types/http.d.ts +92 -92
  150. package/types/misc.d.ts +201 -201
  151. package/types/reactive.d.ts +98 -98
  152. package/types/router.d.ts +190 -190
  153. package/types/ssr.d.ts +102 -102
  154. package/types/store.d.ts +146 -146
  155. package/types/utils.d.ts +245 -245
  156. package/types/webrtc.d.ts +653 -0
@@ -1,41 +1,41 @@
1
- // app.js - Client entry point
2
- //
3
- // Imports shared component definitions and registers them with zQuery.
4
- // The SSR server imports the same definitions via createSSRApp().
5
- // Server-fetched data is embedded in window.__SSR_DATA__ for hydration.
6
-
7
- import { homePage } from './components/home.js';
8
- import { aboutPage } from './components/about.js';
9
- import { blogList } from './components/blog/index.js';
10
- import { blogPost } from './components/blog/post.js';
11
- import { notFound } from './components/not-found.js';
12
- import { routes } from './routes.js';
13
-
14
- // Register shared component definitions on the client
15
- $.component('home-page', homePage);
16
- $.component('about-page', aboutPage);
17
- $.component('blog-list', blogList);
18
- $.component('blog-post', blogPost);
19
- $.component('not-found', notFound);
20
-
21
- // Route → page title mapping (mirrors server getMetaForRoute)
22
- const routeTitles = {
23
- 'home-page': '{{NAME}} — Home',
24
- 'blog-list': 'Blog — {{NAME}}',
25
- 'blog-post': null, // dynamic — set by component
26
- 'about-page': 'About — {{NAME}}',
27
- 'not-found': 'Page Not Found — {{NAME}}',
28
- };
29
-
30
- // Client-side router
31
- const router = $.router({
32
- routes,
33
- fallback: 'not-found',
34
- mode: 'history'
35
- });
36
-
37
- // Update document.title on client-side navigations
38
- router.onChange(({ route }) => {
39
- const title = routeTitles[route.component];
40
- if (title) document.title = title;
41
- });
1
+ // app.js - Client entry point
2
+ //
3
+ // Imports shared component definitions and registers them with zQuery.
4
+ // The SSR server imports the same definitions via createSSRApp().
5
+ // Server-fetched data is embedded in window.__SSR_DATA__ for hydration.
6
+
7
+ import { homePage } from './components/home.js';
8
+ import { aboutPage } from './components/about.js';
9
+ import { blogList } from './components/blog/index.js';
10
+ import { blogPost } from './components/blog/post.js';
11
+ import { notFound } from './components/not-found.js';
12
+ import { routes } from './routes.js';
13
+
14
+ // Register shared component definitions on the client
15
+ $.component('home-page', homePage);
16
+ $.component('about-page', aboutPage);
17
+ $.component('blog-list', blogList);
18
+ $.component('blog-post', blogPost);
19
+ $.component('not-found', notFound);
20
+
21
+ // Route → page title mapping (mirrors server getMetaForRoute)
22
+ const routeTitles = {
23
+ 'home-page': '{{NAME}} — Home',
24
+ 'blog-list': 'Blog — {{NAME}}',
25
+ 'blog-post': null, // dynamic — set by component
26
+ 'about-page': 'About — {{NAME}}',
27
+ 'not-found': 'Page Not Found — {{NAME}}',
28
+ };
29
+
30
+ // Client-side router
31
+ const router = $.router({
32
+ routes,
33
+ fallback: 'not-found',
34
+ mode: 'history'
35
+ });
36
+
37
+ // Update document.title on client-side navigations
38
+ router.onChange(({ route }) => {
39
+ const title = routeTitles[route.component];
40
+ if (title) document.title = title;
41
+ });
@@ -1,55 +1,55 @@
1
- // about.js - About page component
2
-
3
- export const aboutPage = {
4
- render() {
5
- return `
6
- <div class="page-header">
7
- <h1>About</h1>
8
- <p class="subtitle">This app is powered by zQuery — a zero-dependency frontend micro-library.</p>
9
- </div>
10
-
11
- <div class="card">
12
- <h3>What is zQuery?</h3>
13
- <p style="line-height:1.7;">
14
- zQuery is a lightweight, zero-dependency JavaScript library for building
15
- reactive web applications. It provides components, routing, state management,
16
- server-side rendering, and more — all in a single, tiny package with no
17
- build step required.
18
- </p>
19
- </div>
20
-
21
- <div class="card">
22
- <h3>Core Features</h3>
23
- <ul style="padding-left:1.2rem; line-height:2;">
24
- <li><strong>Reactive state</strong> — fine-grained reactivity with <code>$.reactive()</code></li>
25
- <li><strong>Components</strong> — <code>$.component()</code> with state, lifecycle hooks, and computed properties</li>
26
- <li><strong>Routing</strong> — <code>$.router()</code> with history mode, params, guards, and <code>z-link</code> navigation</li>
27
- <li><strong>Store</strong> — centralized state via <code>$.store()</code></li>
28
- <li><strong>SSR</strong> — <code>createSSRApp()</code>, <code>renderToString()</code>, hydration markers</li>
29
- <li><strong>HTTP</strong> — <code>$.http</code> with interceptors, timeout, and parallel requests</li>
30
- <li><strong>Zero dependencies</strong> — nothing to install, audit, or keep up-to-date</li>
31
- </ul>
32
- </div>
33
-
34
- <div class="card">
35
- <h3>This Scaffold</h3>
36
- <p style="line-height:1.7;">
37
- You're running the <strong>SSR scaffold</strong> — a production-ready starter
38
- with server-side rendering, client hydration, param-based routing, JSON API
39
- endpoints, and per-route SEO metadata. Edit the components in <code>app/</code>
40
- and the server in <code>server/</code> to make it your own.
41
- </p>
42
- </div>
43
-
44
- <div class="card">
45
- <h3>Links</h3>
46
- <ul style="padding-left:1.2rem; line-height:2.2; list-style:none;">
47
- <li>📄 <a href="https://z-query.com" target="_blank" rel="noopener">zQuery Website</a></li>
48
- <li>📦 <a href="https://www.npmjs.com/package/zero-query" target="_blank" rel="noopener">npm — zero-query</a></li>
49
- <li>🛠️ <a href="https://github.com/tonywied17/zero-query" target="_blank" rel="noopener">GitHub Repository</a></li>
50
- <li>📖 <a href="https://z-query.com/docs" target="_blank" rel="noopener">Documentation</a></li>
51
- </ul>
52
- </div>
53
- `;
54
- }
55
- };
1
+ // about.js - About page component
2
+
3
+ export const aboutPage = {
4
+ render() {
5
+ return `
6
+ <div class="page-header">
7
+ <h1>About</h1>
8
+ <p class="subtitle">This app is powered by zQuery — a zero-dependency frontend micro-library.</p>
9
+ </div>
10
+
11
+ <div class="card">
12
+ <h3>What is zQuery?</h3>
13
+ <p style="line-height:1.7;">
14
+ zQuery is a lightweight, zero-dependency JavaScript library for building
15
+ reactive web applications. It provides components, routing, state management,
16
+ server-side rendering, and more — all in a single, tiny package with no
17
+ build step required.
18
+ </p>
19
+ </div>
20
+
21
+ <div class="card">
22
+ <h3>Core Features</h3>
23
+ <ul style="padding-left:1.2rem; line-height:2;">
24
+ <li><strong>Reactive state</strong> — fine-grained reactivity with <code>$.reactive()</code></li>
25
+ <li><strong>Components</strong> — <code>$.component()</code> with state, lifecycle hooks, and computed properties</li>
26
+ <li><strong>Routing</strong> — <code>$.router()</code> with history mode, params, guards, and <code>z-link</code> navigation</li>
27
+ <li><strong>Store</strong> — centralized state via <code>$.store()</code></li>
28
+ <li><strong>SSR</strong> — <code>createSSRApp()</code>, <code>renderToString()</code>, hydration markers</li>
29
+ <li><strong>HTTP</strong> — <code>$.http</code> with interceptors, timeout, and parallel requests</li>
30
+ <li><strong>Zero dependencies</strong> — nothing to install, audit, or keep up-to-date</li>
31
+ </ul>
32
+ </div>
33
+
34
+ <div class="card">
35
+ <h3>This Scaffold</h3>
36
+ <p style="line-height:1.7;">
37
+ You're running the <strong>SSR scaffold</strong> — a production-ready starter
38
+ with server-side rendering, client hydration, param-based routing, JSON API
39
+ endpoints, and per-route SEO metadata. Edit the components in <code>app/</code>
40
+ and the server in <code>server/</code> to make it your own.
41
+ </p>
42
+ </div>
43
+
44
+ <div class="card">
45
+ <h3>Links</h3>
46
+ <ul style="padding-left:1.2rem; line-height:2.2; list-style:none;">
47
+ <li>📄 <a href="https://z-query.com" target="_blank" rel="noopener">zQuery Website</a></li>
48
+ <li>📦 <a href="https://www.npmjs.com/package/zero-query" target="_blank" rel="noopener">npm — zero-query</a></li>
49
+ <li>🛠️ <a href="https://github.com/tonywied17/zero-query" target="_blank" rel="noopener">GitHub Repository</a></li>
50
+ <li>📖 <a href="https://z-query.com/docs" target="_blank" rel="noopener">Documentation</a></li>
51
+ </ul>
52
+ </div>
53
+ `;
54
+ }
55
+ };
@@ -1,65 +1,65 @@
1
- // blog/index.js - Blog listing component
2
- //
3
- // Renders a grid of blog post cards. Data flow:
4
- // SSR: Server passes { posts } as props → render() reads this.props.posts
5
- // Client: init() checks window.__SSR_DATA__ first (hydration), then fetches
6
- // from /api/posts for subsequent navigations.
7
- //
8
- // Demonstrates:
9
- // - Shared component that works on both server and client
10
- // - Server data injection via props (SSR) and fetch (client)
11
- // - z-link with dynamic URLs for client-side navigation
12
- // - Clean SSR-friendly templates (no DOM API in render)
13
-
14
- export const blogList = {
15
- state: () => ({
16
- posts: [],
17
- loaded: false,
18
- }),
19
-
20
- async init() {
21
- // On the server, props.posts is injected by app.renderToString()
22
- if (this.props.posts) {
23
- this.state.posts = this.props.posts;
24
- this.state.loaded = true;
25
- return;
26
- }
27
-
28
- // On the client, check for SSR-embedded data first (initial page load)
29
- const ssrData = typeof window !== 'undefined' && window.__SSR_DATA__;
30
- if (ssrData && ssrData.component === 'blog-list') {
31
- this.state.posts = ssrData.props.posts;
32
- this.state.loaded = true;
33
- window.__SSR_DATA__ = null;
34
- return;
35
- }
36
-
37
- // Client navigation — fetch from server API
38
- const res = await fetch('/api/posts');
39
- this.state.posts = await res.json();
40
- this.state.loaded = true;
41
- },
42
-
43
- render() {
44
- const posts = this.state.posts;
45
-
46
- const cards = posts.map(post => `
47
- <a z-link="/blog/${post.slug}" class="blog-card">
48
- <div class="blog-card-header">
49
- <span class="badge badge-ssr">${post.tag}</span>
50
- <time class="blog-date">${post.date}</time>
51
- </div>
52
- <h3 class="blog-title">${post.title}</h3>
53
- <p class="blog-summary">${post.summary}</p>
54
- </a>
55
- `).join('');
56
-
57
- return `
58
- <div class="page-header">
59
- <h1>Blog</h1>
60
- <p class="subtitle">Server-rendered articles — fast first paint, fully crawlable.</p>
61
- </div>
62
- <div class="blog-grid">${cards}</div>
63
- `;
64
- }
65
- };
1
+ // blog/index.js - Blog listing component
2
+ //
3
+ // Renders a grid of blog post cards. Data flow:
4
+ // SSR: Server passes { posts } as props → render() reads this.props.posts
5
+ // Client: init() checks window.__SSR_DATA__ first (hydration), then fetches
6
+ // from /api/posts for subsequent navigations.
7
+ //
8
+ // Demonstrates:
9
+ // - Shared component that works on both server and client
10
+ // - Server data injection via props (SSR) and fetch (client)
11
+ // - z-link with dynamic URLs for client-side navigation
12
+ // - Clean SSR-friendly templates (no DOM API in render)
13
+
14
+ export const blogList = {
15
+ state: () => ({
16
+ posts: [],
17
+ loaded: false,
18
+ }),
19
+
20
+ async init() {
21
+ // On the server, props.posts is injected by app.renderToString()
22
+ if (this.props.posts) {
23
+ this.state.posts = this.props.posts;
24
+ this.state.loaded = true;
25
+ return;
26
+ }
27
+
28
+ // On the client, check for SSR-embedded data first (initial page load)
29
+ const ssrData = typeof window !== 'undefined' && window.__SSR_DATA__;
30
+ if (ssrData && ssrData.component === 'blog-list') {
31
+ this.state.posts = ssrData.props.posts;
32
+ this.state.loaded = true;
33
+ window.__SSR_DATA__ = null;
34
+ return;
35
+ }
36
+
37
+ // Client navigation — fetch from server API
38
+ const res = await fetch('/api/posts');
39
+ this.state.posts = await res.json();
40
+ this.state.loaded = true;
41
+ },
42
+
43
+ render() {
44
+ const posts = this.state.posts;
45
+
46
+ const cards = posts.map(post => `
47
+ <a z-link="/blog/${post.slug}" class="blog-card">
48
+ <div class="blog-card-header">
49
+ <span class="badge badge-ssr">${post.tag}</span>
50
+ <time class="blog-date">${post.date}</time>
51
+ </div>
52
+ <h3 class="blog-title">${post.title}</h3>
53
+ <p class="blog-summary">${post.summary}</p>
54
+ </a>
55
+ `).join('');
56
+
57
+ return `
58
+ <div class="page-header">
59
+ <h1>Blog</h1>
60
+ <p class="subtitle">Server-rendered articles — fast first paint, fully crawlable.</p>
61
+ </div>
62
+ <div class="blog-grid">${cards}</div>
63
+ `;
64
+ }
65
+ };
@@ -1,86 +1,86 @@
1
- // blog/post.js - Single blog post detail component
2
- //
3
- // Renders the full content of a blog post. Data flow:
4
- // SSR: Server passes { post } as props → init() reads this.props.post
5
- // Client: init() checks window.__SSR_DATA__ first, then fetches from
6
- // /api/posts/:slug for client-side navigations.
7
- //
8
- // Route: /blog/:slug
9
- //
10
- // Demonstrates:
11
- // - Param routing with this.props.$params.slug (or this.props.slug)
12
- // - Server data injection via props (SSR) and fetch (client)
13
- // - z-link for back-navigation without full page reload
14
- // - Graceful handling of missing data (404-style fallback)
15
-
16
- export const blogPost = {
17
- state: () => ({
18
- post: null,
19
- loaded: false,
20
- }),
21
-
22
- async init() {
23
- const slug = this.props.slug || (this.props.$params && this.props.$params.slug);
24
-
25
- // On the server, props.post is injected by app.renderToString()
26
- if (this.props.post) {
27
- this.state.post = this.props.post;
28
- this.state.loaded = true;
29
- return;
30
- }
31
-
32
- // On the client, check for SSR-embedded data first (initial page load)
33
- const ssrData = typeof window !== 'undefined' && window.__SSR_DATA__;
34
- if (ssrData && ssrData.component === 'blog-post' && ssrData.params.slug === slug) {
35
- this.state.post = ssrData.props.post;
36
- this.state.loaded = true;
37
- if (ssrData.meta && ssrData.meta.title) document.title = ssrData.meta.title;
38
- window.__SSR_DATA__ = null;
39
- return;
40
- }
41
-
42
- // Client navigation — fetch from server API
43
- const res = await fetch(`/api/posts/${slug}`);
44
- if (res.ok) {
45
- this.state.post = await res.json();
46
- }
47
- this.state.loaded = true;
48
-
49
- // Update page title for client-side navigations
50
- if (this.state.post) {
51
- document.title = `${this.state.post.title} — {{NAME}}`;
52
- } else {
53
- document.title = 'Post Not Found — {{NAME}}';
54
- }
55
- },
56
-
57
- render() {
58
- const post = this.state.post;
59
-
60
- if (!post) {
61
- return `
62
- <div class="page-header center">
63
- <h1>Post Not Found</h1>
64
- <p class="subtitle">The article you're looking for doesn't exist.</p>
65
- <p style="margin-top:1rem;">
66
- <a z-link="/blog" class="back-link">← Back to Blog</a>
67
- </p>
68
- </div>
69
- `;
70
- }
71
-
72
- return `
73
- <article class="blog-post">
74
- <header class="page-header">
75
- <a z-link="/blog" class="back-link">← Back to Blog</a>
76
- <h1>${post.title}</h1>
77
- <div class="blog-post-meta">
78
- <span class="badge badge-ssr">${post.tag}</span>
79
- <time class="blog-date">${post.date}</time>
80
- </div>
81
- </header>
82
- <div class="blog-post-body">${post.body}</div>
83
- </article>
84
- `;
85
- }
86
- };
1
+ // blog/post.js - Single blog post detail component
2
+ //
3
+ // Renders the full content of a blog post. Data flow:
4
+ // SSR: Server passes { post } as props → init() reads this.props.post
5
+ // Client: init() checks window.__SSR_DATA__ first, then fetches from
6
+ // /api/posts/:slug for client-side navigations.
7
+ //
8
+ // Route: /blog/:slug
9
+ //
10
+ // Demonstrates:
11
+ // - Param routing with this.props.$params.slug (or this.props.slug)
12
+ // - Server data injection via props (SSR) and fetch (client)
13
+ // - z-link for back-navigation without full page reload
14
+ // - Graceful handling of missing data (404-style fallback)
15
+
16
+ export const blogPost = {
17
+ state: () => ({
18
+ post: null,
19
+ loaded: false,
20
+ }),
21
+
22
+ async init() {
23
+ const slug = this.props.slug || (this.props.$params && this.props.$params.slug);
24
+
25
+ // On the server, props.post is injected by app.renderToString()
26
+ if (this.props.post) {
27
+ this.state.post = this.props.post;
28
+ this.state.loaded = true;
29
+ return;
30
+ }
31
+
32
+ // On the client, check for SSR-embedded data first (initial page load)
33
+ const ssrData = typeof window !== 'undefined' && window.__SSR_DATA__;
34
+ if (ssrData && ssrData.component === 'blog-post' && ssrData.params.slug === slug) {
35
+ this.state.post = ssrData.props.post;
36
+ this.state.loaded = true;
37
+ if (ssrData.meta && ssrData.meta.title) document.title = ssrData.meta.title;
38
+ window.__SSR_DATA__ = null;
39
+ return;
40
+ }
41
+
42
+ // Client navigation — fetch from server API
43
+ const res = await fetch(`/api/posts/${slug}`);
44
+ if (res.ok) {
45
+ this.state.post = await res.json();
46
+ }
47
+ this.state.loaded = true;
48
+
49
+ // Update page title for client-side navigations
50
+ if (this.state.post) {
51
+ document.title = `${this.state.post.title} — {{NAME}}`;
52
+ } else {
53
+ document.title = 'Post Not Found — {{NAME}}';
54
+ }
55
+ },
56
+
57
+ render() {
58
+ const post = this.state.post;
59
+
60
+ if (!post) {
61
+ return `
62
+ <div class="page-header center">
63
+ <h1>Post Not Found</h1>
64
+ <p class="subtitle">The article you're looking for doesn't exist.</p>
65
+ <p style="margin-top:1rem;">
66
+ <a z-link="/blog" class="back-link">← Back to Blog</a>
67
+ </p>
68
+ </div>
69
+ `;
70
+ }
71
+
72
+ return `
73
+ <article class="blog-post">
74
+ <header class="page-header">
75
+ <a z-link="/blog" class="back-link">← Back to Blog</a>
76
+ <h1>${post.title}</h1>
77
+ <div class="blog-post-meta">
78
+ <span class="badge badge-ssr">${post.tag}</span>
79
+ <time class="blog-date">${post.date}</time>
80
+ </div>
81
+ </header>
82
+ <div class="blog-post-body">${post.body}</div>
83
+ </article>
84
+ `;
85
+ }
86
+ };
@@ -1,37 +1,37 @@
1
- // home.js - Home page component
2
- //
3
- // Exports a plain definition object that works on both client and server.
4
- // The client registers it with $.component(), the server with app.component().
5
-
6
- export const homePage = {
7
- state: () => ({
8
- greeting: 'Hello',
9
- timestamp: new Date().toLocaleTimeString(),
10
- }),
11
-
12
- // init() runs on both client and server - no DOM required
13
- init() {
14
- const hour = new Date().getHours();
15
- this.state.greeting =
16
- hour < 12 ? 'Good morning' :
17
- hour < 18 ? 'Good afternoon' : 'Good evening';
18
- },
19
-
20
- render() {
21
- return `
22
- <div class="page-header">
23
- <h1>${this.state.greeting} 👋</h1>
24
- <p class="subtitle">Rendered with zQuery SSR</p>
25
- </div>
26
- <div class="card">
27
- <h3>Server-Side Rendering</h3>
28
- <p>
29
- This page was rendered to HTML on the server and served as a complete
30
- document. The same component definition powers both the SSR server and
31
- the client-side SPA.
32
- </p>
33
- <p>Rendered at <strong>${this.state.timestamp}</strong></p>
34
- </div>
35
- `;
36
- }
37
- };
1
+ // home.js - Home page component
2
+ //
3
+ // Exports a plain definition object that works on both client and server.
4
+ // The client registers it with $.component(), the server with app.component().
5
+
6
+ export const homePage = {
7
+ state: () => ({
8
+ greeting: 'Hello',
9
+ timestamp: new Date().toLocaleTimeString(),
10
+ }),
11
+
12
+ // init() runs on both client and server - no DOM required
13
+ init() {
14
+ const hour = new Date().getHours();
15
+ this.state.greeting =
16
+ hour < 12 ? 'Good morning' :
17
+ hour < 18 ? 'Good afternoon' : 'Good evening';
18
+ },
19
+
20
+ render() {
21
+ return `
22
+ <div class="page-header">
23
+ <h1>${this.state.greeting} 👋</h1>
24
+ <p class="subtitle">Rendered with zQuery SSR</p>
25
+ </div>
26
+ <div class="card">
27
+ <h3>Server-Side Rendering</h3>
28
+ <p>
29
+ This page was rendered to HTML on the server and served as a complete
30
+ document. The same component definition powers both the SSR server and
31
+ the client-side SPA.
32
+ </p>
33
+ <p>Rendered at <strong>${this.state.timestamp}</strong></p>
34
+ </div>
35
+ `;
36
+ }
37
+ };
@@ -1,15 +1,15 @@
1
- // not-found.js - 404 fallback component
2
-
3
- export const notFound = {
4
- render() {
5
- return `
6
- <div class="page-header" style="text-align:center; margin-top:4rem;">
7
- <h1>404</h1>
8
- <p class="subtitle">Page not found.</p>
9
- <p style="margin-top:1rem;">
10
- <a z-link="/" style="color:var(--accent);">← Home</a>
11
- </p>
12
- </div>
13
- `;
14
- }
15
- };
1
+ // not-found.js - 404 fallback component
2
+
3
+ export const notFound = {
4
+ render() {
5
+ return `
6
+ <div class="page-header" style="text-align:center; margin-top:4rem;">
7
+ <h1>404</h1>
8
+ <p class="subtitle">Page not found.</p>
9
+ <p style="margin-top:1rem;">
10
+ <a z-link="/" style="color:var(--accent);">← Home</a>
11
+ </p>
12
+ </div>
13
+ `;
14
+ }
15
+ };
@@ -1,8 +1,8 @@
1
- // routes.js - Route definitions (shared between client and server)
2
-
3
- export const routes = [
4
- { path: '/', component: 'home-page' },
5
- { path: '/blog', component: 'blog-list' },
6
- { path: '/blog/:slug', component: 'blog-post' },
7
- { path: '/about', component: 'about-page' },
8
- ];
1
+ // routes.js - Route definitions (shared between client and server)
2
+
3
+ export const routes = [
4
+ { path: '/', component: 'home-page' },
5
+ { path: '/blog', component: 'blog-list' },
6
+ { path: '/blog/:slug', component: 'blog-post' },
7
+ { path: '/about', component: 'about-page' },
8
+ ];