zero-query 0.9.9 → 1.0.1
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 +34 -33
- package/cli/args.js +1 -1
- package/cli/commands/build.js +2 -2
- package/cli/commands/bundle.js +21 -18
- package/cli/commands/create.js +9 -2
- package/cli/commands/dev/devtools/index.js +1 -1
- package/cli/commands/dev/devtools/js/core.js +14 -14
- package/cli/commands/dev/devtools/js/elements.js +4 -4
- package/cli/commands/dev/devtools/js/stats.js +1 -1
- package/cli/commands/dev/devtools/styles.css +2 -2
- package/cli/commands/dev/index.js +2 -2
- package/cli/commands/dev/logger.js +1 -1
- package/cli/commands/dev/overlay.js +21 -14
- package/cli/commands/dev/server.js +5 -5
- package/cli/commands/dev/validator.js +7 -7
- package/cli/commands/dev/watcher.js +6 -6
- package/cli/help.js +3 -3
- package/cli/index.js +1 -1
- package/cli/scaffold/default/app/app.js +17 -18
- package/cli/scaffold/default/app/components/about.js +9 -9
- package/cli/scaffold/default/app/components/api-demo.js +6 -6
- package/cli/scaffold/default/app/components/contact-card.js +4 -4
- package/cli/scaffold/default/app/components/contacts/contacts.css +2 -2
- package/cli/scaffold/default/app/components/contacts/contacts.html +3 -3
- package/cli/scaffold/default/app/components/contacts/contacts.js +11 -11
- package/cli/scaffold/default/app/components/counter.js +8 -8
- package/cli/scaffold/default/app/components/home.js +13 -13
- package/cli/scaffold/default/app/components/not-found.js +1 -1
- package/cli/scaffold/default/app/components/playground/playground.css +1 -1
- package/cli/scaffold/default/app/components/playground/playground.html +11 -11
- package/cli/scaffold/default/app/components/playground/playground.js +11 -11
- package/cli/scaffold/default/app/components/todos.js +8 -8
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +1 -1
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +4 -4
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +7 -7
- package/cli/scaffold/default/app/routes.js +1 -1
- package/cli/scaffold/default/app/store.js +1 -1
- package/cli/scaffold/default/global.css +2 -2
- package/cli/scaffold/default/index.html +2 -2
- package/cli/scaffold/minimal/app/app.js +6 -7
- package/cli/scaffold/minimal/app/components/about.js +5 -5
- package/cli/scaffold/minimal/app/components/counter.js +6 -6
- package/cli/scaffold/minimal/app/components/home.js +8 -8
- package/cli/scaffold/minimal/app/components/not-found.js +1 -1
- package/cli/scaffold/minimal/app/routes.js +1 -1
- package/cli/scaffold/minimal/app/store.js +1 -1
- package/cli/scaffold/minimal/global.css +2 -2
- package/cli/scaffold/minimal/index.html +1 -1
- package/cli/scaffold/ssr/app/app.js +1 -2
- package/cli/scaffold/ssr/app/components/about.js +5 -5
- package/cli/scaffold/ssr/app/components/home.js +2 -2
- package/cli/scaffold/ssr/app/components/not-found.js +2 -2
- package/cli/scaffold/ssr/app/routes.js +1 -1
- package/cli/scaffold/ssr/global.css +3 -4
- package/cli/scaffold/ssr/index.html +2 -2
- package/cli/scaffold/ssr/server/index.js +26 -25
- package/cli/utils.js +6 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +508 -227
- package/dist/zquery.min.js +2 -2
- package/index.d.ts +16 -13
- package/index.js +7 -5
- package/package.json +3 -3
- package/src/component.js +64 -63
- package/src/core.js +15 -15
- package/src/diff.js +38 -38
- package/src/errors.js +17 -17
- package/src/expression.js +15 -17
- package/src/http.js +4 -4
- package/src/reactive.js +75 -9
- package/src/router.js +104 -24
- package/src/ssr.js +28 -28
- package/src/store.js +103 -21
- package/src/utils.js +64 -12
- package/tests/audit.test.js +143 -15
- package/tests/cli.test.js +20 -20
- package/tests/component.test.js +121 -121
- package/tests/core.test.js +56 -56
- package/tests/diff.test.js +42 -42
- package/tests/errors.test.js +5 -5
- package/tests/expression.test.js +58 -53
- package/tests/http.test.js +20 -20
- package/tests/reactive.test.js +185 -24
- package/tests/router.test.js +501 -74
- package/tests/ssr.test.js +15 -13
- package/tests/store.test.js +264 -23
- package/tests/test-minifier.js +153 -0
- package/tests/test-ssr.js +27 -0
- package/tests/utils.test.js +163 -26
- package/types/collection.d.ts +2 -2
- package/types/component.d.ts +5 -5
- package/types/errors.d.ts +3 -3
- package/types/http.d.ts +3 -3
- package/types/misc.d.ts +9 -9
- package/types/reactive.d.ts +25 -3
- package/types/router.d.ts +10 -6
- package/types/ssr.d.ts +2 -2
- package/types/store.d.ts +40 -5
- package/types/utils.d.ts +1 -1
package/README.md
CHANGED
|
@@ -15,22 +15,23 @@
|
|
|
15
15
|
|
|
16
16
|
</p>
|
|
17
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
|
|
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 ~91 KB minified browser bundle. Works out of the box with ES modules. An optional CLI bundler is available for single-file production builds.**
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
22
|
| Module | Highlights |
|
|
23
23
|
| --- | --- |
|
|
24
|
-
| **Components** | Reactive state, template literals, `@event` delegation (22 modifiers
|
|
25
|
-
| **Router** | History & hash mode, route params (`:id`), wildcards, guards (`beforeEach`/`afterEach`), lazy loading, `z-link` navigation
|
|
26
|
-
| **Directives** | `z-if`, `z-for`, `z-model`, `z-show`, `z-bind`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip` — 17 built-in template directives |
|
|
27
|
-
| **Reactive** | Deep proxy reactivity, Signals (`.value`, `.peek()`), computed values, effects (auto-tracked with dispose) |
|
|
28
|
-
| **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions, action history, snapshots |
|
|
24
|
+
| **Components** | Reactive state, template literals, `@event` delegation (22 modifiers - key filters, system keys, `.outside`, timing, and more), `z-model` two-way binding (with `z-trim`, `z-number`, `z-lazy`, `z-debounce`, `z-uppercase`, `z-lowercase`), computed properties, watch callbacks, slot-based content projection, directives (`z-if`/`z-else-if`/`z-else`, `z-for`, `z-show`, `z-bind`/`:attr`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip`), DOM morphing engine with LIS-based keyed reconciliation (no innerHTML rebuild), CSP-safe expression evaluation with AST caching, scoped styles, external templates (`templateUrl` / `styleUrl`), lifecycle hooks, auto-injected base styles |
|
|
25
|
+
| **Router** | History & hash mode, route params (`:id`), wildcards, guards (`beforeEach`/`afterEach`), lazy loading, `z-link` navigation with `z-link-params`, `z-to-top` scroll modifier (`instant`/`smooth`), `z-active-route` active-link class directive, `<z-outlet>` declarative mount point, sub-route history substates (`pushSubstate`/`onSubstate`) |
|
|
26
|
+
| **Directives** | `z-if`, `z-else-if`, `z-else`, `z-for`, `z-model`, `z-show`, `z-bind`/`:attr`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip`, `@event`/`z-on` — 17 built-in template directives |
|
|
27
|
+
| **Reactive** | Deep proxy reactivity, Signals (`.value`, `.peek()`), computed values, effects (auto-tracked with dispose), `batch()` for deferred notifications, `untracked()` for dependency-free reads |
|
|
28
|
+
| **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions, `batch()` grouped mutations, `checkpoint()`/`undo()`/`redo()` with configurable stack, action history, snapshots |
|
|
29
29
|
| **Selectors & DOM** | jQuery-like chainable selectors, traversal, DOM manipulation, events, animation |
|
|
30
30
|
| **HTTP** | Fetch wrapper with auto-JSON, interceptors (with unsubscribe & clear), HEAD requests, parallel requests (`http.all`), config inspection (`getConfig`), timeout/abort, base URL |
|
|
31
|
-
| **Utils** | debounce, throttle, pipe, once, sleep, memoize, escapeHtml, stripHtml, uuid, capitalize, truncate, range, chunk, groupBy, unique, pick, omit, getPath/setPath, isEmpty, clamp, retry, timeout, deepClone, deepMerge, storage/session wrappers, event bus |
|
|
31
|
+
| **Utils** | debounce, throttle, pipe, once, sleep, memoize (LRU), escapeHtml, stripHtml, uuid, capitalize, truncate, range, chunk, groupBy, unique, pick, omit, getPath/setPath, isEmpty, clamp, retry, timeout, deepClone (enhanced fallback), deepMerge (prototype-pollution safe), storage/session wrappers, event bus |
|
|
32
|
+
| **Security** | XSS-safe template expressions (`{{}}` auto-escaping), sandboxed expression evaluator (blocks `window`, `Function`, `eval`, `RegExp`, `Error`, prototype chains), prototype pollution prevention in `deepMerge`/`setPath`, `z-link` protocol validation, SSR error sanitization |
|
|
32
33
|
| **Dev Tools** | CLI dev server with live-reload, CSS hot-swap, full-screen error overlay, floating toolbar, dark-themed inspector panel (Router view, DOM tree, network log, component viewer, performance dashboard), fetch interceptor, render instrumentation, CLI bundler for single-file production builds |
|
|
33
|
-
| **SSR** | Server-side rendering to HTML strings in Node.js
|
|
34
|
+
| **SSR** | Server-side rendering to HTML strings in Node.js - `createSSRApp()`, `renderToString()`, `renderPage()` with SEO/Open Graph support, `renderBatch()` for parallel rendering, fragment mode, hydration markers, graceful error handling, `escapeHtml()` utility |
|
|
34
35
|
|
|
35
36
|
---
|
|
36
37
|
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
|
|
39
40
|
### Recommended: CLI Dev Server
|
|
40
41
|
|
|
41
|
-
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
|
|
42
|
+
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.
|
|
42
43
|
|
|
43
44
|
```bash
|
|
44
45
|
# Install (per-project or globally)
|
|
@@ -58,25 +59,25 @@ The `create` command generates a ready-to-run project with a sidebar layout, rou
|
|
|
58
59
|
|
|
59
60
|
#### Error Overlay
|
|
60
61
|
|
|
61
|
-
The dev server includes a **full-screen error overlay** that surfaces errors directly in the browser
|
|
62
|
+
The dev server includes a **full-screen error overlay** that surfaces errors directly in the browser - similar to Vite or Angular:
|
|
62
63
|
|
|
63
|
-
- **Syntax errors**
|
|
64
|
-
- **Runtime errors**
|
|
64
|
+
- **Syntax errors** - JS files are validated on every save *before* the reload is triggered. If a syntax error is found the page stays intact and a dark overlay appears with the error message, file path, line:column, and a code frame pointing to the exact location.
|
|
65
|
+
- **Runtime errors** - uncaught exceptions and unhandled promise rejections are captured and displayed in the same overlay with a cleaned-up stack trace.
|
|
65
66
|
- The overlay **auto-clears** when you fix the error and save. Press `Esc` or click `×` to dismiss manually.
|
|
66
67
|
|
|
67
68
|
#### Floating Toolbar & Inspector
|
|
68
69
|
|
|
69
|
-
A compact expandable toolbar appears in the bottom-right corner. In its **collapsed** state it shows live render and request counters. Click the chevron to **expand** and reveal the route indicator (color-coded by the last navigation event
|
|
70
|
+
A compact expandable toolbar appears in the bottom-right corner. In its **collapsed** state it shows live render and request counters. Click the chevron to **expand** and reveal the route indicator (color-coded by the last navigation event - navigate, pop, replace, hashchange, substate), registered component count, and DOM element count. Click any stat to open a **dark-themed DevTools inspector** as a popup - or visit `http://localhost:<port>/_devtools` for a standalone split-view panel with five tabs: **Router** (live route state, guards, history timeline), **Components** (live state cards), **Performance** (render timeline with timing metrics), **Network** (fetch log with JSON viewer), and **Elements** (live DOM tree with component badges, source viewer, expand/collapse).
|
|
70
71
|
|
|
71
72
|
### Alternative: Manual Setup (No npm)
|
|
72
73
|
|
|
73
|
-
If you prefer **zero tooling**, download `dist/zquery.min.js` from the [dist/ folder](https://github.com/tonywied17/zero-query/tree/main/dist) and drop it into your project root or `assets/scripts/`. Then open `index.html` directly in a browser
|
|
74
|
+
If you prefer **zero tooling**, download `dist/zquery.min.js` from the [dist/ folder](https://github.com/tonywied17/zero-query/tree/main/dist) and drop it into your project root or `assets/scripts/`. Then open `index.html` directly in a browser - no Node.js required.
|
|
74
75
|
|
|
75
76
|
```bash
|
|
76
77
|
git clone https://github.com/tonywied17/zero-query.git
|
|
77
78
|
cd zero-query
|
|
78
79
|
npx zquery build
|
|
79
|
-
# → dist/zquery.min.js (~
|
|
80
|
+
# → dist/zquery.min.js (~91 KB)
|
|
80
81
|
```
|
|
81
82
|
|
|
82
83
|
### Include in HTML
|
|
@@ -96,7 +97,7 @@ npx zquery build
|
|
|
96
97
|
<a z-link="/">Home</a>
|
|
97
98
|
<a z-link="/about">About</a>
|
|
98
99
|
</nav>
|
|
99
|
-
<
|
|
100
|
+
<z-outlet></z-outlet>
|
|
100
101
|
</body>
|
|
101
102
|
</html>
|
|
102
103
|
```
|
|
@@ -110,7 +111,7 @@ import './components/about.js';
|
|
|
110
111
|
import './components/contacts/contacts.js';
|
|
111
112
|
import { routes } from './routes.js';
|
|
112
113
|
|
|
113
|
-
$.router({
|
|
114
|
+
$.router({ routes, fallback: 'not-found' });
|
|
114
115
|
```
|
|
115
116
|
|
|
116
117
|
### Define a Component
|
|
@@ -130,7 +131,7 @@ $.component('home-page', {
|
|
|
130
131
|
});
|
|
131
132
|
```
|
|
132
133
|
|
|
133
|
-
That's it
|
|
134
|
+
That's it - a fully working SPA with the dev server's live-reload.
|
|
134
135
|
|
|
135
136
|
---
|
|
136
137
|
|
|
@@ -194,7 +195,7 @@ my-app/ ← SSR scaffold (npx zquery create my-app --ss
|
|
|
194
195
|
index.html ← client HTML shell
|
|
195
196
|
global.css
|
|
196
197
|
app/
|
|
197
|
-
app.js ← client entry
|
|
198
|
+
app.js ← client entry - registers shared components
|
|
198
199
|
routes.js ← shared route definitions
|
|
199
200
|
components/
|
|
200
201
|
home.js ← shared component (SSR + client)
|
|
@@ -205,12 +206,12 @@ my-app/ ← SSR scaffold (npx zquery create my-app --ss
|
|
|
205
206
|
assets/
|
|
206
207
|
```
|
|
207
208
|
|
|
208
|
-
Components in `app/components/` export plain definition objects
|
|
209
|
+
Components in `app/components/` export plain definition objects - the client registers them with `$.component()`, the server with `app.component()`. The `--ssr` flag handles everything automatically - installs dependencies, starts the server at `http://localhost:3000`, and opens the browser.
|
|
209
210
|
|
|
210
211
|
- One component per file inside `components/`.
|
|
211
212
|
- Names **must contain a hyphen** (Web Component convention): `home-page`, `app-counter`, etc.
|
|
212
213
|
- Components with external templates or styles can use a subfolder (e.g. `contacts/contacts.js` + `contacts.html` + `contacts.css`).
|
|
213
|
-
- `app.js` is the single entry point
|
|
214
|
+
- `app.js` is the single entry point - import components, create the store, and boot the router.
|
|
214
215
|
- `global.css` lives next to `index.html` for easy access; the bundler hashes it into `global.<hash>.min.css` for production.
|
|
215
216
|
- `assets/` holds static files that get copied to `dist/` as-is.
|
|
216
217
|
|
|
@@ -218,7 +219,7 @@ Components in `app/components/` export plain definition objects — the client r
|
|
|
218
219
|
|
|
219
220
|
## CLI Bundler
|
|
220
221
|
|
|
221
|
-
The CLI compiles your entire app
|
|
222
|
+
The CLI compiles your entire app - ES modules, the library, external templates, and assets - into a **single production-ready bundle**. It outputs two builds in one step: a `server/` build for deploying to any web server, and a `local/` build that works straight from disk. No config, no flags - just point it at your app.
|
|
222
223
|
|
|
223
224
|
```bash
|
|
224
225
|
# Auto-detect entry from any .html with a module script
|
|
@@ -240,7 +241,7 @@ dist/
|
|
|
240
241
|
z-app.<hash>.min.js
|
|
241
242
|
global.<hash>.min.css
|
|
242
243
|
assets/
|
|
243
|
-
local/ ← open from disk (file://)
|
|
244
|
+
local/ ← open from disk (file://) - no server needed
|
|
244
245
|
index.html
|
|
245
246
|
z-app.<hash>.min.js
|
|
246
247
|
...
|
|
@@ -257,11 +258,11 @@ dist/
|
|
|
257
258
|
|
|
258
259
|
### What the Bundler Does
|
|
259
260
|
|
|
260
|
-
1. **Entry detection**
|
|
261
|
-
1. **HTML files**
|
|
262
|
-
2. **Module scripts within HTML**
|
|
263
|
-
3. **JS file scan**
|
|
264
|
-
4. **Convention fallbacks**
|
|
261
|
+
1. **Entry detection** - a strict precedence order ensures the correct file is chosen:
|
|
262
|
+
1. **HTML files** - `index.html` is checked first, then other `.html` files (root + one level deep).
|
|
263
|
+
2. **Module scripts within HTML** - within each HTML file, a `<script type="module">` whose `src` resolves to `app.js` wins; otherwise the first module script tag is used.
|
|
264
|
+
3. **JS file scan** - if no HTML match, JS files (up to 2 levels deep) are scanned in two passes: first for `$.router(` (the canonical app entry point), then for `$.mount(`, `$.store(`, or `mountAll(`.
|
|
265
|
+
4. **Convention fallbacks** - `app/app.js`, `scripts/app.js`, `src/app.js`, `js/app.js`, `app.js`, `main.js`.
|
|
265
266
|
2. Resolves all `import` statements and topologically sorts dependencies
|
|
266
267
|
3. Strips `import`/`export` syntax, wraps in an IIFE
|
|
267
268
|
4. Embeds zQuery library and inlines `templateUrl` / `styleUrl` files
|
|
@@ -289,7 +290,7 @@ location / {
|
|
|
289
290
|
}
|
|
290
291
|
```
|
|
291
292
|
|
|
292
|
-
**Sub-path deployment** (e.g. `/my-app/`): set `<base href="/my-app/">` in your HTML
|
|
293
|
+
**Sub-path deployment** (e.g. `/my-app/`): set `<base href="/my-app/">` in your HTML - the router auto-detects it.
|
|
293
294
|
|
|
294
295
|
---
|
|
295
296
|
|
|
@@ -298,13 +299,13 @@ location / {
|
|
|
298
299
|
| Namespace | Methods |
|
|
299
300
|
| --- | --- |
|
|
300
301
|
| `$()` | Chainable selector → `ZQueryCollection` (CSS selectors, elements, NodeLists, HTML strings) |
|
|
301
|
-
| `$.all()` | Alias for `$()`
|
|
302
|
+
| `$.all()` | Alias for `$()` - identical behavior |
|
|
302
303
|
| `$.id` `$.class` `$.classes` `$.tag` `$.name` `$.children` `$.qs` `$.qsa` | Quick DOM refs |
|
|
303
304
|
| `$.create` | Element factory |
|
|
304
305
|
| `$.ready` `$.on` `$.off` | DOM ready, global event delegation & direct listeners |
|
|
305
306
|
| `$.fn` | Collection prototype (extend it) |
|
|
306
307
|
| `$.component` `$.mount` `$.mountAll` `$.getInstance` `$.destroy` `$.components` `$.prefetch` | Component system |
|
|
307
|
-
| `$.morph` `$.morphElement` | DOM morphing engine
|
|
308
|
+
| `$.morph` `$.morphElement` | DOM morphing engine - LIS-based keyed reconciliation, `isEqualNode()` bail-outs, `z-skip` opt-out. Patches existing DOM to match new HTML without destroying unchanged nodes. Auto-key detection (`id`, `data-id`, `data-key`) - no `z-key` required. `$().html()` and `$().replaceWith()` auto-morph existing content; `$().morph()` for explicit morph |
|
|
308
309
|
| `$.safeEval` | CSP-safe expression evaluator (replaces `eval` / `new Function`) |
|
|
309
310
|
| `$.style` | Dynamically load global stylesheet file(s) at runtime |
|
|
310
311
|
| `$.router` `$.getRouter` | SPA router |
|
|
@@ -321,7 +322,7 @@ location / {
|
|
|
321
322
|
| `$.storage` `$.session` | Storage wrappers |
|
|
322
323
|
| `$.EventBus` `$.bus` | Event bus |
|
|
323
324
|
| `$.onError` `$.ZQueryError` `$.ErrorCode` `$.guardCallback` `$.guardAsync` `$.formatError` `$.validate` | Error handling |
|
|
324
|
-
| `$.version` | Library version |\n| `$.libSize` | Minified bundle size string (e.g. `\"~
|
|
325
|
+
| `$.version` | Library version |\n| `$.libSize` | Minified bundle size string (e.g. `\"~91 KB\"`) |
|
|
325
326
|
| `$.unitTests` | Build-time test results `{ passed, failed, total, suites, duration, ok }` |
|
|
326
327
|
| `$.meta` | Build metadata (populated by CLI bundler) |
|
|
327
328
|
| `$.noConflict` | Release `$` global |
|
|
@@ -346,4 +347,4 @@ The official **[zQuery for VS Code](https://marketplace.visualstudio.com/items?i
|
|
|
346
347
|
|
|
347
348
|
## License
|
|
348
349
|
|
|
349
|
-
MIT
|
|
350
|
+
MIT - [Anthony Wiedman / Molex](https://github.com/tonywied17)
|
package/cli/args.js
CHANGED
package/cli/commands/build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cli/commands/build.js
|
|
2
|
+
* cli/commands/build.js - library build command
|
|
3
3
|
*
|
|
4
4
|
* Concatenates the zQuery source modules into dist/zquery.js and
|
|
5
5
|
* dist/zquery.min.js.
|
|
@@ -208,7 +208,7 @@ function buildLibrary() {
|
|
|
208
208
|
const zipBuf = buildZip(entries);
|
|
209
209
|
const zipPath = path.join(DIST, 'zquery.dist.zip');
|
|
210
210
|
fs.writeFileSync(zipPath, zipBuf);
|
|
211
|
-
console.log(` ✓ dist/zquery.dist.zip (${sizeKB(zipBuf)} KB)
|
|
211
|
+
console.log(` ✓ dist/zquery.dist.zip (${sizeKB(zipBuf)} KB) - ${entries.length} files`);
|
|
212
212
|
|
|
213
213
|
return { DIST, OUT_FILE, MIN_FILE };
|
|
214
214
|
}
|
package/cli/commands/bundle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cli/commands/bundle.js
|
|
2
|
+
* cli/commands/bundle.js - app bundler command
|
|
3
3
|
*
|
|
4
4
|
* Walks the ES module import graph starting from an entry file,
|
|
5
5
|
* strips import/export syntax, concatenates everything into a single
|
|
@@ -59,7 +59,7 @@ function extractImports(code) {
|
|
|
59
59
|
return specifiers;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/** Walk the import graph
|
|
62
|
+
/** Walk the import graph - topological sort (leaves first). */
|
|
63
63
|
function walkImportGraph(entry) {
|
|
64
64
|
const visited = new Set();
|
|
65
65
|
const order = [];
|
|
@@ -119,7 +119,7 @@ function rewriteResourceUrls(code, filePath, projectRoot) {
|
|
|
119
119
|
(match, prefix, quote, url) => {
|
|
120
120
|
if (url.startsWith('/') || url.includes('://')) return match;
|
|
121
121
|
const abs = path.resolve(fileDir, url);
|
|
122
|
-
// Only rewrite if the file actually exists
|
|
122
|
+
// Only rewrite if the file actually exists - avoids mangling code examples
|
|
123
123
|
if (!fs.existsSync(abs)) return match;
|
|
124
124
|
const rel = path.relative(projectRoot, abs).replace(/\\/g, '/');
|
|
125
125
|
return `${prefix}${quote}${rel}${quote}`;
|
|
@@ -128,7 +128,7 @@ function rewriteResourceUrls(code, filePath, projectRoot) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* Minify HTML for inlining
|
|
131
|
+
* Minify HTML for inlining - strips indentation and collapses whitespace
|
|
132
132
|
* between tags. Preserves content inside <pre>, <code>, and <textarea>
|
|
133
133
|
* blocks verbatim so syntax-highlighted code samples survive.
|
|
134
134
|
*/
|
|
@@ -164,7 +164,7 @@ function minifyHTML(html) {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
|
-
* Minify CSS for inlining
|
|
167
|
+
* Minify CSS for inlining - strips comments, collapses whitespace,
|
|
168
168
|
* removes unnecessary spaces around punctuation.
|
|
169
169
|
*/
|
|
170
170
|
function minifyCSS(css) {
|
|
@@ -172,8 +172,10 @@ function minifyCSS(css) {
|
|
|
172
172
|
css = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
173
173
|
// Collapse whitespace
|
|
174
174
|
css = css.replace(/\s{2,}/g, ' ');
|
|
175
|
-
// Remove spaces around { } :
|
|
176
|
-
css = css.replace(/\s*([{}
|
|
175
|
+
// Remove spaces around { } ; , (but NOT : — pseudo-selectors like :not() need the preceding space)
|
|
176
|
+
css = css.replace(/\s*([{};,])\s*/g, '$1');
|
|
177
|
+
// Remove space after : (safe in both selectors and declarations)
|
|
178
|
+
css = css.replace(/:\s+/g, ':');
|
|
177
179
|
// Remove trailing semicolons before }
|
|
178
180
|
css = css.replace(/;}/g, '}');
|
|
179
181
|
return css.trim();
|
|
@@ -357,7 +359,8 @@ function _collapseTemplateCSS(tpl) {
|
|
|
357
359
|
let t = segments[s].val;
|
|
358
360
|
t = t.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
359
361
|
t = t.replace(/\s{2,}/g, ' ');
|
|
360
|
-
t = t.replace(/\s*([{}
|
|
362
|
+
t = t.replace(/\s*([{};,])\s*/g, '$1');
|
|
363
|
+
t = t.replace(/:\s+/g, ':');
|
|
361
364
|
t = t.replace(/;}/g, '}');
|
|
362
365
|
segments[s].val = t;
|
|
363
366
|
}
|
|
@@ -396,7 +399,7 @@ function collectInlineResources(files, projectRoot) {
|
|
|
396
399
|
inlineMap[relKey] = fs.readFileSync(tmplPath, 'utf-8');
|
|
397
400
|
}
|
|
398
401
|
} else if (/templateUrl\s*:/.test(code)) {
|
|
399
|
-
// Dynamic templateUrl (e.g. Object.fromEntries, computed map)
|
|
402
|
+
// Dynamic templateUrl (e.g. Object.fromEntries, computed map) -
|
|
400
403
|
// inline all .html files in the component's directory tree so
|
|
401
404
|
// the runtime __zqInline lookup can resolve them by suffix.
|
|
402
405
|
(function scanHtml(dir) {
|
|
@@ -412,7 +415,7 @@ function collectInlineResources(files, projectRoot) {
|
|
|
412
415
|
scanHtml(full);
|
|
413
416
|
}
|
|
414
417
|
}
|
|
415
|
-
} catch { /* permission error
|
|
418
|
+
} catch { /* permission error - skip */ }
|
|
416
419
|
})(fileDir);
|
|
417
420
|
}
|
|
418
421
|
}
|
|
@@ -429,7 +432,7 @@ function collectInlineResources(files, projectRoot) {
|
|
|
429
432
|
/**
|
|
430
433
|
* Auto-detect the app entry point.
|
|
431
434
|
*
|
|
432
|
-
* Strategy
|
|
435
|
+
* Strategy - ordered by precedence (first match wins):
|
|
433
436
|
* 1. HTML discovery: index.html first, then other .html files
|
|
434
437
|
* (root level + one directory deep).
|
|
435
438
|
* 2. Within each HTML file, prefer a module <script> whose src
|
|
@@ -456,7 +459,7 @@ function detectEntry(projectRoot) {
|
|
|
456
459
|
htmlFiles.push(path.join(sub, child.name));
|
|
457
460
|
}
|
|
458
461
|
}
|
|
459
|
-
} catch { /* permission error
|
|
462
|
+
} catch { /* permission error - skip */ }
|
|
460
463
|
}
|
|
461
464
|
}
|
|
462
465
|
|
|
@@ -490,8 +493,8 @@ function detectEntry(projectRoot) {
|
|
|
490
493
|
}
|
|
491
494
|
|
|
492
495
|
// 2. Search JS files for entry-point patterns.
|
|
493
|
-
// Pass 1
|
|
494
|
-
// Pass 2
|
|
496
|
+
// Pass 1 - $.router (the canonical entry point).
|
|
497
|
+
// Pass 2 - $.mount, $.store, mountAll (component-level, lower confidence).
|
|
495
498
|
const routerRe = /\$\.router\s*\(/;
|
|
496
499
|
const otherRe = /\$\.(mount|store)\s*\(|mountAll\s*\(/;
|
|
497
500
|
|
|
@@ -541,8 +544,8 @@ function detectEntry(projectRoot) {
|
|
|
541
544
|
/**
|
|
542
545
|
* Rewrite an HTML file to replace the module <script> with the bundle.
|
|
543
546
|
* Produces two variants:
|
|
544
|
-
* server/index.html
|
|
545
|
-
* local/index.html
|
|
547
|
+
* server/index.html - <base href="/"> for SPA deep routes
|
|
548
|
+
* local/index.html - relative paths for file:// access
|
|
546
549
|
*/
|
|
547
550
|
function rewriteHtml(projectRoot, htmlRelPath, bundleFile, includeLib, bundledFiles, serverDir, localDir, globalCssOrigHref, globalCssHash) {
|
|
548
551
|
const htmlPath = path.resolve(projectRoot, htmlRelPath);
|
|
@@ -737,7 +740,7 @@ function bundleApp() {
|
|
|
737
740
|
const minimal = flag('minimal', 'm');
|
|
738
741
|
const globalCssOverride = option('global-css', null, null);
|
|
739
742
|
|
|
740
|
-
// Entry point
|
|
743
|
+
// Entry point - positional arg (directory or file) or auto-detection
|
|
741
744
|
let entry = null;
|
|
742
745
|
let targetDir = null;
|
|
743
746
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -984,7 +987,7 @@ function bundleApp() {
|
|
|
984
987
|
console.log(`\n ✓ ${minBase} (${sizeKB(fs.readFileSync(minFile))} KB)`);
|
|
985
988
|
|
|
986
989
|
// ------------------------------------------------------------------
|
|
987
|
-
// Global CSS bundling
|
|
990
|
+
// Global CSS bundling - extract from index.html <link> or --global-css
|
|
988
991
|
// ------------------------------------------------------------------
|
|
989
992
|
let globalCssHash = null;
|
|
990
993
|
let globalCssOrigHref = null;
|
package/cli/commands/create.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// cli/commands/create.js
|
|
1
|
+
// cli/commands/create.js - scaffold a new zQuery project
|
|
2
2
|
//
|
|
3
3
|
// Templates live in cli/scaffold/<variant>/ (default or minimal).
|
|
4
4
|
// Reads template files, replaces {{NAME}} with the project name,
|
|
@@ -48,7 +48,7 @@ function createProject(args) {
|
|
|
48
48
|
process.exit(1);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
console.log(`\n zQuery
|
|
51
|
+
console.log(`\n zQuery - Create Project (${variant})\n`);
|
|
52
52
|
console.log(` Scaffolding into ${target}\n`);
|
|
53
53
|
|
|
54
54
|
// Resolve the scaffold template directory for the chosen variant
|
|
@@ -89,6 +89,13 @@ function createProject(args) {
|
|
|
89
89
|
process.exit(1);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// Copy zquery.min.js into the project root so the SSR server can serve it
|
|
93
|
+
const zqMin = path.join(target, 'node_modules', 'zero-query', 'dist', 'zquery.min.js');
|
|
94
|
+
if (fs.existsSync(zqMin)) {
|
|
95
|
+
fs.copyFileSync(zqMin, path.join(target, 'zquery.min.js'));
|
|
96
|
+
console.log(` ✓ zquery.min.js`);
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
console.log(`\n Starting SSR server...\n`);
|
|
93
100
|
const child = spawn('node', ['server/index.js'], { cwd: target, stdio: 'inherit' });
|
|
94
101
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cli/commands/dev/devtools/index.js
|
|
2
|
+
* cli/commands/dev/devtools/index.js - DevTools HTML assembler
|
|
3
3
|
*
|
|
4
4
|
* Reads CSS, HTML, and JS partials from this folder and concatenates them
|
|
5
5
|
* into a single self-contained HTML page served at /_devtools.
|
|
@@ -39,7 +39,7 @@ function formatTime(ts) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// ===================================================================
|
|
42
|
-
// Connection
|
|
42
|
+
// Connection - find target window (opener popup → iframe fallback)
|
|
43
43
|
// ===================================================================
|
|
44
44
|
function isConnected() {
|
|
45
45
|
try { return targetWin && (targetWin === window.opener ? !targetWin.closed : true) && targetWin.document; }
|
|
@@ -48,11 +48,11 @@ function isConnected() {
|
|
|
48
48
|
|
|
49
49
|
function detectMode() {
|
|
50
50
|
if (window.opener) {
|
|
51
|
-
// Opened as popup
|
|
51
|
+
// Opened as popup - hide iframe, use opener
|
|
52
52
|
mode = 'popup';
|
|
53
53
|
targetWin = window.opener;
|
|
54
54
|
} else {
|
|
55
|
-
// Standalone tab
|
|
55
|
+
// Standalone tab - embed app in iframe
|
|
56
56
|
mode = 'split-h';
|
|
57
57
|
targetWin = null; // will set from iframe.contentWindow
|
|
58
58
|
}
|
|
@@ -110,11 +110,11 @@ var tbDragging = false;
|
|
|
110
110
|
|
|
111
111
|
var isH = mode === 'split-h';
|
|
112
112
|
if (isH) {
|
|
113
|
-
// Vertical column divider
|
|
113
|
+
// Vertical column divider - drag toolbar up/down
|
|
114
114
|
startPos = e.clientY;
|
|
115
115
|
startOffset = parseInt(toolbar.style.top, 10) || toolbar.offsetTop;
|
|
116
116
|
} else {
|
|
117
|
-
// Horizontal row divider
|
|
117
|
+
// Horizontal row divider - drag toolbar left/right
|
|
118
118
|
startPos = e.clientX;
|
|
119
119
|
startOffset = parseInt(toolbar.style.left, 10) || toolbar.offsetLeft;
|
|
120
120
|
}
|
|
@@ -180,7 +180,7 @@ divider.addEventListener('mousedown', function(e) {
|
|
|
180
180
|
});
|
|
181
181
|
|
|
182
182
|
// ===================================================================
|
|
183
|
-
// Refresh button
|
|
183
|
+
// Refresh button - reload the embedded iframe
|
|
184
184
|
// ===================================================================
|
|
185
185
|
document.getElementById('btn-refresh').addEventListener('click', function(e) {
|
|
186
186
|
e.stopPropagation();
|
|
@@ -192,7 +192,7 @@ document.getElementById('btn-refresh').addEventListener('click', function(e) {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
// ===================================================================
|
|
195
|
-
// Viewport preset buttons
|
|
195
|
+
// Viewport preset buttons - resize browser pane to mobile/tablet/desktop
|
|
196
196
|
// ===================================================================
|
|
197
197
|
var viewportBtns = document.querySelectorAll('.viewport-btn');
|
|
198
198
|
|
|
@@ -219,10 +219,10 @@ viewportBtns.forEach(function(btn) {
|
|
|
219
219
|
var total = rootEl.offsetWidth;
|
|
220
220
|
|
|
221
221
|
if (targetWidth === 0) {
|
|
222
|
-
// Desktop
|
|
222
|
+
// Desktop - reset to default CSS proportions
|
|
223
223
|
rootEl.style.gridTemplateColumns = '';
|
|
224
224
|
} else {
|
|
225
|
-
// Mobile/Tablet
|
|
225
|
+
// Mobile/Tablet - set iframe column to exact pixel width
|
|
226
226
|
var pct = Math.min(85, Math.max(15, (targetWidth / total) * 100));
|
|
227
227
|
rootEl.style.gridTemplateColumns = pct.toFixed(1) + '% 4px 1fr';
|
|
228
228
|
}
|
|
@@ -233,7 +233,7 @@ viewportBtns.forEach(function(btn) {
|
|
|
233
233
|
});
|
|
234
234
|
|
|
235
235
|
// ===================================================================
|
|
236
|
-
// Route indicator
|
|
236
|
+
// Route indicator - toggle label showing current path + hash
|
|
237
237
|
// ===================================================================
|
|
238
238
|
var routeBtn = document.getElementById('btn-route');
|
|
239
239
|
var routeLabel = document.getElementById('route-label');
|
|
@@ -266,7 +266,7 @@ setInterval(function() {
|
|
|
266
266
|
}, 500);
|
|
267
267
|
|
|
268
268
|
// ===================================================================
|
|
269
|
-
// init
|
|
269
|
+
// init - connect to target window (popup or iframe)
|
|
270
270
|
// ===================================================================
|
|
271
271
|
function init() {
|
|
272
272
|
// If popup mode, targetWin is already set
|
|
@@ -310,7 +310,7 @@ function init() {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
// ===================================================================
|
|
313
|
-
// connectToTarget
|
|
313
|
+
// connectToTarget - read existing data, start listeners, periodic sync
|
|
314
314
|
// ===================================================================
|
|
315
315
|
function connectToTarget() {
|
|
316
316
|
|
|
@@ -372,7 +372,7 @@ function connectToTarget() {
|
|
|
372
372
|
// Periodic refresh for components + perf (fast when tab is visible)
|
|
373
373
|
setInterval(function() {
|
|
374
374
|
if (!isConnected()) {
|
|
375
|
-
// Retry connection
|
|
375
|
+
// Retry connection - opener may be mid-mutation, not truly gone
|
|
376
376
|
try {
|
|
377
377
|
if (mode === 'popup' && window.opener && !window.opener.closed) {
|
|
378
378
|
targetWin = window.opener;
|
|
@@ -389,7 +389,7 @@ function connectToTarget() {
|
|
|
389
389
|
return;
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
|
-
// Keep targetDoc fresh
|
|
392
|
+
// Keep targetDoc fresh - the opener may have reloaded (live-reload)
|
|
393
393
|
try {
|
|
394
394
|
var freshDoc = targetWin.document;
|
|
395
395
|
if (freshDoc !== targetDoc) {
|
|
@@ -72,7 +72,7 @@ function buildNode(node, depth) {
|
|
|
72
72
|
var nodePath = getNodePath(node);
|
|
73
73
|
var hasChildren = false;
|
|
74
74
|
var childNodes = node.childNodes;
|
|
75
|
-
// style/script content is shown inline via the peek button
|
|
75
|
+
// style/script content is shown inline via the peek button - treat as leaf
|
|
76
76
|
if (tag !== 'style' && tag !== 'script') {
|
|
77
77
|
for (var i = 0; i < childNodes.length; i++) {
|
|
78
78
|
var cn = childNodes[i];
|
|
@@ -238,7 +238,7 @@ function buildNode(node, depth) {
|
|
|
238
238
|
toggleNested(childContainer, 0);
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
-
// Row click
|
|
241
|
+
// Row click - select element (not toggle)
|
|
242
242
|
row.addEventListener('click', function(e) {
|
|
243
243
|
// Don't select when clicking toggle arrow, badge, action buttons, or peek
|
|
244
244
|
if (e.target.closest('.tree-toggle') || e.target.closest('.tree-badge') || e.target.closest('.tree-action') || e.target.closest('.tree-peek')) return;
|
|
@@ -351,7 +351,7 @@ function showDetail(node) {
|
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
// ===================================================================
|
|
354
|
-
// MutationObserver
|
|
354
|
+
// MutationObserver - watch target document for live DOM changes
|
|
355
355
|
// ===================================================================
|
|
356
356
|
function startObserver() {
|
|
357
357
|
if (!targetDoc || observer) return;
|
|
@@ -394,7 +394,7 @@ function startObserver() {
|
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
if (!dominated) return; // All mutations were from devtools highlight
|
|
397
|
+
if (!dominated) return; // All mutations were from devtools highlight - skip rebuild
|
|
398
398
|
|
|
399
399
|
// Debounce tree rebuild
|
|
400
400
|
clearTimeout(startObserver._timer);
|
|
@@ -9,7 +9,7 @@ function updateStats() {
|
|
|
9
9
|
document.getElementById('morph-count').textContent = morphCount + ' renders';
|
|
10
10
|
document.getElementById('req-count').textContent = requests.length + ' requests';
|
|
11
11
|
|
|
12
|
-
// Route stat
|
|
12
|
+
// Route stat - show current path
|
|
13
13
|
try {
|
|
14
14
|
var router = targetWin && targetWin.$ && targetWin.$.getRouter();
|
|
15
15
|
var routeStat = document.getElementById('route-stat');
|
|
@@ -20,7 +20,7 @@ button{font:inherit;cursor:pointer;border:none;background:none;color:inherit}
|
|
|
20
20
|
.divider:hover{background:var(--accent)}
|
|
21
21
|
|
|
22
22
|
/* Divider toolbar */
|
|
23
|
-
/* split-h: divider is a tall 4px column
|
|
23
|
+
/* split-h: divider is a tall 4px column - toolbar sits to its LEFT, overlaying the browser */
|
|
24
24
|
.divider-toolbar{position:absolute;display:flex;flex-direction:column;align-items:center;gap:2px;
|
|
25
25
|
padding:4px 2px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;
|
|
26
26
|
z-index:11;top:12px;right:100%;margin-right:2px;cursor:grab}
|
|
@@ -36,7 +36,7 @@ border:1px solid var(--border);border-radius:4px;font-size:11px;color:var(--acce
|
|
|
36
36
|
max-width:0;overflow:hidden;opacity:0;transition:max-width .25s ease,opacity .2s ease,padding .2s ease;
|
|
37
37
|
padding:0;pointer-events:none;line-height:22px;text-overflow:ellipsis}
|
|
38
38
|
.route-label.open{max-width:50vw;opacity:1;padding:2px 8px;pointer-events:auto}
|
|
39
|
-
/* split-v: divider is a wide 4px row
|
|
39
|
+
/* split-v: divider is a wide 4px row - toolbar sits ABOVE it as a horizontal bar */
|
|
40
40
|
#root.split-v .divider-toolbar{flex-direction:row;gap:4px;padding:2px 4px;
|
|
41
41
|
top:auto;right:auto;bottom:100%;left:12px;margin-right:0;margin-bottom:2px}
|
|
42
42
|
#root.split-v .divider-sep{width:1px;height:14px;margin:0 1px}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cli/commands/dev/index.js
|
|
2
|
+
* cli/commands/dev/index.js - Dev server orchestrator
|
|
3
3
|
*
|
|
4
4
|
* Ties together the HTTP server, file watcher, logger, and overlay
|
|
5
5
|
* to provide a complete development environment with live-reload,
|
|
@@ -44,7 +44,7 @@ function resolveRoot(htmlEntry) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// ---------------------------------------------------------------------------
|
|
47
|
-
// devServer
|
|
47
|
+
// devServer - main entry point (called from cli/index.js)
|
|
48
48
|
// ---------------------------------------------------------------------------
|
|
49
49
|
|
|
50
50
|
async function devServer() {
|