zero-query 0.2.5 → 0.2.8
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 +256 -1401
- package/cli.js +400 -33
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +2 -2
- package/dist/zquery.min.js +2 -2
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -1,1401 +1,256 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="docs/images/logo.svg" alt="zQuery logo" width="300" height="300">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
<h1 align="center">zQuery</h1>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
|
|
9
|
-
[](https://www.npmjs.com/package/zero-query)
|
|
10
|
-
[](https://www.npmjs.com/package/zero-query)
|
|
11
|
-
[](https://github.com/tonywied17/zero-query)
|
|
12
|
-
[](https://opensource.org/licenses/MIT)
|
|
13
|
-
[](package.json)
|
|
14
|
-
[](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code)
|
|
15
|
-
|
|
16
|
-
</p>
|
|
17
|
-
|
|
18
|
-
> **Lightweight, zero-dependency frontend library that combines jQuery-style DOM manipulation with a modern reactive component system, SPA router, global state management, HTTP client, and utility toolkit — all in a single ~45 KB minified browser bundle. Works out of the box with ES modules — no build step required. An optional CLI bundler is available for single-file distribution.**
|
|
19
|
-
|
|
20
|
-
## Features
|
|
21
|
-
|
|
22
|
-
| Module | Highlights |
|
|
23
|
-
| --- | --- |
|
|
24
|
-
| **Core `$()`** | jQuery-like chainable selectors, traversal, DOM manipulation, events, animation |
|
|
25
|
-
| **Components** | Reactive state, template literals, `@event` delegation, `z-model` two-way binding, scoped styles, lifecycle hooks |
|
|
26
|
-
| **Router** | History & hash mode, route params (`:id`), guards, lazy loading, `z-link` navigation |
|
|
27
|
-
| **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions |
|
|
28
|
-
| **HTTP** | Fetch wrapper with auto-JSON, interceptors, timeout/abort, base URL |
|
|
29
|
-
| **Reactive** | Deep proxy reactivity, Signals, computed values, effects |
|
|
30
|
-
| **Utils** | debounce, throttle, pipe, once, sleep, escapeHtml, uuid, deepClone, deepMerge, storage/session wrappers, event bus |
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Quick Start
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
###
|
|
67
|
-
|
|
68
|
-
```html
|
|
69
|
-
<!DOCTYPE html>
|
|
70
|
-
<html lang="en">
|
|
71
|
-
<head>
|
|
72
|
-
<meta charset="UTF-8">
|
|
73
|
-
<title>My App</title>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
</
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
app.
|
|
129
|
-
routes.js
|
|
130
|
-
store.js
|
|
131
|
-
components/
|
|
132
|
-
home.js
|
|
133
|
-
counter.js
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// Wrap an existing element
|
|
258
|
-
$(document.getElementById('app')); // returns the element as-is
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Quick-Ref Shortcuts
|
|
262
|
-
|
|
263
|
-
```js
|
|
264
|
-
$.id('myId') // document.getElementById('myId')
|
|
265
|
-
$.class('myClass') // document.querySelector('.myClass')
|
|
266
|
-
$.classes('myClass') // Array.from(document.getElementsByClassName('myClass'))
|
|
267
|
-
$.tag('div') // Array.from(document.getElementsByTagName('div'))
|
|
268
|
-
$.children('parentId') // Array.from of children of #parentId
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Element Creation
|
|
272
|
-
|
|
273
|
-
```js
|
|
274
|
-
const btn = $.create('button', {
|
|
275
|
-
class: 'primary',
|
|
276
|
-
style: { padding: '10px' },
|
|
277
|
-
onclick: () => alert('clicked'),
|
|
278
|
-
data: { action: 'submit' }
|
|
279
|
-
}, 'Click Me');
|
|
280
|
-
|
|
281
|
-
document.body.appendChild(btn);
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Collection Methods (on `ZQueryCollection` via `$.all()`)
|
|
285
|
-
|
|
286
|
-
**Traversal:** `find()`, `parent()`, `closest()`, `children()`, `siblings()`, `next()`, `prev()`, `filter()`, `not()`, `has()`
|
|
287
|
-
|
|
288
|
-
**Iteration:** `each()`, `map()`, `first()`, `last()`, `eq(i)`, `toArray()`
|
|
289
|
-
|
|
290
|
-
**Classes:** `addClass()`, `removeClass()`, `toggleClass()`, `hasClass()`
|
|
291
|
-
|
|
292
|
-
**Attributes:** `attr()`, `removeAttr()`, `prop()`, `data()`
|
|
293
|
-
|
|
294
|
-
**Content:** `html()`, `text()`, `val()`
|
|
295
|
-
|
|
296
|
-
**DOM Manipulation:** `append()`, `prepend()`, `after()`, `before()`, `wrap()`, `remove()`, `empty()`, `clone()`, `replaceWith()`
|
|
297
|
-
|
|
298
|
-
**CSS / Dimensions:** `css()`, `width()`, `height()`, `offset()`, `position()`
|
|
299
|
-
|
|
300
|
-
**Visibility:** `show()`, `hide()`, `toggle()`
|
|
301
|
-
|
|
302
|
-
**Events:** `on()`, `off()`, `one()`, `trigger()`, `click()`, `submit()`, `focus()`, `blur()`
|
|
303
|
-
|
|
304
|
-
**Animation:** `animate()`, `fadeIn()`, `fadeOut()`, `slideToggle()`
|
|
305
|
-
|
|
306
|
-
**Forms:** `serialize()`, `serializeObject()`
|
|
307
|
-
|
|
308
|
-
### Delegated Events
|
|
309
|
-
|
|
310
|
-
```js
|
|
311
|
-
// Direct event on a collection
|
|
312
|
-
$.all('.btn').on('click', (e) => console.log('clicked', e.target));
|
|
313
|
-
|
|
314
|
-
// Delegated event (like jQuery's .on with selector)
|
|
315
|
-
$.all('#list').on('click', '.item', function(e) {
|
|
316
|
-
console.log('Item clicked:', this.textContent);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// One-time event
|
|
320
|
-
$.all('.btn').one('click', () => console.log('fires once'));
|
|
321
|
-
|
|
322
|
-
// Custom event
|
|
323
|
-
$.all('.widget').trigger('custom:update', { value: 42 });
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### Global Delegation
|
|
327
|
-
|
|
328
|
-
```js
|
|
329
|
-
// Listen for clicks on any .delete-btn anywhere in the document
|
|
330
|
-
$.on('click', '.delete-btn', function(e) {
|
|
331
|
-
this.closest('.row').remove();
|
|
332
|
-
});
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Extend Collection Prototype
|
|
336
|
-
|
|
337
|
-
```js
|
|
338
|
-
// Add custom methods to all collections (like $.fn in jQuery)
|
|
339
|
-
$.fn.highlight = function(color = 'yellow') {
|
|
340
|
-
return this.css({ background: color });
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
$.all('.important').highlight('#ff0');
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
---
|
|
347
|
-
|
|
348
|
-
## Components
|
|
349
|
-
|
|
350
|
-
Declarative components with reactive state, template literals, event delegation, two-way binding, scoped styles, and lifecycle hooks. No JSX, no virtual DOM, no build step.
|
|
351
|
-
|
|
352
|
-
### Defining a Component
|
|
353
|
-
|
|
354
|
-
```js
|
|
355
|
-
$.component('app-counter', {
|
|
356
|
-
// Initial state (object or function returning object)
|
|
357
|
-
state: () => ({ count: 0, step: 1 }),
|
|
358
|
-
|
|
359
|
-
// Lifecycle hooks
|
|
360
|
-
init() { /* runs before first render */ },
|
|
361
|
-
mounted() { /* runs after first render & DOM insert */ },
|
|
362
|
-
updated() { /* runs after every re-render */ },
|
|
363
|
-
destroyed() { /* runs on destroy — clean up subscriptions */ },
|
|
364
|
-
|
|
365
|
-
// Methods (available as this.methodName and in @event bindings)
|
|
366
|
-
increment() { this.state.count += this.state.step; },
|
|
367
|
-
decrement() { this.state.count -= this.state.step; },
|
|
368
|
-
reset() { this.state.count = 0; },
|
|
369
|
-
|
|
370
|
-
// Template (required) — return an HTML string
|
|
371
|
-
render() {
|
|
372
|
-
return `
|
|
373
|
-
<div class="counter">
|
|
374
|
-
<h2>Count: ${this.state.count}</h2>
|
|
375
|
-
<button @click="decrement">−</button>
|
|
376
|
-
<button @click="reset">Reset</button>
|
|
377
|
-
<button @click="increment">+</button>
|
|
378
|
-
<input z-model="step" type="number" min="1">
|
|
379
|
-
</div>
|
|
380
|
-
`;
|
|
381
|
-
},
|
|
382
|
-
|
|
383
|
-
// Scoped styles (optional — auto-prefixed to this component)
|
|
384
|
-
styles: `
|
|
385
|
-
.counter { text-align: center; }
|
|
386
|
-
button { margin: 4px; }
|
|
387
|
-
`
|
|
388
|
-
});
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Mounting
|
|
392
|
-
|
|
393
|
-
```js
|
|
394
|
-
// Mount into a specific element
|
|
395
|
-
$.mount('#app', 'app-counter');
|
|
396
|
-
|
|
397
|
-
// Mount with props
|
|
398
|
-
$.mount('#app', 'app-counter', { initialCount: 10 });
|
|
399
|
-
|
|
400
|
-
// Auto-mount: scan DOM for registered custom tags
|
|
401
|
-
$.mountAll(); // finds all <app-counter> tags and mounts them
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Directives
|
|
405
|
-
|
|
406
|
-
| Directive | Purpose | Example |
|
|
407
|
-
| --- | --- | --- |
|
|
408
|
-
| `@event` | Delegated event binding | `@click="save"` or `@click="save(1, 'draft')"` |
|
|
409
|
-
| `@event.prevent` | `preventDefault()` modifier | `@submit.prevent="handleForm"` |
|
|
410
|
-
| `@event.stop` | `stopPropagation()` modifier | `@click.stop="toggle"` |
|
|
411
|
-
| `z-model` | Reactive two-way input binding | `<input z-model="name">` |
|
|
412
|
-
| `z-model` + `z-lazy` | Update on blur instead of every keystroke | `<input z-model="search" z-lazy>` |
|
|
413
|
-
| `z-model` + `z-trim` | Trim whitespace before writing to state | `<input z-model="name" z-trim>` |
|
|
414
|
-
| `z-model` + `z-number` | Force numeric conversion | `<input z-model="qty" z-number>` |
|
|
415
|
-
| `z-ref` | Element reference | `<input z-ref="emailInput">` → `this.refs.emailInput` |
|
|
416
|
-
|
|
417
|
-
### Two-Way Binding (`z-model`)
|
|
418
|
-
|
|
419
|
-
`z-model` creates a **reactive two-way sync** between a form element and a state property. When the user types, state updates; when state changes, the element updates. Other parts of the template referencing the same state value re-render instantly.
|
|
420
|
-
|
|
421
|
-
Focus and cursor position are **automatically preserved** during re-renders.
|
|
422
|
-
|
|
423
|
-
```js
|
|
424
|
-
$.component('profile-form', {
|
|
425
|
-
state: () => ({
|
|
426
|
-
user: { name: '', email: '' },
|
|
427
|
-
age: 25,
|
|
428
|
-
plan: 'free',
|
|
429
|
-
tags: [],
|
|
430
|
-
}),
|
|
431
|
-
|
|
432
|
-
render() {
|
|
433
|
-
const s = this.state;
|
|
434
|
-
return `
|
|
435
|
-
<!-- Text input -- live display updates as you type -->
|
|
436
|
-
<input z-model="user.name" z-trim placeholder="Name">
|
|
437
|
-
<p>Hello, ${s.user.name || 'stranger'}!</p>
|
|
438
|
-
|
|
439
|
-
<!-- Nested key, email type -->
|
|
440
|
-
<input z-model="user.email" type="email" placeholder="Email">
|
|
441
|
-
|
|
442
|
-
<!-- Number -- auto-converts to Number -->
|
|
443
|
-
<input z-model="age" type="number" min="0">
|
|
444
|
-
<p>Age: ${s.age} (type: ${typeof s.age})</p>
|
|
445
|
-
|
|
446
|
-
<!-- Radio group -->
|
|
447
|
-
<label><input z-model="plan" type="radio" value="free"> Free</label>
|
|
448
|
-
<label><input z-model="plan" type="radio" value="pro"> Pro</label>
|
|
449
|
-
<p>Plan: ${s.plan}</p>
|
|
450
|
-
|
|
451
|
-
<!-- Select multiple -- syncs as array -->
|
|
452
|
-
<select z-model="tags" multiple>
|
|
453
|
-
<option>javascript</option>
|
|
454
|
-
<option>html</option>
|
|
455
|
-
<option>css</option>
|
|
456
|
-
</select>
|
|
457
|
-
<p>Tags: ${s.tags.join(', ')}</p>
|
|
458
|
-
`;
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
**Supported elements:** text inputs, textarea, number/range, checkbox, radio, select, select-multiple, contenteditable.
|
|
464
|
-
|
|
465
|
-
**Nested keys:** `z-model="user.name"` binds to `this.state.user.name`.
|
|
466
|
-
|
|
467
|
-
**Modifiers:** `z-lazy` (change event), `z-trim` (strip whitespace), `z-number` (force numeric). Combinable.
|
|
468
|
-
|
|
469
|
-
### Event Arguments
|
|
470
|
-
|
|
471
|
-
```js
|
|
472
|
-
// Pass arguments to methods from templates
|
|
473
|
-
// Supports: strings, numbers, booleans, null, state references
|
|
474
|
-
`<button @click="remove(${item.id})">Delete</button>`
|
|
475
|
-
`<button @click="setFilter('active')">Active</button>`
|
|
476
|
-
`<button @click="update(state.count)">Update</button>`
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
### Props
|
|
480
|
-
|
|
481
|
-
Props are passed as attributes on the custom element tag or via `$.mount()`:
|
|
482
|
-
|
|
483
|
-
```js
|
|
484
|
-
$.component('user-card', {
|
|
485
|
-
render() {
|
|
486
|
-
return `<div class="card"><h3>${this.props.name}</h3></div>`;
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
// Via mount
|
|
491
|
-
$.mount('#target', 'user-card', { name: 'Tony' });
|
|
492
|
-
|
|
493
|
-
// Via HTML tag (auto-mounted)
|
|
494
|
-
// <user-card name="Tony"></user-card>
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
### Instance API
|
|
498
|
-
|
|
499
|
-
```js
|
|
500
|
-
const instance = $.mount('#app', 'my-component');
|
|
501
|
-
|
|
502
|
-
instance.state.count = 5; // trigger re-render
|
|
503
|
-
instance.setState({ count: 5 }); // batch update
|
|
504
|
-
instance.emit('change', { v: 1 }); // dispatch custom event (bubbles)
|
|
505
|
-
instance.refs.myInput.focus(); // access z-ref elements
|
|
506
|
-
instance.destroy(); // teardown
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
### Getting & Destroying Instances
|
|
510
|
-
|
|
511
|
-
```js
|
|
512
|
-
const inst = $.getInstance('#app'); // get instance for element
|
|
513
|
-
$.destroy('#app'); // destroy component at element
|
|
514
|
-
$.components(); // get registry of all definitions (debug)
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### External Templates & Styles (`templateUrl` / `styleUrl`)
|
|
518
|
-
|
|
519
|
-
Components can load their HTML templates and CSS from external files instead of defining them inline. This is useful for large components, maintaining separation of concerns, or organizing components into folder structures.
|
|
520
|
-
|
|
521
|
-
**Relative Path Resolution:**
|
|
522
|
-
|
|
523
|
-
Relative `templateUrl`, `styleUrl`, and `pages.dir` paths are automatically resolved **relative to the component file** — no extra configuration needed:
|
|
524
|
-
|
|
525
|
-
```js
|
|
526
|
-
// File: scripts/components/widget/widget.js
|
|
527
|
-
$.component('my-widget', {
|
|
528
|
-
templateUrl: 'template.html', // → scripts/components/widget/template.html
|
|
529
|
-
styleUrl: 'styles.css', // → scripts/components/widget/styles.css
|
|
530
|
-
});
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
> zQuery auto-detects the calling module's URL at registration time. If you need to override the resolved base, pass a `base` string (e.g. `base: 'scripts/shared/'`). Absolute paths and full URLs are never affected.
|
|
534
|
-
|
|
535
|
-
**`styleUrl`** — load styles from a CSS file:
|
|
536
|
-
|
|
537
|
-
```js
|
|
538
|
-
$.component('my-widget', {
|
|
539
|
-
state: { title: 'Hello' },
|
|
540
|
-
|
|
541
|
-
render() {
|
|
542
|
-
return `<div class="widget"><h2>${this.state.title}</h2></div>`;
|
|
543
|
-
},
|
|
544
|
-
|
|
545
|
-
styleUrl: 'styles.css'
|
|
546
|
-
});
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
**`templateUrl`** — load HTML template from a file:
|
|
550
|
-
|
|
551
|
-
```js
|
|
552
|
-
$.component('my-widget', {
|
|
553
|
-
state: { title: 'Hello', items: ['A', 'B'] },
|
|
554
|
-
|
|
555
|
-
templateUrl: 'template.html',
|
|
556
|
-
styleUrl: 'styles.css'
|
|
557
|
-
});
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
The template file uses `{{expression}}` interpolation to access component state:
|
|
561
|
-
|
|
562
|
-
```html
|
|
563
|
-
<!-- components/my-widget/template.html -->
|
|
564
|
-
<div class="widget">
|
|
565
|
-
<h2>{{title}}</h2>
|
|
566
|
-
<p>Item count: {{items.length}}</p>
|
|
567
|
-
</div>
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
> **Notes:**
|
|
571
|
-
> - If both `render()` and `templateUrl` are defined, `render()` takes priority.
|
|
572
|
-
> - If both `styles` and `styleUrl` are defined, they are merged.
|
|
573
|
-
> - Templates and styles are fetched once per component definition and cached — multiple instances share the same cache.
|
|
574
|
-
> - Relative paths resolve relative to the component file automatically. Absolute paths and full URLs are used as-is.
|
|
575
|
-
> - `{{expression}}` has access to all `state` properties via a `with(state)` context.
|
|
576
|
-
|
|
577
|
-
### Multiple Templates — `templateUrl` as object or array
|
|
578
|
-
|
|
579
|
-
`templateUrl` also accepts an **object map** or **array** of URLs. When multiple templates are loaded, they are available inside the component via `this.templates` — a keyed map you can reference in your `render()` function.
|
|
580
|
-
|
|
581
|
-
```js
|
|
582
|
-
// Object form — keyed by name
|
|
583
|
-
$.component('docs-page', {
|
|
584
|
-
templateUrl: {
|
|
585
|
-
'router': 'pages/router.html',
|
|
586
|
-
'store': 'pages/store.html',
|
|
587
|
-
'components': 'pages/components.html',
|
|
588
|
-
},
|
|
589
|
-
render() {
|
|
590
|
-
const page = this.props.$params.section || 'router';
|
|
591
|
-
return `<div>${this.templates[page]}</div>`;
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
// Array form — keyed by index
|
|
596
|
-
$.component('multi-step', {
|
|
597
|
-
templateUrl: ['pages/step1.html', 'pages/step2.html'],
|
|
598
|
-
render() {
|
|
599
|
-
return `<div>${this.templates[this.state.step]}</div>`;
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
### Multiple Stylesheets — `styleUrl` as array
|
|
605
|
-
|
|
606
|
-
`styleUrl` can also accept an **array of URLs**. All stylesheets are fetched in parallel, concatenated, and scoped to the component.
|
|
607
|
-
|
|
608
|
-
```js
|
|
609
|
-
$.component('my-widget', {
|
|
610
|
-
styleUrl: [
|
|
611
|
-
'../shared/base.css',
|
|
612
|
-
'styles.css',
|
|
613
|
-
],
|
|
614
|
-
render() { return '<div class="widget">Content</div>'; }
|
|
615
|
-
});
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
### Global Stylesheets
|
|
619
|
-
|
|
620
|
-
**Recommended:** Use a standard `<link rel="stylesheet">` tag in your `index.html` `<head>` for app-wide CSS (resets, layout, themes). This is the most reliable way to prevent FOUC (Flash of Unstyled Content) because the browser loads the stylesheet before first paint — no JavaScript execution needed.
|
|
621
|
-
|
|
622
|
-
```html
|
|
623
|
-
<!-- index.html -->
|
|
624
|
-
<head>
|
|
625
|
-
<link rel="stylesheet" href="styles/styles.css">
|
|
626
|
-
<script src="scripts/vendor/zQuery.min.js"></script>
|
|
627
|
-
<script type="module" src="scripts/app.js"></script>
|
|
628
|
-
</head>
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
### Additional Stylesheets — `$.style()`
|
|
632
|
-
|
|
633
|
-
`$.style()` loads **global** (unscoped) stylesheet files programmatically — useful for **dynamic theme switching**, **loading additional CSS files at runtime**, **conditional styles**, or any case where a static `<link>` tag isn't flexible enough. Paths resolve **relative to the calling file**, just like component paths.
|
|
634
|
-
|
|
635
|
-
```js
|
|
636
|
-
// Load a stylesheet file dynamically
|
|
637
|
-
$.style('themes/dark.css');
|
|
638
|
-
|
|
639
|
-
// Multiple files
|
|
640
|
-
$.style(['reset.css', 'theme.css']);
|
|
641
|
-
|
|
642
|
-
// Returns a handle to remove later (theme switching)
|
|
643
|
-
const dark = $.style('themes/dark.css');
|
|
644
|
-
// ... later
|
|
645
|
-
dark.remove(); // unloads the stylesheet
|
|
646
|
-
|
|
647
|
-
// Override global styles by loading an additional file
|
|
648
|
-
$.style('overrides.css');
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
> **`<link rel>` vs `$.style()` vs `styleUrl`:**
|
|
652
|
-
> - Use a **`<link rel="stylesheet">`** in `index.html` for global/app-wide styles — best FOUC prevention.
|
|
653
|
-
> - Use **`$.style()`** to dynamically load additional stylesheet files (themes, overrides, conditional styles).
|
|
654
|
-
> - Use **`styleUrl`** on a component definition for styles scoped to that specific component.
|
|
655
|
-
> - Use component **`styles`** (inline string) for scoped inline CSS within a component definition.
|
|
656
|
-
|
|
657
|
-
### Pages Config — Multi-Page Components
|
|
658
|
-
|
|
659
|
-
The `pages` option is a high-level shorthand for components that display content from multiple HTML files in a directory (e.g. documentation, wizards, tabbed content). It replaces the need to manually build a `templateUrl` object map and maintain a separate page list.
|
|
660
|
-
|
|
661
|
-
```js
|
|
662
|
-
// File: scripts/components/docs/docs.js
|
|
663
|
-
$.component('docs-page', {
|
|
664
|
-
pages: {
|
|
665
|
-
dir: 'pages', // → scripts/components/docs/pages/
|
|
666
|
-
param: 'section', // reads :section from the route
|
|
667
|
-
default: 'getting-started', // fallback when param absent
|
|
668
|
-
items: [
|
|
669
|
-
'getting-started', // label auto-derived: 'Getting Started'
|
|
670
|
-
'project-structure', // label auto-derived: 'Project Structure'
|
|
671
|
-
{ id: 'http', label: 'HTTP Client' },
|
|
672
|
-
{ id: 'utils', label: 'Utilities' },
|
|
673
|
-
],
|
|
674
|
-
},
|
|
675
|
-
|
|
676
|
-
styleUrl: 'docs.css', // → scripts/components/docs/docs.css
|
|
677
|
-
|
|
678
|
-
render() {
|
|
679
|
-
return `
|
|
680
|
-
<nav>
|
|
681
|
-
${this.pages.map(p => `
|
|
682
|
-
<a class="${this.activePage === p.id ? 'active' : ''}"
|
|
683
|
-
z-link="/docs/${p.id}">${p.label}</a>
|
|
684
|
-
`).join('')}
|
|
685
|
-
</nav>
|
|
686
|
-
<main>${this.templates[this.activePage] || ''}</main>
|
|
687
|
-
`;
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
The `param` property tells the component **which route parameter to read**. It must match a `:param` segment in your router config. Use `fallback` so one route handles both the bare path and the parameterized path:
|
|
693
|
-
|
|
694
|
-
```js
|
|
695
|
-
// routes.js
|
|
696
|
-
$.router({
|
|
697
|
-
routes: [
|
|
698
|
-
{ path: '/docs/:section', component: 'docs-page', fallback: '/docs' },
|
|
699
|
-
]
|
|
700
|
-
});
|
|
701
|
-
// /docs → activePage = default ('getting-started')
|
|
702
|
-
// /docs/router → activePage = 'router'
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
| Property | Type | Description |
|
|
706
|
-
| --- | --- | --- |
|
|
707
|
-
| `dir` | `string` | Directory path containing the page HTML files (resolved via `base`) |
|
|
708
|
-
| `param` | `string` | Route param name — must match a `:param` segment in your route |
|
|
709
|
-
| `default` | `string` | Page id shown when the route param is absent |
|
|
710
|
-
| `ext` | `string` | File extension (default `'.html'`) |
|
|
711
|
-
| `items` | `Array` | Page ids (strings) and/or `{id, label}` objects |
|
|
712
|
-
|
|
713
|
-
> **How it works:** Under the hood, `pages` auto-generates a `templateUrl` object map (`{ id: 'dir/id.html' }`) and normalizes the items into a `{id, label}` array. String ids auto-derive labels by converting kebab-case to Title Case (e.g. `'getting-started'` → `'Getting Started'`). The component then exposes `this.pages`, `this.activePage`, and `this.templates` inside `render()`.
|
|
714
|
-
|
|
715
|
-
---
|
|
716
|
-
|
|
717
|
-
## Router
|
|
718
|
-
|
|
719
|
-
Client-side SPA router supporting both history mode and hash mode, with route params, query strings, navigation guards, and lazy loading.
|
|
720
|
-
|
|
721
|
-
### Setup
|
|
722
|
-
|
|
723
|
-
```js
|
|
724
|
-
const router = $.router({
|
|
725
|
-
el: '#app', // outlet element
|
|
726
|
-
mode: 'history', // 'history' (default) or 'hash'
|
|
727
|
-
base: '/my-app', // base path for sub-directory deployments
|
|
728
|
-
routes: [
|
|
729
|
-
{ path: '/', component: 'home-page' },
|
|
730
|
-
{ path: '/user/:id', component: 'user-page' },
|
|
731
|
-
{ path: '/settings', component: 'settings-page' },
|
|
732
|
-
],
|
|
733
|
-
fallback: 'not-found' // 404 component
|
|
734
|
-
});
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### Route Definitions
|
|
738
|
-
|
|
739
|
-
```js
|
|
740
|
-
{
|
|
741
|
-
path: '/user/:id', // :param for dynamic segments
|
|
742
|
-
component: 'user-page', // registered component name (string)
|
|
743
|
-
load: () => import('./pages/user.js'), // lazy load module before mount
|
|
744
|
-
}
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
- **`path`** — URL pattern. Use `:param` for named params, `*` for wildcard.
|
|
748
|
-
- **`component`** — registered component name (string) or a render function `(route) => htmlString`.
|
|
749
|
-
- **`load`** — optional async function for lazy loading (called before component mount).
|
|
750
|
-
- **`fallback`** — an additional path that also matches this route. When matched via fallback, missing params are `undefined`. Useful for pages-config components: `{ path: '/docs/:section', fallback: '/docs' }`.
|
|
751
|
-
|
|
752
|
-
### Navigation
|
|
753
|
-
|
|
754
|
-
```js
|
|
755
|
-
router.navigate('/user/42'); // push + resolve
|
|
756
|
-
router.replace('/login'); // replace current history entry
|
|
757
|
-
router.back(); // history.back()
|
|
758
|
-
router.forward(); // history.forward()
|
|
759
|
-
router.go(-2); // history.go(n)
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
### Navigation Links (HTML)
|
|
763
|
-
|
|
764
|
-
```html
|
|
765
|
-
<!-- z-link attribute for SPA navigation (no page reload) -->
|
|
766
|
-
<a z-link="/">Home</a>
|
|
767
|
-
<a z-link="/user/42">Profile</a>
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
### Route Params & Query
|
|
771
|
-
|
|
772
|
-
Inside a routed component, params and query are available as props:
|
|
773
|
-
|
|
774
|
-
```js
|
|
775
|
-
$.component('user-page', {
|
|
776
|
-
render() {
|
|
777
|
-
const userId = this.props.$params.id;
|
|
778
|
-
const tab = this.props.$query.tab || 'overview';
|
|
779
|
-
return `<h1>User ${userId}</h1><p>Tab: ${tab}</p>`;
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
### Navigation Guards
|
|
785
|
-
|
|
786
|
-
```js
|
|
787
|
-
// Before guard — return false to cancel, string to redirect
|
|
788
|
-
router.beforeEach((to, from) => {
|
|
789
|
-
if (to.path === '/admin' && !isLoggedIn()) {
|
|
790
|
-
return '/login'; // redirect
|
|
791
|
-
}
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
// After guard — analytics, etc.
|
|
795
|
-
router.afterEach((to, from) => {
|
|
796
|
-
trackPageView(to.path);
|
|
797
|
-
});
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### Route Change Listener
|
|
801
|
-
|
|
802
|
-
```js
|
|
803
|
-
const unsub = router.onChange((to, from) => {
|
|
804
|
-
console.log(`Navigated: ${from?.path} → ${to.path}`);
|
|
805
|
-
});
|
|
806
|
-
// unsub() to stop listening
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
### Current Route
|
|
810
|
-
|
|
811
|
-
```js
|
|
812
|
-
router.current // { route, params, query, path }
|
|
813
|
-
router.path // current path string
|
|
814
|
-
router.query // current query as object
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
### Dynamic Routes
|
|
818
|
-
|
|
819
|
-
```js
|
|
820
|
-
router.add({ path: '/new-page', component: 'new-page' });
|
|
821
|
-
router.remove('/old-page');
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
### Sub-Path Deployment & `<base href>`
|
|
825
|
-
|
|
826
|
-
When deploying under a sub-directory (e.g. `https://example.com/my-app/`), the router **auto-detects** `<base href>` — no extra code needed.
|
|
827
|
-
|
|
828
|
-
**Option 1 — HTML `<base href>` tag (recommended):**
|
|
829
|
-
|
|
830
|
-
Add a `<base href>` tag to your `index.html`:
|
|
831
|
-
|
|
832
|
-
```html
|
|
833
|
-
<head>
|
|
834
|
-
<base href="/my-app/">
|
|
835
|
-
<!-- ... -->
|
|
836
|
-
</head>
|
|
837
|
-
```
|
|
838
|
-
|
|
839
|
-
The router reads this automatically. No changes to `app.js` required — just call `$.router()` as usual:
|
|
840
|
-
|
|
841
|
-
```js
|
|
842
|
-
$.router({ el: '#app', routes, fallback: 'not-found' });
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
**Option 2 — Explicit `base` option:**
|
|
846
|
-
|
|
847
|
-
```js
|
|
848
|
-
$.router({ el: '#app', base: '/my-app', routes: [...] });
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
> **Tip:** Using `<base href>` is preferred because it also controls how the browser resolves relative URLs for scripts, stylesheets, images, and fetch requests — keeping all path configuration in one place. The router checks `config.base` → `window.__ZQ_BASE` → `<base href>` tag, in that order.
|
|
852
|
-
|
|
853
|
-
For history mode, configure your server to rewrite all non-file requests to `index.html`. Example `.htaccess`:
|
|
854
|
-
|
|
855
|
-
```
|
|
856
|
-
RewriteEngine On
|
|
857
|
-
RewriteBase /my-app/
|
|
858
|
-
RewriteCond %{REQUEST_FILENAME} -f
|
|
859
|
-
RewriteRule ^ - [L]
|
|
860
|
-
RewriteRule ^ index.html [L]
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
---
|
|
864
|
-
|
|
865
|
-
## Store
|
|
866
|
-
|
|
867
|
-
Lightweight global state management with reactive proxies, named actions, computed getters, middleware, and subscriptions.
|
|
868
|
-
|
|
869
|
-
### Setup
|
|
870
|
-
|
|
871
|
-
```js
|
|
872
|
-
const store = $.store({
|
|
873
|
-
state: {
|
|
874
|
-
count: 0,
|
|
875
|
-
user: null,
|
|
876
|
-
todos: [],
|
|
877
|
-
},
|
|
878
|
-
|
|
879
|
-
actions: {
|
|
880
|
-
increment(state) { state.count++; },
|
|
881
|
-
setUser(state, user) { state.user = user; },
|
|
882
|
-
addTodo(state, text) {
|
|
883
|
-
const raw = state.todos.__raw || state.todos;
|
|
884
|
-
state.todos = [...raw, { id: Date.now(), text, done: false }];
|
|
885
|
-
},
|
|
886
|
-
},
|
|
887
|
-
|
|
888
|
-
getters: {
|
|
889
|
-
doubleCount: (state) => state.count * 2,
|
|
890
|
-
doneCount: (state) => state.todos.filter(t => t.done).length,
|
|
891
|
-
},
|
|
892
|
-
|
|
893
|
-
debug: true, // logs actions to console
|
|
894
|
-
});
|
|
895
|
-
```
|
|
896
|
-
|
|
897
|
-
### Dispatching Actions
|
|
898
|
-
|
|
899
|
-
```js
|
|
900
|
-
store.dispatch('increment');
|
|
901
|
-
store.dispatch('setUser', { name: 'Tony', role: 'admin' });
|
|
902
|
-
store.dispatch('addTodo', 'Write documentation');
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
### Reading State
|
|
906
|
-
|
|
907
|
-
```js
|
|
908
|
-
store.state.count // reactive — triggers subscribers on change
|
|
909
|
-
store.getters.doubleCount // computed from state
|
|
910
|
-
store.snapshot() // deep-cloned plain object
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
### Subscriptions
|
|
914
|
-
|
|
915
|
-
```js
|
|
916
|
-
// Subscribe to a specific key
|
|
917
|
-
const unsub = store.subscribe('count', (newVal, oldVal, key) => {
|
|
918
|
-
console.log(`count changed: ${oldVal} → ${newVal}`);
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
// Wildcard — subscribe to all changes
|
|
922
|
-
store.subscribe((key, newVal, oldVal) => {
|
|
923
|
-
console.log(`${key} changed`);
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
unsub(); // unsubscribe
|
|
927
|
-
```
|
|
928
|
-
|
|
929
|
-
### Using Store in Components
|
|
930
|
-
|
|
931
|
-
```js
|
|
932
|
-
$.component('my-widget', {
|
|
933
|
-
mounted() {
|
|
934
|
-
// Re-render when store key changes
|
|
935
|
-
this._unsub = store.subscribe('count', () => {
|
|
936
|
-
this._scheduleUpdate();
|
|
937
|
-
});
|
|
938
|
-
},
|
|
939
|
-
|
|
940
|
-
destroyed() {
|
|
941
|
-
this._unsub?.();
|
|
942
|
-
},
|
|
943
|
-
|
|
944
|
-
render() {
|
|
945
|
-
return `<p>Count: ${store.state.count}</p>`;
|
|
946
|
-
}
|
|
947
|
-
});
|
|
948
|
-
```
|
|
949
|
-
|
|
950
|
-
### Middleware
|
|
951
|
-
|
|
952
|
-
```js
|
|
953
|
-
store.use((actionName, args, state) => {
|
|
954
|
-
console.log(`[middleware] ${actionName}`, args);
|
|
955
|
-
// return false to block the action
|
|
956
|
-
});
|
|
957
|
-
```
|
|
958
|
-
|
|
959
|
-
### Named Stores
|
|
960
|
-
|
|
961
|
-
```js
|
|
962
|
-
const userStore = $.store('users', { state: { list: [] }, actions: { ... } });
|
|
963
|
-
const appStore = $.store('app', { state: { theme: 'dark' }, actions: { ... } });
|
|
964
|
-
|
|
965
|
-
// Retrieve later
|
|
966
|
-
$.getStore('users');
|
|
967
|
-
$.getStore('app');
|
|
968
|
-
```
|
|
969
|
-
|
|
970
|
-
### State Management
|
|
971
|
-
|
|
972
|
-
```js
|
|
973
|
-
store.replaceState({ count: 0, user: null, todos: [] });
|
|
974
|
-
store.reset({ count: 0, user: null, todos: [] }); // also clears history
|
|
975
|
-
store.history; // array of { action, args, timestamp }
|
|
976
|
-
```
|
|
977
|
-
|
|
978
|
-
---
|
|
979
|
-
|
|
980
|
-
## HTTP Client
|
|
981
|
-
|
|
982
|
-
A lightweight fetch wrapper providing auto-JSON serialization, interceptors, timeout/abort support, and a clean chainable API.
|
|
983
|
-
|
|
984
|
-
### Basic Requests
|
|
985
|
-
|
|
986
|
-
```js
|
|
987
|
-
// GET with query params
|
|
988
|
-
const res = await $.get('/api/users', { page: 1, limit: 10 });
|
|
989
|
-
console.log(res.data); // parsed JSON
|
|
990
|
-
|
|
991
|
-
// POST with JSON body
|
|
992
|
-
const created = await $.post('/api/users', { name: 'Tony', email: 'tony@example.com' });
|
|
993
|
-
|
|
994
|
-
// PUT, PATCH, DELETE
|
|
995
|
-
await $.put('/api/users/1', { name: 'Updated' });
|
|
996
|
-
await $.patch('/api/users/1', { email: 'new@example.com' });
|
|
997
|
-
await $.delete('/api/users/1');
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
### Configuration
|
|
1001
|
-
|
|
1002
|
-
```js
|
|
1003
|
-
$.http.configure({
|
|
1004
|
-
baseURL: 'https://api.example.com',
|
|
1005
|
-
headers: { Authorization: 'Bearer token123' },
|
|
1006
|
-
timeout: 15000, // ms (default: 30000)
|
|
1007
|
-
});
|
|
1008
|
-
```
|
|
1009
|
-
|
|
1010
|
-
### Interceptors
|
|
1011
|
-
|
|
1012
|
-
```js
|
|
1013
|
-
// Request interceptor
|
|
1014
|
-
$.http.onRequest((fetchOpts, url) => {
|
|
1015
|
-
fetchOpts.headers['X-Request-ID'] = $.uuid();
|
|
1016
|
-
// return false to block | return { url, options } to modify
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
|
-
// Response interceptor
|
|
1020
|
-
$.http.onResponse((result) => {
|
|
1021
|
-
if (result.status === 401) {
|
|
1022
|
-
window.location.href = '/login';
|
|
1023
|
-
}
|
|
1024
|
-
});
|
|
1025
|
-
```
|
|
1026
|
-
|
|
1027
|
-
### Abort / Cancel
|
|
1028
|
-
|
|
1029
|
-
```js
|
|
1030
|
-
const controller = $.http.createAbort();
|
|
1031
|
-
|
|
1032
|
-
$.get('/api/slow', null, { signal: controller.signal })
|
|
1033
|
-
.catch(err => console.log('Aborted:', err.message));
|
|
1034
|
-
|
|
1035
|
-
// Cancel after 2 seconds
|
|
1036
|
-
setTimeout(() => controller.abort(), 2000);
|
|
1037
|
-
```
|
|
1038
|
-
|
|
1039
|
-
### Response Object
|
|
1040
|
-
|
|
1041
|
-
Every request resolves with:
|
|
1042
|
-
|
|
1043
|
-
```js
|
|
1044
|
-
{
|
|
1045
|
-
ok: true, // response.ok
|
|
1046
|
-
status: 200, // HTTP status code
|
|
1047
|
-
statusText: 'OK',
|
|
1048
|
-
headers: { ... }, // parsed headers object
|
|
1049
|
-
data: { ... }, // auto-parsed body (JSON, text, or blob)
|
|
1050
|
-
response: Response, // raw fetch Response object
|
|
1051
|
-
}
|
|
1052
|
-
```
|
|
1053
|
-
|
|
1054
|
-
### FormData Upload
|
|
1055
|
-
|
|
1056
|
-
```js
|
|
1057
|
-
const formData = new FormData();
|
|
1058
|
-
formData.append('file', fileInput.files[0]);
|
|
1059
|
-
await $.post('/api/upload', formData);
|
|
1060
|
-
// Content-Type header is automatically removed so the browser sets multipart boundary
|
|
1061
|
-
```
|
|
1062
|
-
|
|
1063
|
-
### Raw Fetch
|
|
1064
|
-
|
|
1065
|
-
```js
|
|
1066
|
-
const raw = await $.http.raw('/api/stream', { method: 'GET' });
|
|
1067
|
-
const reader = raw.body.getReader();
|
|
1068
|
-
```
|
|
1069
|
-
|
|
1070
|
-
---
|
|
1071
|
-
|
|
1072
|
-
## Reactive Primitives
|
|
1073
|
-
|
|
1074
|
-
### Deep Reactive Proxy
|
|
1075
|
-
|
|
1076
|
-
```js
|
|
1077
|
-
const data = $.reactive({ user: { name: 'Tony' } }, (key, value, old) => {
|
|
1078
|
-
console.log(`${key} changed: ${old} → ${value}`);
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1081
|
-
data.user.name = 'Updated'; // triggers callback
|
|
1082
|
-
data.__isReactive; // true
|
|
1083
|
-
data.__raw; // original plain object
|
|
1084
|
-
```
|
|
1085
|
-
|
|
1086
|
-
### Signals
|
|
1087
|
-
|
|
1088
|
-
Lightweight reactive primitives inspired by Solid/Preact signals:
|
|
1089
|
-
|
|
1090
|
-
```js
|
|
1091
|
-
const count = $.signal(0);
|
|
1092
|
-
|
|
1093
|
-
// Auto-tracking effect
|
|
1094
|
-
$.effect(() => {
|
|
1095
|
-
console.log('Count is:', count.value); // runs immediately, re-runs on change
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
count.value = 5; // triggers the effect
|
|
1099
|
-
|
|
1100
|
-
// Computed signal (derived)
|
|
1101
|
-
const doubled = $.computed(() => count.value * 2);
|
|
1102
|
-
console.log(doubled.value); // 10
|
|
1103
|
-
|
|
1104
|
-
// Manual subscription
|
|
1105
|
-
const unsub = count.subscribe(() => console.log('changed'));
|
|
1106
|
-
|
|
1107
|
-
// Peek without tracking
|
|
1108
|
-
count.peek(); // returns value without subscribing
|
|
1109
|
-
```
|
|
1110
|
-
|
|
1111
|
-
---
|
|
1112
|
-
|
|
1113
|
-
## Utilities
|
|
1114
|
-
|
|
1115
|
-
All utilities are available on the `$` namespace.
|
|
1116
|
-
|
|
1117
|
-
### Function Utilities
|
|
1118
|
-
|
|
1119
|
-
```js
|
|
1120
|
-
// Debounce — delays until ms of inactivity
|
|
1121
|
-
const search = $.debounce((query) => fetchResults(query), 300);
|
|
1122
|
-
search('hello');
|
|
1123
|
-
search.cancel(); // cancel pending call
|
|
1124
|
-
|
|
1125
|
-
// Throttle — max once per ms
|
|
1126
|
-
const scroll = $.throttle(() => updatePosition(), 100);
|
|
1127
|
-
|
|
1128
|
-
// Pipe — left-to-right function composition
|
|
1129
|
-
const transform = $.pipe(trim, lowercase, capitalize);
|
|
1130
|
-
transform(' HELLO '); // 'Hello'
|
|
1131
|
-
|
|
1132
|
-
// Once — only runs the first time
|
|
1133
|
-
const init = $.once(() => { /* heavy setup */ });
|
|
1134
|
-
|
|
1135
|
-
// Sleep — async delay
|
|
1136
|
-
await $.sleep(1000);
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1139
|
-
### String Utilities
|
|
1140
|
-
|
|
1141
|
-
```js
|
|
1142
|
-
$.escapeHtml('<script>alert("xss")</script>');
|
|
1143
|
-
// <script>alert("xss")</script>
|
|
1144
|
-
|
|
1145
|
-
// Template tag with auto-escaping
|
|
1146
|
-
const safe = $.html`<div>${userInput}</div>`;
|
|
1147
|
-
|
|
1148
|
-
// Mark trusted HTML (skips escaping)
|
|
1149
|
-
const raw = $.trust('<strong>Bold</strong>');
|
|
1150
|
-
const output = $.html`<div>${raw}</div>`; // <div><strong>Bold</strong></div>
|
|
1151
|
-
|
|
1152
|
-
$.uuid(); // 'a1b2c3d4-...'
|
|
1153
|
-
$.camelCase('my-component'); // 'myComponent'
|
|
1154
|
-
$.kebabCase('myComponent'); // 'my-component'
|
|
1155
|
-
```
|
|
1156
|
-
|
|
1157
|
-
### Object Utilities
|
|
1158
|
-
|
|
1159
|
-
```js
|
|
1160
|
-
const clone = $.deepClone({ nested: { value: 1 } });
|
|
1161
|
-
const merged = $.deepMerge({}, defaults, userConfig);
|
|
1162
|
-
$.isEqual({ a: 1 }, { a: 1 }); // true
|
|
1163
|
-
```
|
|
1164
|
-
|
|
1165
|
-
### URL Utilities
|
|
1166
|
-
|
|
1167
|
-
```js
|
|
1168
|
-
$.param({ page: 1, sort: 'name' }); // 'page=1&sort=name'
|
|
1169
|
-
$.parseQuery('page=1&sort=name'); // { page: '1', sort: 'name' }
|
|
1170
|
-
```
|
|
1171
|
-
|
|
1172
|
-
### Storage Wrappers
|
|
1173
|
-
|
|
1174
|
-
```js
|
|
1175
|
-
// localStorage with auto JSON serialization
|
|
1176
|
-
$.storage.set('user', { name: 'Tony', prefs: { theme: 'dark' } });
|
|
1177
|
-
$.storage.get('user'); // { name: 'Tony', prefs: { theme: 'dark' } }
|
|
1178
|
-
$.storage.get('missing', []); // [] (fallback)
|
|
1179
|
-
$.storage.remove('user');
|
|
1180
|
-
$.storage.clear();
|
|
1181
|
-
|
|
1182
|
-
// sessionStorage (same API)
|
|
1183
|
-
$.session.set('token', 'abc123');
|
|
1184
|
-
$.session.get('token');
|
|
1185
|
-
```
|
|
1186
|
-
|
|
1187
|
-
### Event Bus
|
|
1188
|
-
|
|
1189
|
-
Global pub/sub for cross-component communication without direct coupling:
|
|
1190
|
-
|
|
1191
|
-
```js
|
|
1192
|
-
// Subscribe
|
|
1193
|
-
const unsub = $.bus.on('user:login', (user) => {
|
|
1194
|
-
console.log('Logged in:', user.name);
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
// Emit
|
|
1198
|
-
$.bus.emit('user:login', { name: 'Tony' });
|
|
1199
|
-
|
|
1200
|
-
// One-time listener
|
|
1201
|
-
$.bus.once('app:ready', () => { /* runs once */ });
|
|
1202
|
-
|
|
1203
|
-
// Unsubscribe
|
|
1204
|
-
unsub();
|
|
1205
|
-
$.bus.off('user:login', handler);
|
|
1206
|
-
$.bus.clear(); // remove all listeners
|
|
1207
|
-
```
|
|
1208
|
-
|
|
1209
|
-
---
|
|
1210
|
-
|
|
1211
|
-
## DOM Ready
|
|
1212
|
-
|
|
1213
|
-
```js
|
|
1214
|
-
// Shorthand (pass function to $)
|
|
1215
|
-
$(() => {
|
|
1216
|
-
console.log('DOM ready');
|
|
1217
|
-
});
|
|
1218
|
-
|
|
1219
|
-
// Explicit
|
|
1220
|
-
$.ready(() => {
|
|
1221
|
-
console.log('DOM ready');
|
|
1222
|
-
});
|
|
1223
|
-
```
|
|
1224
|
-
|
|
1225
|
-
---
|
|
1226
|
-
|
|
1227
|
-
## No-Conflict Mode
|
|
1228
|
-
|
|
1229
|
-
```js
|
|
1230
|
-
const mq = $.noConflict(); // removes $ from window, returns the library
|
|
1231
|
-
mq('.card').addClass('active');
|
|
1232
|
-
```
|
|
1233
|
-
|
|
1234
|
-
---
|
|
1235
|
-
|
|
1236
|
-
## Building from Source
|
|
1237
|
-
|
|
1238
|
-
```bash
|
|
1239
|
-
# One-time library build
|
|
1240
|
-
node build.js
|
|
1241
|
-
# → dist/zQuery.js (development)
|
|
1242
|
-
# → dist/zQuery.min.js (production)
|
|
1243
|
-
|
|
1244
|
-
# Or use the CLI
|
|
1245
|
-
npx zquery build
|
|
1246
|
-
```
|
|
1247
|
-
|
|
1248
|
-
> **Note:** `npx zquery build` and `node build.js` must be run from the zero-query project root (where `src/` and `index.js` live). If you've added a `build` script to your own `package.json`, `npm run build` handles the working directory for you.
|
|
1249
|
-
|
|
1250
|
-
The build script is zero-dependency — just Node.js. It concatenates all ES modules into a single IIFE and strips import/export statements. The minified version strips comments and collapses whitespace. For production builds, pipe through Terser for optimal compression.
|
|
1251
|
-
|
|
1252
|
-
---
|
|
1253
|
-
|
|
1254
|
-
## Running the Starter App
|
|
1255
|
-
|
|
1256
|
-
```bash
|
|
1257
|
-
# From the project root
|
|
1258
|
-
node build.js # build the library
|
|
1259
|
-
cp dist/zQuery.min.js examples/starter-app/scripts/vendor/ # copy to app
|
|
1260
|
-
|
|
1261
|
-
# Start the dev server (uses zero-http)
|
|
1262
|
-
npm run serve
|
|
1263
|
-
# → http://localhost:3000
|
|
1264
|
-
|
|
1265
|
-
# Or use any static server
|
|
1266
|
-
npx serve examples/starter-app
|
|
1267
|
-
```
|
|
1268
|
-
|
|
1269
|
-
The starter app includes: Home, Counter (reactive state + z-model), Todos (global store + subscriptions), API Docs (full reference), and About pages.
|
|
1270
|
-
|
|
1271
|
-
#### Bundled Version (Single-File)
|
|
1272
|
-
|
|
1273
|
-
You can also build a fully self-contained bundled version of the starter app:
|
|
1274
|
-
|
|
1275
|
-
```bash
|
|
1276
|
-
npm run bundle:app
|
|
1277
|
-
|
|
1278
|
-
# Deploy the server build
|
|
1279
|
-
# → dist/server/index.html (with <base href="/"> for web servers)
|
|
1280
|
-
|
|
1281
|
-
# Or open the local build from disk — no server needed
|
|
1282
|
-
start examples/starter-app/dist/local/index.html
|
|
1283
|
-
```
|
|
1284
|
-
|
|
1285
|
-
See [CLI Bundler](#cli-bundler-optional) for details.
|
|
1286
|
-
|
|
1287
|
-
### Local Dev Server
|
|
1288
|
-
|
|
1289
|
-
The project ships with a lightweight dev server powered by [zero-http](https://github.com/tonywied17/zero-http). It handles history-mode SPA routing (all non-file requests serve `index.html`).
|
|
1290
|
-
|
|
1291
|
-
```bash
|
|
1292
|
-
# Serve with SPA fallback routing (recommended during development)
|
|
1293
|
-
npm run serve
|
|
1294
|
-
|
|
1295
|
-
# Custom port
|
|
1296
|
-
node examples/starter-app/local-server.js 8080
|
|
1297
|
-
|
|
1298
|
-
# Watch mode — auto-rebuild bundle on file changes
|
|
1299
|
-
npm run dev
|
|
1300
|
-
|
|
1301
|
-
# Or install zero-http yourself for any project
|
|
1302
|
-
npm install zero-http --save-dev
|
|
1303
|
-
```
|
|
1304
|
-
|
|
1305
|
-
`npm run serve` gives the fastest feedback loop — edit your ES module source files and refresh the browser. Use `npm run dev` when you need the bundled output to update automatically as you work.
|
|
1306
|
-
|
|
1307
|
-
### Production Deployment
|
|
1308
|
-
|
|
1309
|
-
For production, use the bundled `dist/server/` output. It includes `<base href="/">` so deep-route refreshes resolve assets correctly. Configure your web server to rewrite non-file requests to `index.html`:
|
|
1310
|
-
|
|
1311
|
-
**Apache (.htaccess):**
|
|
1312
|
-
|
|
1313
|
-
```apache
|
|
1314
|
-
RewriteEngine On
|
|
1315
|
-
RewriteBase /
|
|
1316
|
-
RewriteCond %{REQUEST_FILENAME} !-f
|
|
1317
|
-
RewriteCond %{REQUEST_FILENAME} !-d
|
|
1318
|
-
RewriteRule ^ index.html [L]
|
|
1319
|
-
```
|
|
1320
|
-
|
|
1321
|
-
**Nginx:**
|
|
1322
|
-
|
|
1323
|
-
```nginx
|
|
1324
|
-
location / {
|
|
1325
|
-
try_files $uri $uri/ /index.html;
|
|
1326
|
-
}
|
|
1327
|
-
```
|
|
1328
|
-
|
|
1329
|
-
**Sub-path deployment** (e.g. hosted at `/my-app/`):
|
|
1330
|
-
|
|
1331
|
-
Set `<base href="/my-app/">` in your HTML `<head>` — the router auto-detects it. Or pass `base` explicitly:
|
|
1332
|
-
|
|
1333
|
-
```js
|
|
1334
|
-
$.router({ el: '#app', base: '/my-app', routes });
|
|
1335
|
-
```
|
|
1336
|
-
|
|
1337
|
-
```apache
|
|
1338
|
-
# Apache — adjust RewriteBase
|
|
1339
|
-
RewriteBase /my-app/
|
|
1340
|
-
```
|
|
1341
|
-
|
|
1342
|
-
```nginx
|
|
1343
|
-
# Nginx — adjust location block
|
|
1344
|
-
location /my-app/ {
|
|
1345
|
-
try_files $uri $uri/ /my-app/index.html;
|
|
1346
|
-
}
|
|
1347
|
-
```
|
|
1348
|
-
|
|
1349
|
-
---
|
|
1350
|
-
|
|
1351
|
-
## Complete API at a Glance
|
|
1352
|
-
|
|
1353
|
-
| Namespace | Methods |
|
|
1354
|
-
| --- | --- |
|
|
1355
|
-
| `$()` | Single-element selector → `Element \| null` |
|
|
1356
|
-
| `$.all()` | Collection selector → `ZQueryCollection` |
|
|
1357
|
-
| `$.id` `$.class` `$.classes` `$.tag` `$.children` | Quick DOM refs |
|
|
1358
|
-
| `$.create` | Element factory |
|
|
1359
|
-
| `$.ready` `$.on` | DOM ready, global delegation |
|
|
1360
|
-
| `$.fn` | Collection prototype (extend it) |
|
|
1361
|
-
| `$.component` `$.mount` `$.mountAll` `$.getInstance` `$.destroy` `$.components` | Component system |
|
|
1362
|
-
| `$.style` | Dynamically load additional global (unscoped) stylesheet file(s) — paths resolve relative to the calling file |
|
|
1363
|
-
| `$.router` `$.getRouter` | SPA router |
|
|
1364
|
-
| `$.store` `$.getStore` | State management |
|
|
1365
|
-
| `$.http` `$.get` `$.post` `$.put` `$.patch` `$.delete` | HTTP client |
|
|
1366
|
-
| `$.reactive` `$.signal` `$.computed` `$.effect` | Reactive primitives |
|
|
1367
|
-
| `$.debounce` `$.throttle` `$.pipe` `$.once` `$.sleep` | Function utils |
|
|
1368
|
-
| `$.escapeHtml` `$.html` `$.trust` `$.uuid` `$.camelCase` `$.kebabCase` | String utils |
|
|
1369
|
-
| `$.deepClone` `$.deepMerge` `$.isEqual` | Object utils |
|
|
1370
|
-
| `$.param` `$.parseQuery` | URL utils |
|
|
1371
|
-
| `$.storage` `$.session` | Storage wrappers |
|
|
1372
|
-
| `$.bus` | Event bus |
|
|
1373
|
-
| `$.version` | Library version |
|
|
1374
|
-
| `$.noConflict` | Release `$` global |
|
|
1375
|
-
|
|
1376
|
-
| CLI | Description |
|
|
1377
|
-
| --- | --- |
|
|
1378
|
-
| `zquery build` | Build the zQuery library (`dist/zQuery.min.js`) |
|
|
1379
|
-
| `zquery bundle [entry]` | Bundle app ES modules into a single IIFE file |
|
|
1380
|
-
| `zquery --help` | Show CLI usage and options |
|
|
1381
|
-
|
|
1382
|
-
For full method signatures and options, see [API.md](API.md).
|
|
1383
|
-
|
|
1384
|
-
---
|
|
1385
|
-
|
|
1386
|
-
## Editor Support
|
|
1387
|
-
|
|
1388
|
-
The official **[zQuery for VS Code](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code)** extension for Visual Studio Code provides:
|
|
1389
|
-
|
|
1390
|
-
- **Autocomplete** for `$.*`, `$.http.*`, `$.storage.*`, `$.bus.*`, and 50+ collection chain methods
|
|
1391
|
-
- **Hover documentation** with signatures and code examples for every method and directive
|
|
1392
|
-
- **HTML directive support** — completions and docs for `@event` handlers, `z-model`, `z-ref`, `z-link`
|
|
1393
|
-
- **55+ code snippets** — type `zq-` for components, router, store, HTTP, signals, and more
|
|
1394
|
-
|
|
1395
|
-
Install it from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code) or search **"zQuery for VS Code"** in the Extensions view.
|
|
1396
|
-
|
|
1397
|
-
---
|
|
1398
|
-
|
|
1399
|
-
## License
|
|
1400
|
-
|
|
1401
|
-
MIT — [Anthony Wiedman / Molex](https://github.com/tonywied17)
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/logo.svg" alt="zQuery logo" width="300" height="300">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">zQuery</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/zero-query)
|
|
10
|
+
[](https://www.npmjs.com/package/zero-query)
|
|
11
|
+
[](https://github.com/tonywied17/zero-query)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
[](package.json)
|
|
14
|
+
[](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code)
|
|
15
|
+
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
> **Lightweight, zero-dependency frontend library that combines jQuery-style DOM manipulation with a modern reactive component system, SPA router, global state management, HTTP client, and utility toolkit — all in a single ~45 KB minified browser bundle. Works out of the box with ES modules — no build step required. An optional CLI bundler is available for single-file distribution.**
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
| Module | Highlights |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| **Core `$()`** | jQuery-like chainable selectors, traversal, DOM manipulation, events, animation |
|
|
25
|
+
| **Components** | Reactive state, template literals, `@event` delegation, `z-model` two-way binding, scoped styles, lifecycle hooks |
|
|
26
|
+
| **Router** | History & hash mode, route params (`:id`), guards, lazy loading, `z-link` navigation |
|
|
27
|
+
| **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions |
|
|
28
|
+
| **HTTP** | Fetch wrapper with auto-JSON, interceptors, timeout/abort, base URL |
|
|
29
|
+
| **Reactive** | Deep proxy reactivity, Signals, computed values, effects |
|
|
30
|
+
| **Utils** | debounce, throttle, pipe, once, sleep, escapeHtml, uuid, deepClone, deepMerge, storage/session wrappers, event bus |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### Recommended: CLI Dev Server
|
|
37
|
+
|
|
38
|
+
The fastest way to develop with zQuery is via the built-in **CLI dev server** with **live-reload**. It serves your ES modules as-is and automatically resolves the library — no manual downloads required.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install (per-project or globally)
|
|
42
|
+
npm install zero-query --save-dev # or: npm install zero-query -g
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Scaffold a new project and start the server:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx zquery create my-app
|
|
49
|
+
cd my-app
|
|
50
|
+
npx zquery dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The `create` command generates a ready-to-run project with `index.html`, a router, two components, and styles. The dev server watches for file changes, hot-swaps CSS in-place, full-reloads on JS/HTML changes, and handles SPA fallback routing.
|
|
54
|
+
|
|
55
|
+
### Alternative: Manual Setup (No npm)
|
|
56
|
+
|
|
57
|
+
If you prefer **zero tooling**, download `dist/zQuery.min.js` from the [GitHub releases](https://github.com/tonywied17/zero-query/releases/tag/RELEASE) and drop it into `scripts/vendor/`. Then open `index.html` directly in a browser — no Node.js required.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/tonywied17/zero-query.git
|
|
61
|
+
cd zero-query
|
|
62
|
+
node build.js
|
|
63
|
+
# → dist/zQuery.min.js (~45 KB)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Include in HTML
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<!DOCTYPE html>
|
|
70
|
+
<html lang="en">
|
|
71
|
+
<head>
|
|
72
|
+
<meta charset="UTF-8">
|
|
73
|
+
<title>My App</title>
|
|
74
|
+
<link rel="stylesheet" href="styles/styles.css">
|
|
75
|
+
<script src="scripts/vendor/zQuery.min.js"></script>
|
|
76
|
+
<script type="module" src="scripts/app.js"></script>
|
|
77
|
+
</head>
|
|
78
|
+
<body>
|
|
79
|
+
<nav>
|
|
80
|
+
<a z-link="/">Home</a>
|
|
81
|
+
<a z-link="/about">About</a>
|
|
82
|
+
</nav>
|
|
83
|
+
<div id="app"></div>
|
|
84
|
+
</body>
|
|
85
|
+
</html>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Boot Your App
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
// scripts/app.js
|
|
92
|
+
import './components/home.js';
|
|
93
|
+
import './components/about.js';
|
|
94
|
+
import { routes } from './routes.js';
|
|
95
|
+
|
|
96
|
+
$.router({ el: '#app', routes, fallback: 'not-found' });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Define a Component
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
// scripts/components/home.js
|
|
103
|
+
$.component('home-page', {
|
|
104
|
+
state: () => ({ count: 0 }),
|
|
105
|
+
increment() { this.state.count++; },
|
|
106
|
+
render() {
|
|
107
|
+
return `
|
|
108
|
+
<h1>Home</h1>
|
|
109
|
+
<p>Count: ${this.state.count}</p>
|
|
110
|
+
<button @click="increment">+1</button>
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
That's it — a fully working SPA with the dev server's live-reload.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Recommended Project Structure
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
my-app/
|
|
124
|
+
index.html
|
|
125
|
+
scripts/
|
|
126
|
+
vendor/
|
|
127
|
+
zQuery.min.js ← only needed for manual setup; dev server auto-resolves
|
|
128
|
+
app.js
|
|
129
|
+
routes.js
|
|
130
|
+
store.js
|
|
131
|
+
components/
|
|
132
|
+
home.js
|
|
133
|
+
counter.js
|
|
134
|
+
about.js
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- One component per file inside `components/`.
|
|
138
|
+
- Names **must contain a hyphen** (Web Component convention): `home-page`, `app-counter`, etc.
|
|
139
|
+
- `app.js` is the single entry point — import components, create the store, and boot the router.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## CLI Bundler
|
|
144
|
+
|
|
145
|
+
The CLI can compile your entire app — ES modules, the library, external templates, and assets — into a **single bundled file**.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Auto-detect entry from index.html
|
|
149
|
+
npx zquery bundle
|
|
150
|
+
|
|
151
|
+
# Or specify an entry point
|
|
152
|
+
npx zquery bundle path/to/scripts/app.js
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Output goes to `dist/` next to your `index.html`:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
dist/
|
|
159
|
+
server/ ← deploy to your web server (<base href="/"> for SPA routes)
|
|
160
|
+
index.html
|
|
161
|
+
z-app.<hash>.js
|
|
162
|
+
z-app.<hash>.min.js
|
|
163
|
+
styles/
|
|
164
|
+
local/ ← open from disk (file://) — no server needed
|
|
165
|
+
index.html
|
|
166
|
+
z-app.<hash>.js
|
|
167
|
+
...
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Flags
|
|
171
|
+
|
|
172
|
+
| Flag | Short | Description |
|
|
173
|
+
| --- | --- | --- |
|
|
174
|
+
| `--out <path>` | `-o` | Custom output directory |
|
|
175
|
+
| `--html <file>` | — | Use a specific HTML file |
|
|
176
|
+
|
|
177
|
+
### What the Bundler Does
|
|
178
|
+
|
|
179
|
+
1. Reads `index.html` for the `<script type="module">` entry point
|
|
180
|
+
2. Resolves all `import` statements and topologically sorts dependencies
|
|
181
|
+
3. Strips `import`/`export` syntax, wraps in an IIFE
|
|
182
|
+
4. Embeds zQuery library and inlines `templateUrl` / `styleUrl` / `pages` files
|
|
183
|
+
5. Rewrites HTML, copies assets, produces hashed filenames
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Production Deployment
|
|
188
|
+
|
|
189
|
+
Deploy the `dist/server/` output. Configure your web server to rewrite non-file requests to `index.html`:
|
|
190
|
+
|
|
191
|
+
**Apache (.htaccess):**
|
|
192
|
+
```apache
|
|
193
|
+
RewriteEngine On
|
|
194
|
+
RewriteBase /
|
|
195
|
+
RewriteCond %{REQUEST_FILENAME} !-f
|
|
196
|
+
RewriteCond %{REQUEST_FILENAME} !-d
|
|
197
|
+
RewriteRule ^ index.html [L]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Nginx:**
|
|
201
|
+
```nginx
|
|
202
|
+
location / {
|
|
203
|
+
try_files $uri $uri/ /index.html;
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Sub-path deployment** (e.g. `/my-app/`): set `<base href="/my-app/">` in your HTML — the router auto-detects it.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Complete API at a Glance
|
|
212
|
+
|
|
213
|
+
| Namespace | Methods |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| `$()` | Single-element selector → `Element \| null` |
|
|
216
|
+
| `$.all()` | Collection selector → `ZQueryCollection` |
|
|
217
|
+
| `$.id` `$.class` `$.classes` `$.tag` `$.children` | Quick DOM refs |
|
|
218
|
+
| `$.create` | Element factory |
|
|
219
|
+
| `$.ready` `$.on` | DOM ready, global delegation |
|
|
220
|
+
| `$.fn` | Collection prototype (extend it) |
|
|
221
|
+
| `$.component` `$.mount` `$.mountAll` `$.getInstance` `$.destroy` `$.components` | Component system |
|
|
222
|
+
| `$.style` | Dynamically load global stylesheet file(s) at runtime |
|
|
223
|
+
| `$.router` `$.getRouter` | SPA router |
|
|
224
|
+
| `$.store` `$.getStore` | State management |
|
|
225
|
+
| `$.http` `$.get` `$.post` `$.put` `$.patch` `$.delete` | HTTP client |
|
|
226
|
+
| `$.reactive` `$.signal` `$.computed` `$.effect` | Reactive primitives |
|
|
227
|
+
| `$.debounce` `$.throttle` `$.pipe` `$.once` `$.sleep` | Function utils |
|
|
228
|
+
| `$.escapeHtml` `$.html` `$.trust` `$.uuid` `$.camelCase` `$.kebabCase` | String utils |
|
|
229
|
+
| `$.deepClone` `$.deepMerge` `$.isEqual` | Object utils |
|
|
230
|
+
| `$.param` `$.parseQuery` | URL utils |
|
|
231
|
+
| `$.storage` `$.session` | Storage wrappers |
|
|
232
|
+
| `$.bus` | Event bus |
|
|
233
|
+
| `$.version` | Library version |
|
|
234
|
+
| `$.noConflict` | Release `$` global |
|
|
235
|
+
|
|
236
|
+
| CLI Command | Description |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| `zquery create [dir]` | Scaffold a new project (index.html, scripts, styles) |
|
|
239
|
+
| `zquery dev [root]` | Dev server with live-reload (port 3100) |
|
|
240
|
+
| `zquery bundle [entry]` | Bundle app into a single IIFE file |
|
|
241
|
+
| `zquery build` | Build the zQuery library (`dist/zQuery.min.js`) |
|
|
242
|
+
| `zquery --help` | Show CLI usage |
|
|
243
|
+
|
|
244
|
+
For full method signatures, options, and examples, see **[API.md](API.md)**.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Editor Support
|
|
249
|
+
|
|
250
|
+
The official **[zQuery for VS Code](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code)** extension provides autocomplete, hover docs, HTML directive support, and 55+ code snippets for every API method and directive. Install it from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code) or search **"zQuery for VS Code"** in Extensions.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## License
|
|
255
|
+
|
|
256
|
+
MIT — [Anthony Wiedman / Molex](https://github.com/tonywied17)
|