zero-query 1.0.9 → 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.
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/cli/args.js +33 -33
- package/cli/commands/build-api.js +443 -0
- package/cli/commands/build.js +254 -216
- package/cli/commands/bundle.js +1228 -1183
- package/cli/commands/create.js +137 -121
- package/cli/commands/dev/devtools/index.js +56 -56
- package/cli/commands/dev/devtools/js/components.js +49 -49
- package/cli/commands/dev/devtools/js/core.js +423 -423
- package/cli/commands/dev/devtools/js/elements.js +421 -421
- package/cli/commands/dev/devtools/js/network.js +166 -166
- package/cli/commands/dev/devtools/js/performance.js +73 -73
- package/cli/commands/dev/devtools/js/router.js +105 -105
- package/cli/commands/dev/devtools/js/source.js +132 -132
- package/cli/commands/dev/devtools/js/stats.js +35 -35
- package/cli/commands/dev/devtools/js/tabs.js +79 -79
- package/cli/commands/dev/devtools/panel.html +95 -95
- package/cli/commands/dev/devtools/styles.css +244 -244
- package/cli/commands/dev/index.js +107 -107
- package/cli/commands/dev/logger.js +75 -75
- package/cli/commands/dev/overlay.js +858 -858
- package/cli/commands/dev/server.js +220 -167
- package/cli/commands/dev/validator.js +94 -94
- package/cli/commands/dev/watcher.js +172 -172
- package/cli/help.js +114 -112
- package/cli/index.js +52 -52
- package/cli/scaffold/default/LICENSE +21 -21
- package/cli/scaffold/default/app/app.js +207 -207
- package/cli/scaffold/default/app/components/about.js +201 -201
- package/cli/scaffold/default/app/components/api-demo.js +143 -143
- package/cli/scaffold/default/app/components/contact-card.js +231 -231
- package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
- package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
- package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
- package/cli/scaffold/default/app/components/counter.js +127 -127
- package/cli/scaffold/default/app/components/home.js +249 -249
- package/cli/scaffold/default/app/components/not-found.js +16 -16
- package/cli/scaffold/default/app/components/playground/playground.css +115 -115
- package/cli/scaffold/default/app/components/playground/playground.html +161 -161
- package/cli/scaffold/default/app/components/playground/playground.js +116 -116
- package/cli/scaffold/default/app/components/todos.js +225 -225
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
- package/cli/scaffold/default/app/routes.js +15 -15
- package/cli/scaffold/default/app/store.js +101 -101
- package/cli/scaffold/default/global.css +552 -552
- package/cli/scaffold/default/index.html +99 -99
- package/cli/scaffold/minimal/app/app.js +85 -85
- package/cli/scaffold/minimal/app/components/about.js +68 -68
- package/cli/scaffold/minimal/app/components/counter.js +122 -122
- package/cli/scaffold/minimal/app/components/home.js +68 -68
- package/cli/scaffold/minimal/app/components/not-found.js +16 -16
- package/cli/scaffold/minimal/app/routes.js +9 -9
- package/cli/scaffold/minimal/app/store.js +36 -36
- package/cli/scaffold/minimal/global.css +300 -300
- package/cli/scaffold/minimal/index.html +44 -44
- package/cli/scaffold/ssr/app/app.js +41 -41
- package/cli/scaffold/ssr/app/components/about.js +55 -55
- package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
- package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
- package/cli/scaffold/ssr/app/components/home.js +37 -37
- package/cli/scaffold/ssr/app/components/not-found.js +15 -15
- package/cli/scaffold/ssr/app/routes.js +8 -8
- package/cli/scaffold/ssr/global.css +228 -228
- package/cli/scaffold/ssr/index.html +37 -37
- package/cli/scaffold/ssr/package.json +8 -8
- package/cli/scaffold/ssr/server/data/posts.js +144 -144
- package/cli/scaffold/ssr/server/index.js +213 -213
- package/cli/scaffold/webrtc/app/app.js +11 -0
- package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
- package/cli/scaffold/webrtc/app/lib/room.js +252 -0
- package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
- package/cli/scaffold/webrtc/global.css +250 -0
- package/cli/scaffold/webrtc/index.html +21 -0
- package/cli/utils.js +305 -287
- package/dist/API.md +7264 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +10313 -6252
- package/dist/zquery.min.js +8 -601
- package/index.d.ts +570 -365
- package/index.js +311 -232
- package/package.json +76 -69
- package/src/component.js +1709 -1454
- package/src/core.js +921 -921
- package/src/diff.js +497 -497
- package/src/errors.js +209 -209
- package/src/expression.js +922 -922
- package/src/http.js +242 -242
- package/src/package.json +1 -1
- package/src/reactive.js +255 -254
- package/src/router.js +843 -773
- package/src/ssr.js +418 -418
- package/src/store.js +318 -272
- package/src/utils.js +515 -515
- package/src/webrtc/e2ee.js +351 -0
- package/src/webrtc/errors.js +116 -0
- package/src/webrtc/ice.js +301 -0
- package/src/webrtc/index.js +131 -0
- package/src/webrtc/joinToken.js +119 -0
- package/src/webrtc/observe.js +172 -0
- package/src/webrtc/peer.js +351 -0
- package/src/webrtc/reactive.js +268 -0
- package/src/webrtc/room.js +625 -0
- package/src/webrtc/sdp.js +302 -0
- package/src/webrtc/sfu/index.js +43 -0
- package/src/webrtc/sfu/livekit.js +131 -0
- package/src/webrtc/sfu/mediasoup.js +150 -0
- package/src/webrtc/signaling.js +373 -0
- package/src/webrtc/turn.js +237 -0
- package/tests/_helpers/webrtcFakes.js +289 -0
- package/tests/audit.test.js +4158 -4158
- package/tests/cli.test.js +1136 -1023
- package/tests/compare.test.js +497 -0
- package/tests/component.test.js +3969 -3938
- package/tests/core.test.js +1910 -1910
- package/tests/dev-server.test.js +489 -0
- package/tests/diff.test.js +1416 -1416
- package/tests/docs.test.js +1664 -0
- package/tests/electron-features.test.js +864 -0
- package/tests/errors.test.js +619 -619
- package/tests/expression.test.js +1056 -1056
- package/tests/http.test.js +648 -648
- package/tests/reactive.test.js +819 -819
- package/tests/router.test.js +2327 -2327
- package/tests/ssr.test.js +870 -870
- package/tests/store.test.js +830 -830
- package/tests/test-minifier.js +153 -153
- package/tests/test-ssr.js +27 -27
- package/tests/utils.test.js +1377 -1377
- package/tests/webrtc/e2ee.test.js +283 -0
- package/tests/webrtc/ice.test.js +202 -0
- package/tests/webrtc/joinToken.test.js +89 -0
- package/tests/webrtc/observe.test.js +111 -0
- package/tests/webrtc/peer.test.js +373 -0
- package/tests/webrtc/reactive.test.js +235 -0
- package/tests/webrtc/room.test.js +406 -0
- package/tests/webrtc/sdp.test.js +151 -0
- package/tests/webrtc/sfu-livekit.test.js +119 -0
- package/tests/webrtc/sfu.test.js +160 -0
- package/tests/webrtc/signaling.test.js +251 -0
- package/tests/webrtc/turn.test.js +256 -0
- package/types/collection.d.ts +383 -383
- package/types/component.d.ts +186 -186
- package/types/errors.d.ts +135 -135
- package/types/http.d.ts +92 -92
- package/types/misc.d.ts +201 -201
- package/types/reactive.d.ts +98 -98
- package/types/router.d.ts +190 -190
- package/types/ssr.d.ts +102 -102
- package/types/store.d.ts +146 -145
- package/types/utils.d.ts +245 -245
- 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
|
+
];
|