zero-query 1.1.1 → 1.2.0

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 (154) 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 +443 -442
  5. package/cli/commands/build.js +254 -247
  6. package/cli/commands/bundle.js +1228 -1224
  7. package/cli/commands/create.js +137 -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 +295 -0
  73. package/cli/scaffold/webrtc/app/lib/room.js +252 -0
  74. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  75. package/cli/scaffold/webrtc/global.css +250 -0
  76. package/cli/scaffold/webrtc/index.html +21 -0
  77. package/cli/utils.js +305 -287
  78. package/dist/API.md +661 -0
  79. package/dist/zquery.dist.zip +0 -0
  80. package/dist/zquery.js +10313 -6614
  81. package/dist/zquery.min.js +8 -631
  82. package/index.d.ts +570 -371
  83. package/index.js +311 -240
  84. package/package.json +76 -70
  85. package/src/component.js +1709 -1691
  86. package/src/core.js +921 -921
  87. package/src/diff.js +497 -497
  88. package/src/errors.js +209 -209
  89. package/src/expression.js +922 -922
  90. package/src/http.js +242 -242
  91. package/src/package.json +1 -1
  92. package/src/reactive.js +255 -255
  93. package/src/router.js +843 -843
  94. package/src/ssr.js +418 -418
  95. package/src/store.js +318 -318
  96. package/src/utils.js +515 -515
  97. package/src/webrtc/e2ee.js +351 -0
  98. package/src/webrtc/errors.js +116 -0
  99. package/src/webrtc/ice.js +301 -0
  100. package/src/webrtc/index.js +131 -0
  101. package/src/webrtc/joinToken.js +119 -0
  102. package/src/webrtc/observe.js +172 -0
  103. package/src/webrtc/peer.js +351 -0
  104. package/src/webrtc/reactive.js +268 -0
  105. package/src/webrtc/room.js +625 -0
  106. package/src/webrtc/sdp.js +302 -0
  107. package/src/webrtc/sfu/index.js +43 -0
  108. package/src/webrtc/sfu/livekit.js +131 -0
  109. package/src/webrtc/sfu/mediasoup.js +150 -0
  110. package/src/webrtc/signaling.js +373 -0
  111. package/src/webrtc/turn.js +237 -0
  112. package/tests/_helpers/webrtcFakes.js +289 -0
  113. package/tests/audit.test.js +4158 -4158
  114. package/tests/cli.test.js +1136 -1103
  115. package/tests/compare.test.js +497 -486
  116. package/tests/component.test.js +3969 -3938
  117. package/tests/core.test.js +1910 -1910
  118. package/tests/dev-server.test.js +489 -489
  119. package/tests/diff.test.js +1416 -1416
  120. package/tests/docs.test.js +1664 -1650
  121. package/tests/electron-features.test.js +864 -864
  122. package/tests/errors.test.js +619 -619
  123. package/tests/expression.test.js +1056 -1056
  124. package/tests/http.test.js +648 -648
  125. package/tests/reactive.test.js +819 -819
  126. package/tests/router.test.js +2327 -2327
  127. package/tests/ssr.test.js +870 -870
  128. package/tests/store.test.js +830 -830
  129. package/tests/test-minifier.js +153 -153
  130. package/tests/test-ssr.js +27 -27
  131. package/tests/utils.test.js +1377 -1377
  132. package/tests/webrtc/e2ee.test.js +283 -0
  133. package/tests/webrtc/ice.test.js +202 -0
  134. package/tests/webrtc/joinToken.test.js +89 -0
  135. package/tests/webrtc/observe.test.js +111 -0
  136. package/tests/webrtc/peer.test.js +373 -0
  137. package/tests/webrtc/reactive.test.js +235 -0
  138. package/tests/webrtc/room.test.js +406 -0
  139. package/tests/webrtc/sdp.test.js +151 -0
  140. package/tests/webrtc/sfu-livekit.test.js +119 -0
  141. package/tests/webrtc/sfu.test.js +160 -0
  142. package/tests/webrtc/signaling.test.js +251 -0
  143. package/tests/webrtc/turn.test.js +256 -0
  144. package/types/collection.d.ts +383 -383
  145. package/types/component.d.ts +186 -186
  146. package/types/errors.d.ts +135 -135
  147. package/types/http.d.ts +92 -92
  148. package/types/misc.d.ts +201 -201
  149. package/types/reactive.d.ts +98 -98
  150. package/types/router.d.ts +190 -190
  151. package/types/ssr.d.ts +102 -102
  152. package/types/store.d.ts +146 -146
  153. package/types/utils.d.ts +245 -245
  154. 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
+ ];