zero-query 0.4.9 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/cli/commands/build.js +4 -2
- package/cli/commands/bundle.js +113 -10
- package/cli/commands/dev/index.js +82 -0
- package/cli/commands/dev/logger.js +70 -0
- package/cli/commands/dev/overlay.js +317 -0
- package/cli/commands/dev/server.js +129 -0
- package/cli/commands/dev/validator.js +94 -0
- package/cli/commands/dev/watcher.js +114 -0
- package/cli/commands/{dev.js → dev.old.js} +8 -4
- package/cli/help.js +18 -6
- package/cli/scaffold/favicon.ico +0 -0
- package/cli/scaffold/scripts/components/about.js +14 -2
- package/cli/scaffold/scripts/components/contacts/contacts.html +5 -4
- package/cli/scaffold/scripts/components/contacts/contacts.js +17 -1
- package/cli/scaffold/scripts/components/counter.js +30 -10
- package/cli/scaffold/scripts/components/home.js +3 -3
- package/cli/scaffold/scripts/components/todos.js +6 -5
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +1550 -97
- package/dist/zquery.min.js +11 -8
- package/index.d.ts +253 -14
- package/index.js +25 -8
- package/package.json +8 -2
- package/src/component.js +175 -44
- package/src/core.js +25 -18
- package/src/diff.js +280 -0
- package/src/errors.js +155 -0
- package/src/expression.js +806 -0
- package/src/http.js +18 -10
- package/src/reactive.js +29 -4
- package/src/router.js +11 -5
- package/src/ssr.js +224 -0
- package/src/store.js +24 -8
|
@@ -321,10 +321,13 @@ function devServer() {
|
|
|
321
321
|
|
|
322
322
|
const { createApp, static: serveStatic } = zeroHttp;
|
|
323
323
|
|
|
324
|
+
// Custom HTML entry file (default: index.html)
|
|
325
|
+
const htmlEntry = option('index', 'i', 'index.html');
|
|
326
|
+
|
|
324
327
|
// Determine the project root to serve
|
|
325
328
|
let root = null;
|
|
326
329
|
for (let i = 1; i < args.length; i++) {
|
|
327
|
-
if (!args[i].startsWith('-') && args[i - 1] !== '-p' && args[i - 1] !== '--port') {
|
|
330
|
+
if (!args[i].startsWith('-') && args[i - 1] !== '-p' && args[i - 1] !== '--port' && args[i - 1] !== '--index') {
|
|
328
331
|
root = path.resolve(process.cwd(), args[i]);
|
|
329
332
|
break;
|
|
330
333
|
}
|
|
@@ -336,7 +339,7 @@ function devServer() {
|
|
|
336
339
|
path.join(process.cwd(), 'src'),
|
|
337
340
|
];
|
|
338
341
|
for (const c of candidates) {
|
|
339
|
-
if (fs.existsSync(path.join(c,
|
|
342
|
+
if (fs.existsSync(path.join(c, htmlEntry))) { root = c; break; }
|
|
340
343
|
}
|
|
341
344
|
if (!root) root = process.cwd();
|
|
342
345
|
}
|
|
@@ -389,9 +392,9 @@ function devServer() {
|
|
|
389
392
|
res.status(404).send('Not Found');
|
|
390
393
|
return;
|
|
391
394
|
}
|
|
392
|
-
const indexPath = path.join(root,
|
|
395
|
+
const indexPath = path.join(root, htmlEntry);
|
|
393
396
|
if (!fs.existsSync(indexPath)) {
|
|
394
|
-
res.status(404).send(
|
|
397
|
+
res.status(404).send(`${htmlEntry} not found`);
|
|
395
398
|
return;
|
|
396
399
|
}
|
|
397
400
|
let html = fs.readFileSync(indexPath, 'utf-8');
|
|
@@ -495,6 +498,7 @@ function devServer() {
|
|
|
495
498
|
console.log(` \x1b[2m${'-'.repeat(40)}\x1b[0m`);
|
|
496
499
|
console.log(` Local: \x1b[36mhttp://localhost:${PORT}/\x1b[0m`);
|
|
497
500
|
console.log(` Root: ${path.relative(process.cwd(), root) || '.'}`);
|
|
501
|
+
if (htmlEntry !== 'index.html') console.log(` HTML: \x1b[36m${htmlEntry}\x1b[0m`);
|
|
498
502
|
console.log(` Live Reload: \x1b[32menabled\x1b[0m (SSE)`);
|
|
499
503
|
console.log(` Overlay: \x1b[32menabled\x1b[0m (syntax + runtime errors)`);
|
|
500
504
|
if (noIntercept) console.log(` Intercept: \x1b[33mdisabled\x1b[0m (--no-intercept)`);
|
package/cli/help.js
CHANGED
|
@@ -12,6 +12,7 @@ function showHelp() {
|
|
|
12
12
|
|
|
13
13
|
dev [root] Start a dev server with live-reload
|
|
14
14
|
--port, -p <number> Port number (default: 3100)
|
|
15
|
+
--index, -i <file> Index HTML file (default: index.html)
|
|
15
16
|
--no-intercept Disable auto-resolution of zquery.min.js
|
|
16
17
|
(serve the on-disk vendor copy instead)
|
|
17
18
|
|
|
@@ -20,9 +21,10 @@ function showHelp() {
|
|
|
20
21
|
overlay in the browser. Runtime errors and
|
|
21
22
|
unhandled rejections are also captured.
|
|
22
23
|
|
|
23
|
-
bundle [dir]
|
|
24
|
-
--out, -o <path> Output directory (default: dist/ next to
|
|
25
|
-
--
|
|
24
|
+
bundle [dir|file] Bundle app ES modules into a single file
|
|
25
|
+
--out, -o <path> Output directory (default: dist/ next to HTML file)
|
|
26
|
+
--index, -i <file> Index HTML file (default: auto-detected)
|
|
27
|
+
--minimal, -m Only output HTML + bundled JS (skip static assets)
|
|
26
28
|
|
|
27
29
|
build Build the zQuery library \u2192 dist/ --watch, -w Watch src/ and rebuild on changes (must be run from the project root where src/ lives)
|
|
28
30
|
|
|
@@ -34,9 +36,10 @@ function showHelp() {
|
|
|
34
36
|
2. Within HTML: module script pointing to app.js, else first module script
|
|
35
37
|
3. JS scan: $.router( first (entry point), then $.mount( / $.store(
|
|
36
38
|
4. Convention fallbacks (scripts/app.js, app.js, etc.)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
• Passing a directory auto-detects the entry; passing a file uses it directly
|
|
40
|
+
• zquery.min.js is always embedded (auto-built from source if not found)
|
|
41
|
+
• HTML file is auto-detected (any .html, not just index.html)
|
|
42
|
+
• Output goes to dist/server/ and dist/local/ next to the detected HTML file
|
|
40
43
|
|
|
41
44
|
OUTPUT
|
|
42
45
|
|
|
@@ -79,9 +82,18 @@ function showHelp() {
|
|
|
79
82
|
# Bundle an app from the project root
|
|
80
83
|
zquery bundle my-app/
|
|
81
84
|
|
|
85
|
+
# Pass a direct entry file (skip auto-detection)
|
|
86
|
+
zquery bundle my-app/scripts/main.js
|
|
87
|
+
|
|
82
88
|
# Custom output directory
|
|
83
89
|
zquery bundle my-app/ -o build/
|
|
84
90
|
|
|
91
|
+
# Minimal build (only HTML + bundled JS, no static assets)
|
|
92
|
+
zquery bundle my-app/ --minimal
|
|
93
|
+
|
|
94
|
+
# Dev server with a custom index page
|
|
95
|
+
zquery dev my-app/ --index home.html
|
|
96
|
+
|
|
85
97
|
The bundler walks the ES module import graph starting from the entry
|
|
86
98
|
file, topologically sorts dependencies, strips import/export syntax,
|
|
87
99
|
and concatenates everything into a single IIFE with content-hashed
|
|
Binary file
|
|
@@ -46,6 +46,18 @@ $.component('about-page', {
|
|
|
46
46
|
<strong>$.component()</strong>
|
|
47
47
|
<span>Reactive components with state, lifecycle hooks, and template rendering</span>
|
|
48
48
|
</div>
|
|
49
|
+
<div class="feature-item">
|
|
50
|
+
<strong>computed / watch</strong>
|
|
51
|
+
<span>Derived state properties and reactive watchers on the counter page</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="feature-item">
|
|
54
|
+
<strong>DOM Diffing</strong>
|
|
55
|
+
<span>Efficient <code>morph()</code> engine patches only changed DOM nodes on re-render</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="feature-item">
|
|
58
|
+
<strong>z-key</strong>
|
|
59
|
+
<span>Keyed list reconciliation in z-for loops (todos, counter history, contacts)</span>
|
|
60
|
+
</div>
|
|
49
61
|
<div class="feature-item">
|
|
50
62
|
<strong>$.router()</strong>
|
|
51
63
|
<span>SPA routing with history mode, z-link navigation, and fallback pages</span>
|
|
@@ -79,8 +91,8 @@ $.component('about-page', {
|
|
|
79
91
|
<span>Safe rendering of user-generated and API content</span>
|
|
80
92
|
</div>
|
|
81
93
|
<div class="feature-item">
|
|
82
|
-
<strong
|
|
83
|
-
<span>
|
|
94
|
+
<strong>CSP-safe expressions</strong>
|
|
95
|
+
<span>Template expressions evaluated without <code>eval()</code> or <code>new Function()</code></span>
|
|
84
96
|
</div>
|
|
85
97
|
<div class="feature-item">
|
|
86
98
|
<strong>z-model / z-ref</strong>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<h1>Contacts</h1>
|
|
14
14
|
<p class="subtitle">
|
|
15
15
|
External template & styles via <code>templateUrl</code> / <code>styleUrl</code>.
|
|
16
|
-
Directives: <code>z-if</code>, <code>z-for</code>, <code>z-show</code>,
|
|
16
|
+
Directives: <code>z-if</code>, <code>z-for</code> + <code>z-key</code>, <code>z-show</code>,
|
|
17
17
|
<code>z-bind</code>, <code>z-class</code>, <code>z-model</code>, and more.
|
|
18
18
|
</p>
|
|
19
19
|
</div>
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
<ul class="contacts-list">
|
|
74
74
|
<li
|
|
75
75
|
z-for="contact in contacts"
|
|
76
|
+
z-key="{{contact.id}}"
|
|
76
77
|
class="contacts-item {{contact.id === selectedId ? 'selected' : ''}} {{contact.favorite ? 'is-favorite' : ''}}"
|
|
77
78
|
@click="selectContact({{contact.id}})"
|
|
78
79
|
>
|
|
@@ -111,15 +112,15 @@
|
|
|
111
112
|
<div class="card contact-detail" z-if="selectedId !== null">
|
|
112
113
|
<div class="detail-header">
|
|
113
114
|
<div>
|
|
114
|
-
<h3 z-text="
|
|
115
|
-
<p class="muted" z-text="
|
|
115
|
+
<h3 z-text="selectedName"></h3>
|
|
116
|
+
<p class="muted" z-text="selectedEmail"></p>
|
|
116
117
|
</div>
|
|
117
118
|
<div class="detail-actions">
|
|
118
119
|
<button
|
|
119
120
|
class="btn btn-outline btn-sm"
|
|
120
121
|
@click="cycleStatus({{selectedId}})"
|
|
121
122
|
>
|
|
122
|
-
Status: <span z-text="
|
|
123
|
+
Status: <span z-text="selectedStatus"></span>
|
|
123
124
|
</button>
|
|
124
125
|
|
|
125
126
|
<!-- Confirm delete pattern using z-if / z-else -->
|
|
@@ -23,6 +23,9 @@ $.component('contacts-page', {
|
|
|
23
23
|
nameError: '',
|
|
24
24
|
emailError: '',
|
|
25
25
|
selectedId: null,
|
|
26
|
+
selectedName: '',
|
|
27
|
+
selectedEmail: '',
|
|
28
|
+
selectedStatus: '',
|
|
26
29
|
confirmDeleteId: null,
|
|
27
30
|
totalAdded: 0,
|
|
28
31
|
favoriteCount: 0,
|
|
@@ -42,6 +45,7 @@ $.component('contacts-page', {
|
|
|
42
45
|
this.state.contacts = store.state.contacts;
|
|
43
46
|
this.state.totalAdded = store.state.contactsAdded;
|
|
44
47
|
this.state.favoriteCount = store.getters.favoriteCount;
|
|
48
|
+
this._syncSelected();
|
|
45
49
|
},
|
|
46
50
|
|
|
47
51
|
// -- Actions --
|
|
@@ -101,8 +105,10 @@ $.component('contacts-page', {
|
|
|
101
105
|
},
|
|
102
106
|
|
|
103
107
|
selectContact(id) {
|
|
104
|
-
|
|
108
|
+
const numId = Number(id);
|
|
109
|
+
this.state.selectedId = this.state.selectedId === numId ? null : numId;
|
|
105
110
|
this.state.confirmDeleteId = null;
|
|
111
|
+
this._syncSelected();
|
|
106
112
|
},
|
|
107
113
|
|
|
108
114
|
confirmDelete(id) {
|
|
@@ -125,6 +131,16 @@ $.component('contacts-page', {
|
|
|
125
131
|
|
|
126
132
|
cycleStatus(id) {
|
|
127
133
|
$.getStore('main').dispatch('cycleContactStatus', Number(id));
|
|
134
|
+
this._syncSelected();
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
_syncSelected() {
|
|
138
|
+
const c = this.state.selectedId != null
|
|
139
|
+
? this.state.contacts.find(c => c.id === this.state.selectedId)
|
|
140
|
+
: null;
|
|
141
|
+
this.state.selectedName = c ? c.name : '';
|
|
142
|
+
this.state.selectedEmail = c ? c.email : '';
|
|
143
|
+
this.state.selectedStatus = c ? c.status : '';
|
|
128
144
|
},
|
|
129
145
|
|
|
130
146
|
_clearForm() {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// scripts/components/counter.js — interactive counter
|
|
2
2
|
//
|
|
3
|
-
// Demonstrates: component state,
|
|
4
|
-
// z-model two-way binding with
|
|
5
|
-
// z-if, z-for
|
|
3
|
+
// Demonstrates: component state, computed properties, watch callbacks,
|
|
4
|
+
// @click event binding, z-model two-way binding with
|
|
5
|
+
// z-number modifier, z-class, z-if, z-for with z-key,
|
|
6
|
+
// $.bus toast notifications
|
|
6
7
|
|
|
7
8
|
$.component('counter-page', {
|
|
8
9
|
state: () => ({
|
|
@@ -11,16 +12,35 @@ $.component('counter-page', {
|
|
|
11
12
|
history: [],
|
|
12
13
|
}),
|
|
13
14
|
|
|
15
|
+
// Computed properties — derived values that update automatically
|
|
16
|
+
computed: {
|
|
17
|
+
isNegative: (state) => state.count < 0,
|
|
18
|
+
historyCount: (state) => state.history.length,
|
|
19
|
+
lastAction: (state) => state.history.length ? state.history[state.history.length - 1] : null,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// Watch — react to specific state changes
|
|
23
|
+
watch: {
|
|
24
|
+
count(val) {
|
|
25
|
+
if (val === 100) $.bus.emit('toast', { message: 'Century! 🎉', type: 'success' });
|
|
26
|
+
if (val === -100) $.bus.emit('toast', { message: 'Negative century!', type: 'error' });
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
|
|
14
30
|
increment() {
|
|
15
31
|
this.state.count += this.state.step;
|
|
16
|
-
this.
|
|
17
|
-
if (this.state.history.length > 8) this.state.history.shift();
|
|
32
|
+
this._pushHistory('+', this.state.step, this.state.count);
|
|
18
33
|
},
|
|
19
34
|
|
|
20
35
|
decrement() {
|
|
21
36
|
this.state.count -= this.state.step;
|
|
22
|
-
this.
|
|
23
|
-
|
|
37
|
+
this._pushHistory('−', this.state.step, this.state.count);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
_pushHistory(action, value, result) {
|
|
41
|
+
const raw = this.state.history.__raw || this.state.history;
|
|
42
|
+
const next = [...raw, { id: Date.now(), action, value, result }];
|
|
43
|
+
this.state.history = next.length > 8 ? next.slice(-8) : next;
|
|
24
44
|
},
|
|
25
45
|
|
|
26
46
|
reset() {
|
|
@@ -33,7 +53,7 @@ $.component('counter-page', {
|
|
|
33
53
|
return `
|
|
34
54
|
<div class="page-header">
|
|
35
55
|
<h1>Counter</h1>
|
|
36
|
-
<p class="subtitle">
|
|
56
|
+
<p class="subtitle"><code>computed</code>, <code>watch</code>, <code>@click</code>, <code>z-model</code>, <code>z-class</code>, and <code>z-for</code> with <code>z-key</code>.</p>
|
|
37
57
|
</div>
|
|
38
58
|
|
|
39
59
|
<div class="card counter-card">
|
|
@@ -55,9 +75,9 @@ $.component('counter-page', {
|
|
|
55
75
|
</div>
|
|
56
76
|
|
|
57
77
|
<div class="card card-muted" z-if="history.length > 0">
|
|
58
|
-
<h3>History</h3>
|
|
78
|
+
<h3>History <small style="color:var(--text-muted);font-weight:400;">(${this.computed.historyCount} entries)</small></h3>
|
|
59
79
|
<div class="history-list">
|
|
60
|
-
<span z-for="e in history" class="history-item">{{e.action}}{{e.value}} → <strong>{{e.result}}</strong></span>
|
|
80
|
+
<span z-for="e in history" z-key="{{e.id}}" class="history-item">{{e.action}}{{e.value}} → <strong>{{e.result}}</strong></span>
|
|
61
81
|
</div>
|
|
62
82
|
</div>
|
|
63
83
|
`;
|
|
@@ -62,19 +62,19 @@ $.component('home-page', {
|
|
|
62
62
|
|
|
63
63
|
<div class="card">
|
|
64
64
|
<h3>🔢 Counter</h3>
|
|
65
|
-
<p>
|
|
65
|
+
<p><code>computed</code> properties, <code>watch</code> callbacks, and <code>z-for</code> with <code>z-key</code> diffing.</p>
|
|
66
66
|
<a z-link="/counter" class="btn btn-outline">Try It →</a>
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
69
|
<div class="card">
|
|
70
70
|
<h3>✅ Todos</h3>
|
|
71
|
-
<p>Global store
|
|
71
|
+
<p>Global store, <code>z-key</code> keyed lists, DOM diffing. <strong>${store.getters.todoCount}</strong> items, <strong>${store.getters.doneCount}</strong> done.</p>
|
|
72
72
|
<a z-link="/todos" class="btn btn-outline">Try It →</a>
|
|
73
73
|
</div>
|
|
74
74
|
|
|
75
75
|
<div class="card">
|
|
76
76
|
<h3>📇 Contacts</h3>
|
|
77
|
-
<p>External templates
|
|
77
|
+
<p>External templates, scoped styles, and <code>z-key</code> keyed lists. <strong>${store.getters.contactCount}</strong> contacts, <strong>${store.getters.favoriteCount}</strong> ★ favorited.</p>
|
|
78
78
|
<a z-link="/contacts" class="btn btn-outline">Try It →</a>
|
|
79
79
|
</div>
|
|
80
80
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// scripts/components/todos.js — todo list with global store
|
|
2
2
|
//
|
|
3
3
|
// Demonstrates: $.getStore, store.dispatch, store.subscribe,
|
|
4
|
-
// store getters,
|
|
5
|
-
// z-if, z-show, @click
|
|
6
|
-
//
|
|
4
|
+
// store getters, computed properties, z-model, z-ref,
|
|
5
|
+
// z-class, z-for with z-key, z-if, z-show, @click
|
|
6
|
+
// with args, @submit.prevent, mounted/destroyed
|
|
7
|
+
// lifecycle, $.bus toast, $.debounce
|
|
7
8
|
|
|
8
9
|
$.component('todos-page', {
|
|
9
10
|
state: () => ({
|
|
@@ -83,7 +84,7 @@ $.component('todos-page', {
|
|
|
83
84
|
return `
|
|
84
85
|
<div class="page-header">
|
|
85
86
|
<h1>Todos</h1>
|
|
86
|
-
<p class="subtitle">Global store with <code>$.store()</code>, <code>z-for</code>, <code>z-class</code>, <code>z-if</code>, and <code>z-show</code>.</p>
|
|
87
|
+
<p class="subtitle">Global store with <code>$.store()</code>, <code>z-for</code> + <code>z-key</code>, <code>z-class</code>, <code>z-if</code>, and <code>z-show</code>.</p>
|
|
87
88
|
</div>
|
|
88
89
|
|
|
89
90
|
<div class="card">
|
|
@@ -114,7 +115,7 @@ $.component('todos-page', {
|
|
|
114
115
|
</div>
|
|
115
116
|
|
|
116
117
|
<ul z-else class="todo-list">
|
|
117
|
-
<li z-for="t in filtered" class="todo-item {{t.done ? 'done' : ''}}">
|
|
118
|
+
<li z-for="t in filtered" z-key="{{t.id}}" class="todo-item {{t.done ? 'done' : ''}}">
|
|
118
119
|
<button class="todo-check" @click="toggleTodo('{{t.id}}')"></button>
|
|
119
120
|
<span class="todo-text">{{$.escapeHtml(t.text)}}</span>
|
|
120
121
|
<button class="todo-remove" @click="removeTodo('{{t.id}}')">✕</button>
|
package/dist/zquery.dist.zip
CHANGED
|
Binary file
|