zero-query 0.4.9 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/cli/commands/build.js +4 -2
- package/cli/commands/bundle.js +113 -10
- package/cli/commands/dev/index.js +82 -0
- package/cli/commands/dev/logger.js +70 -0
- package/cli/commands/dev/overlay.js +317 -0
- package/cli/commands/dev/server.js +129 -0
- package/cli/commands/dev/validator.js +94 -0
- package/cli/commands/dev/watcher.js +114 -0
- package/cli/commands/{dev.js → dev.old.js} +8 -4
- package/cli/help.js +18 -6
- package/cli/scaffold/favicon.ico +0 -0
- package/cli/scaffold/scripts/components/about.js +14 -2
- package/cli/scaffold/scripts/components/contacts/contacts.html +5 -4
- package/cli/scaffold/scripts/components/contacts/contacts.js +17 -1
- package/cli/scaffold/scripts/components/counter.js +30 -10
- package/cli/scaffold/scripts/components/home.js +3 -3
- package/cli/scaffold/scripts/components/todos.js +6 -5
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +1550 -97
- package/dist/zquery.min.js +11 -8
- package/index.d.ts +253 -14
- package/index.js +25 -8
- package/package.json +8 -2
- package/src/component.js +175 -44
- package/src/core.js +25 -18
- package/src/diff.js +280 -0
- package/src/errors.js +155 -0
- package/src/expression.js +806 -0
- package/src/http.js +18 -10
- package/src/reactive.js +29 -4
- package/src/router.js +11 -5
- package/src/ssr.js +224 -0
- package/src/store.js +24 -8
package/README.md
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
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 — all in a single ~
|
|
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 ~84 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
24
|
| **Core `$()`** | jQuery-like chainable selectors, traversal, DOM manipulation, events, animation |
|
|
25
|
-
| **Components** | Reactive state, template literals, `@event` delegation (8 modifiers), `z-model` two-way binding, 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`), scoped styles, external templates (`templateUrl` / `styleUrl`), lifecycle hooks, auto-injected base styles
|
|
25
|
+
| **Components** | Reactive state, template literals, `@event` delegation (8 modifiers), `z-model` two-way binding, 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`), DOM morphing engine (no innerHTML rebuild), CSP-safe expression evaluation, scoped styles, external templates (`templateUrl` / `styleUrl`), lifecycle hooks, auto-injected base styles |
|
|
26
26
|
| **Router** | History & hash mode, route params (`:id`), guards, lazy loading, `z-link` navigation |
|
|
27
27
|
| **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions |
|
|
28
28
|
| **HTTP** | Fetch wrapper with auto-JSON, interceptors, timeout/abort, base URL |
|
|
@@ -69,7 +69,7 @@ If you prefer **zero tooling**, download `dist/zQuery.min.js` from the [GitHub r
|
|
|
69
69
|
git clone https://github.com/tonywied17/zero-query.git
|
|
70
70
|
cd zero-query
|
|
71
71
|
npx zquery build
|
|
72
|
-
# → dist/zQuery.min.js (~
|
|
72
|
+
# → dist/zQuery.min.js (~84 KB)
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
### Include in HTML
|
|
@@ -162,7 +162,7 @@ my-app/
|
|
|
162
162
|
|
|
163
163
|
## CLI Bundler
|
|
164
164
|
|
|
165
|
-
The CLI
|
|
165
|
+
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.
|
|
166
166
|
|
|
167
167
|
```bash
|
|
168
168
|
# Auto-detect entry from any .html with a module script
|
|
@@ -170,6 +170,9 @@ npx zquery bundle
|
|
|
170
170
|
|
|
171
171
|
# Or point to an app directory from anywhere
|
|
172
172
|
npx zquery bundle my-app/
|
|
173
|
+
|
|
174
|
+
# Or pass a direct entry file (skips auto-detection)
|
|
175
|
+
npx zquery bundle my-app/scripts/main.js
|
|
173
176
|
```
|
|
174
177
|
|
|
175
178
|
Output goes to `dist/` next to your `index.html`:
|
|
@@ -192,7 +195,8 @@ dist/
|
|
|
192
195
|
| Flag | Short | Description |
|
|
193
196
|
| --- | --- | --- |
|
|
194
197
|
| `--out <path>` | `-o` | Custom output directory |
|
|
195
|
-
| `--
|
|
198
|
+
| `--index <file>` | `-i` | Index HTML file (default: auto-detected) |
|
|
199
|
+
| `--minimal` | `-m` | Only output HTML + bundled JS (skip static assets) |
|
|
196
200
|
|
|
197
201
|
### What the Bundler Does
|
|
198
202
|
|
|
@@ -236,13 +240,15 @@ location / {
|
|
|
236
240
|
|
|
237
241
|
| Namespace | Methods |
|
|
238
242
|
| --- | --- |
|
|
239
|
-
| `$()` |
|
|
240
|
-
| `$.all()` |
|
|
241
|
-
| `$.id` `$.class` `$.classes` `$.tag` `$.children` | Quick DOM refs |
|
|
243
|
+
| `$()` | Chainable selector → `ZQueryCollection` (CSS selectors, elements, NodeLists, HTML strings) |
|
|
244
|
+
| `$.all()` | Alias for `$()` — identical behavior |
|
|
245
|
+
| `$.id` `$.class` `$.classes` `$.tag` `$.name` `$.children` | Quick DOM refs |
|
|
242
246
|
| `$.create` | Element factory |
|
|
243
247
|
| `$.ready` `$.on` `$.off` | DOM ready, global event delegation & direct listeners |
|
|
244
248
|
| `$.fn` | Collection prototype (extend it) |
|
|
245
249
|
| `$.component` `$.mount` `$.mountAll` `$.getInstance` `$.destroy` `$.components` | Component system |
|
|
250
|
+
| `$.morph` | DOM morphing engine — patch existing DOM to match new HTML without destroying unchanged nodes |
|
|
251
|
+
| `$.safeEval` | CSP-safe expression evaluator (replaces `eval` / `new Function`) |
|
|
246
252
|
| `$.style` | Dynamically load global stylesheet file(s) at runtime |
|
|
247
253
|
| `$.router` `$.getRouter` | SPA router |
|
|
248
254
|
| `$.store` `$.getStore` | State management |
|
|
@@ -261,8 +267,8 @@ location / {
|
|
|
261
267
|
| CLI Command | Description |
|
|
262
268
|
| --- | --- |
|
|
263
269
|
| `zquery create [dir]` | Scaffold a new project (index.html, components, store, styles) |
|
|
264
|
-
| `zquery dev [root]` | Dev server with live-reload & error overlay (port 3100) |
|
|
265
|
-
| `zquery bundle [dir]` | Bundle app into a single IIFE file |
|
|
270
|
+
| `zquery dev [root]` | Dev server with live-reload & error overlay (port 3100). `--index` for custom HTML. |
|
|
271
|
+
| `zquery bundle [dir\|file]` | Bundle app into a single IIFE file. Accepts dir or direct entry file. |
|
|
266
272
|
| `zquery build` | Build the zQuery library (`dist/zQuery.min.js`) |
|
|
267
273
|
| `zquery --help` | Show CLI usage |
|
|
268
274
|
|
package/cli/commands/build.js
CHANGED
|
@@ -16,8 +16,10 @@ function buildLibrary() {
|
|
|
16
16
|
const VERSION = pkg.version;
|
|
17
17
|
|
|
18
18
|
const modules = [
|
|
19
|
-
'src/
|
|
20
|
-
'src/
|
|
19
|
+
'src/errors.js',
|
|
20
|
+
'src/reactive.js', 'src/core.js', 'src/expression.js', 'src/diff.js',
|
|
21
|
+
'src/component.js', 'src/router.js', 'src/store.js', 'src/http.js',
|
|
22
|
+
'src/utils.js',
|
|
21
23
|
];
|
|
22
24
|
|
|
23
25
|
const DIST = path.join(process.cwd(), 'dist');
|
package/cli/commands/bundle.js
CHANGED
|
@@ -13,7 +13,7 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const crypto = require('crypto');
|
|
15
15
|
|
|
16
|
-
const { args, option }
|
|
16
|
+
const { args, flag, option } = require('../args');
|
|
17
17
|
const { minify, sizeKB } = require('../utils');
|
|
18
18
|
const buildLibrary = require('./build');
|
|
19
19
|
|
|
@@ -104,6 +104,8 @@ function rewriteResourceUrls(code, filePath, projectRoot) {
|
|
|
104
104
|
(match, prefix, quote, url) => {
|
|
105
105
|
if (url.startsWith('/') || url.includes('://')) return match;
|
|
106
106
|
const abs = path.resolve(fileDir, url);
|
|
107
|
+
// Only rewrite if the file actually exists — avoids mangling code examples
|
|
108
|
+
if (!fs.existsSync(abs)) return match;
|
|
107
109
|
const rel = path.relative(projectRoot, abs).replace(/\\/g, '/');
|
|
108
110
|
return `${prefix}${quote}${rel}${quote}`;
|
|
109
111
|
}
|
|
@@ -379,18 +381,68 @@ function rewriteHtml(projectRoot, htmlRelPath, bundleFile, includeLib, bundledFi
|
|
|
379
381
|
console.log(` ✓ Copied ${copiedCount} asset(s) into both dist dirs`);
|
|
380
382
|
}
|
|
381
383
|
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
// Static asset copying
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Copy the entire app directory into both dist/server and dist/local,
|
|
390
|
+
* skipping only build outputs and tooling dirs. This ensures all static
|
|
391
|
+
* assets (icons/, images/, fonts/, styles/, scripts/, manifests, etc.)
|
|
392
|
+
* are available in the built output without maintaining a fragile whitelist.
|
|
393
|
+
*/
|
|
394
|
+
function copyStaticAssets(appRoot, serverDir, localDir, bundledFiles) {
|
|
395
|
+
const SKIP_DIRS = new Set(['dist', 'node_modules', '.git', '.vscode', 'scripts']);
|
|
396
|
+
|
|
397
|
+
let copiedCount = 0;
|
|
398
|
+
|
|
399
|
+
function copyEntry(srcPath, relPath) {
|
|
400
|
+
const stat = fs.statSync(srcPath);
|
|
401
|
+
if (stat.isDirectory()) {
|
|
402
|
+
if (SKIP_DIRS.has(path.basename(srcPath))) return;
|
|
403
|
+
for (const child of fs.readdirSync(srcPath)) {
|
|
404
|
+
copyEntry(path.join(srcPath, child), path.join(relPath, child));
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
if (bundledFiles && bundledFiles.has(path.resolve(srcPath))) return;
|
|
408
|
+
for (const distDir of [serverDir, localDir]) {
|
|
409
|
+
const dest = path.join(distDir, relPath);
|
|
410
|
+
if (fs.existsSync(dest)) continue; // already copied by rewriteHtml
|
|
411
|
+
const dir = path.dirname(dest);
|
|
412
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
413
|
+
fs.copyFileSync(srcPath, dest);
|
|
414
|
+
}
|
|
415
|
+
copiedCount++;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (const entry of fs.readdirSync(appRoot, { withFileTypes: true })) {
|
|
420
|
+
if (entry.isDirectory()) {
|
|
421
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
422
|
+
copyEntry(path.join(appRoot, entry.name), entry.name);
|
|
423
|
+
} else {
|
|
424
|
+
copyEntry(path.join(appRoot, entry.name), entry.name);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (copiedCount > 0) {
|
|
429
|
+
console.log(` \u2713 Copied ${copiedCount} additional static asset(s) into both dist dirs`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
382
433
|
// ---------------------------------------------------------------------------
|
|
383
434
|
// Main bundleApp function
|
|
384
435
|
// ---------------------------------------------------------------------------
|
|
385
436
|
|
|
386
437
|
function bundleApp() {
|
|
387
438
|
const projectRoot = process.cwd();
|
|
439
|
+
const minimal = flag('minimal', 'm');
|
|
388
440
|
|
|
389
|
-
// Entry point —
|
|
441
|
+
// Entry point — positional arg (directory or file) or auto-detection
|
|
390
442
|
let entry = null;
|
|
391
443
|
let targetDir = null;
|
|
392
444
|
for (let i = 1; i < args.length; i++) {
|
|
393
|
-
if (!args[i].startsWith('-') && args[i - 1] !== '-o' && args[i - 1] !== '--out' && args[i - 1] !== '--
|
|
445
|
+
if (!args[i].startsWith('-') && args[i - 1] !== '-o' && args[i - 1] !== '--out' && args[i - 1] !== '--index') {
|
|
394
446
|
const resolved = path.resolve(projectRoot, args[i]);
|
|
395
447
|
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
396
448
|
targetDir = resolved;
|
|
@@ -405,16 +457,19 @@ function bundleApp() {
|
|
|
405
457
|
|
|
406
458
|
if (!entry || !fs.existsSync(entry)) {
|
|
407
459
|
console.error(`\n \u2717 Could not find entry file.`);
|
|
408
|
-
console.error(` Provide an app directory: zquery bundle my-app
|
|
460
|
+
console.error(` Provide an app directory: zquery bundle my-app/`);
|
|
461
|
+
console.error(` Or pass a direct entry file: zquery bundle my-app/scripts/main.js\n`);
|
|
409
462
|
process.exit(1);
|
|
410
463
|
}
|
|
411
464
|
|
|
412
465
|
const outPath = option('out', 'o', null);
|
|
413
466
|
|
|
414
|
-
// Auto-detect
|
|
415
|
-
let htmlFile = option('
|
|
467
|
+
// Auto-detect HTML file
|
|
468
|
+
let htmlFile = option('index', 'i', null);
|
|
416
469
|
let htmlAbs = htmlFile ? path.resolve(projectRoot, htmlFile) : null;
|
|
417
470
|
if (!htmlFile) {
|
|
471
|
+
// Strategy: first look for index.html walking up from entry, then
|
|
472
|
+
// scan for any .html that references the entry via a module script tag.
|
|
418
473
|
const htmlCandidates = [];
|
|
419
474
|
let entryDir = path.dirname(entry);
|
|
420
475
|
while (entryDir.length >= projectRoot.length) {
|
|
@@ -432,6 +487,47 @@ function bundleApp() {
|
|
|
432
487
|
break;
|
|
433
488
|
}
|
|
434
489
|
}
|
|
490
|
+
|
|
491
|
+
// If no index.html found, scan for any .html file that references
|
|
492
|
+
// the entry point (supports home.html, app.html, etc.)
|
|
493
|
+
if (!htmlAbs) {
|
|
494
|
+
const searchRoot = targetDir || projectRoot;
|
|
495
|
+
const htmlScan = [];
|
|
496
|
+
for (const e of fs.readdirSync(searchRoot, { withFileTypes: true })) {
|
|
497
|
+
if (e.isFile() && e.name.endsWith('.html')) {
|
|
498
|
+
htmlScan.push(path.join(searchRoot, e.name));
|
|
499
|
+
} else if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules' && e.name !== 'dist') {
|
|
500
|
+
try {
|
|
501
|
+
for (const child of fs.readdirSync(path.join(searchRoot, e.name), { withFileTypes: true })) {
|
|
502
|
+
if (child.isFile() && child.name.endsWith('.html')) {
|
|
503
|
+
htmlScan.push(path.join(searchRoot, e.name, child.name));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} catch { /* skip */ }
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Prefer the HTML file that references our entry via a module script
|
|
510
|
+
const moduleScriptRe = /<script[^>]+type\s*=\s*["']module["'][^>]+src\s*=\s*["']([^"']+)["']/g;
|
|
511
|
+
for (const hp of htmlScan) {
|
|
512
|
+
const content = fs.readFileSync(hp, 'utf-8');
|
|
513
|
+
let m;
|
|
514
|
+
moduleScriptRe.lastIndex = 0;
|
|
515
|
+
while ((m = moduleScriptRe.exec(content)) !== null) {
|
|
516
|
+
const resolved = path.resolve(path.dirname(hp), m[1]);
|
|
517
|
+
if (resolved === path.resolve(entry)) {
|
|
518
|
+
htmlAbs = hp;
|
|
519
|
+
htmlFile = path.relative(projectRoot, hp);
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (htmlAbs) break;
|
|
524
|
+
}
|
|
525
|
+
// Last resort: use the first .html found
|
|
526
|
+
if (!htmlAbs && htmlScan.length > 0) {
|
|
527
|
+
htmlAbs = htmlScan[0];
|
|
528
|
+
htmlFile = path.relative(projectRoot, htmlScan[0]);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
435
531
|
}
|
|
436
532
|
|
|
437
533
|
// Output directory
|
|
@@ -459,7 +555,8 @@ function bundleApp() {
|
|
|
459
555
|
console.log(` Entry: ${entryRel}`);
|
|
460
556
|
console.log(` Output: ${path.relative(projectRoot, baseDistDir)}/server/ & local/`);
|
|
461
557
|
console.log(` Library: embedded`);
|
|
462
|
-
console.log(` HTML: ${htmlFile || 'not found (no
|
|
558
|
+
console.log(` HTML: ${htmlFile || 'not found (no HTML detected)'}`);
|
|
559
|
+
if (minimal) console.log(` Mode: minimal (HTML + bundle only)`);
|
|
463
560
|
console.log('');
|
|
464
561
|
|
|
465
562
|
// ------ doBuild (inlined) ------
|
|
@@ -571,10 +668,16 @@ function bundleApp() {
|
|
|
571
668
|
console.log(`\n ✓ ${bundleBase} (${sizeKB(fs.readFileSync(bundleFile))} KB)`);
|
|
572
669
|
console.log(` ✓ ${minBase} (${sizeKB(fs.readFileSync(minFile))} KB)`);
|
|
573
670
|
|
|
574
|
-
// Rewrite HTML
|
|
671
|
+
// Rewrite HTML (use full bundle — minified version mangles template literal whitespace)
|
|
672
|
+
const bundledFileSet = new Set(files);
|
|
575
673
|
if (htmlFile) {
|
|
576
|
-
|
|
577
|
-
|
|
674
|
+
rewriteHtml(projectRoot, htmlFile, bundleFile, true, bundledFileSet, serverDir, localDir);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Copy static asset directories (icons/, images/, fonts/, etc.)
|
|
678
|
+
if (!minimal) {
|
|
679
|
+
const appRoot = htmlAbs ? path.dirname(htmlAbs) : (targetDir || projectRoot);
|
|
680
|
+
copyStaticAssets(appRoot, serverDir, localDir, bundledFileSet);
|
|
578
681
|
}
|
|
579
682
|
|
|
580
683
|
const elapsed = Date.now() - start;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/dev/index.js — Dev server orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Ties together the HTTP server, file watcher, logger, and overlay
|
|
5
|
+
* to provide a complete development environment with live-reload,
|
|
6
|
+
* syntax validation, and a full-screen error overlay that surfaces
|
|
7
|
+
* both build-time and runtime ZQueryErrors.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const { args, flag, option } = require('../../args');
|
|
16
|
+
const { createServer } = require('./server');
|
|
17
|
+
const { startWatcher } = require('./watcher');
|
|
18
|
+
const { printBanner } = require('./logger');
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Resolve project root
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
function resolveRoot(htmlEntry) {
|
|
25
|
+
// Explicit positional argument → zquery dev <dir>
|
|
26
|
+
for (let i = 1; i < args.length; i++) {
|
|
27
|
+
const prev = args[i - 1];
|
|
28
|
+
if (!args[i].startsWith('-') && prev !== '-p' && prev !== '--port' && prev !== '--index' && prev !== '-i') {
|
|
29
|
+
return path.resolve(process.cwd(), args[i]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Auto-detect: first candidate that contains the HTML entry file
|
|
34
|
+
const candidates = [
|
|
35
|
+
process.cwd(),
|
|
36
|
+
path.join(process.cwd(), 'public'),
|
|
37
|
+
path.join(process.cwd(), 'src'),
|
|
38
|
+
];
|
|
39
|
+
for (const c of candidates) {
|
|
40
|
+
if (fs.existsSync(path.join(c, htmlEntry))) return c;
|
|
41
|
+
}
|
|
42
|
+
return process.cwd();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// devServer — main entry point (called from cli/index.js)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
function devServer() {
|
|
50
|
+
const htmlEntry = option('index', 'i', 'index.html');
|
|
51
|
+
const port = parseInt(option('port', 'p', '3100'), 10);
|
|
52
|
+
const noIntercept = flag('no-intercept');
|
|
53
|
+
const root = resolveRoot(htmlEntry);
|
|
54
|
+
|
|
55
|
+
// Start HTTP server + SSE pool
|
|
56
|
+
const { app, pool, listen } = createServer({ root, htmlEntry, port, noIntercept });
|
|
57
|
+
|
|
58
|
+
// Start file watcher
|
|
59
|
+
const watcher = startWatcher({ root, pool });
|
|
60
|
+
|
|
61
|
+
// Boot
|
|
62
|
+
listen(() => {
|
|
63
|
+
printBanner({
|
|
64
|
+
port,
|
|
65
|
+
root: path.relative(process.cwd(), root) || '.',
|
|
66
|
+
htmlEntry,
|
|
67
|
+
noIntercept,
|
|
68
|
+
watchDirCount: watcher.dirs.length,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Graceful shutdown
|
|
73
|
+
process.on('SIGINT', () => {
|
|
74
|
+
console.log('\n Shutting down...');
|
|
75
|
+
watcher.destroy();
|
|
76
|
+
pool.closeAll();
|
|
77
|
+
app.close(() => process.exit(0));
|
|
78
|
+
setTimeout(() => process.exit(0), 1000);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = devServer;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/dev/logger.js — Terminal output helpers
|
|
3
|
+
*
|
|
4
|
+
* Provides styled console output for the dev server: startup banner,
|
|
5
|
+
* timestamped file-change messages, and error formatting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
// ANSI colour helpers (works on all modern terminals)
|
|
11
|
+
const c = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bold: '\x1b[1m',
|
|
14
|
+
dim: '\x1b[2m',
|
|
15
|
+
red: '\x1b[31m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
magenta: '\x1b[35m',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function timestamp() {
|
|
23
|
+
return new Date().toLocaleTimeString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Event-level log helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
function logCSS(relPath) {
|
|
31
|
+
console.log(` ${timestamp()} ${c.magenta} css ${c.reset} ${relPath}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function logReload(relPath) {
|
|
35
|
+
console.log(` ${timestamp()} ${c.cyan} reload ${c.reset} ${relPath}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function logError(descriptor) {
|
|
39
|
+
const t = timestamp();
|
|
40
|
+
console.log(` ${t} ${c.red} error ${c.reset} ${descriptor.file}`);
|
|
41
|
+
console.log(` ${c.red}${descriptor.type}: ${descriptor.message}${c.reset}`);
|
|
42
|
+
if (descriptor.line) {
|
|
43
|
+
console.log(` ${c.dim}at line ${descriptor.line}${descriptor.column ? ':' + descriptor.column : ''}${c.reset}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Startup banner
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
function printBanner({ port, root, htmlEntry, noIntercept, watchDirCount }) {
|
|
52
|
+
const rule = c.dim + '-'.repeat(40) + c.reset;
|
|
53
|
+
console.log(`\n ${c.bold}zQuery Dev Server${c.reset}`);
|
|
54
|
+
console.log(` ${rule}`);
|
|
55
|
+
console.log(` Local: ${c.cyan}http://localhost:${port}/${c.reset}`);
|
|
56
|
+
console.log(` Root: ${root}`);
|
|
57
|
+
if (htmlEntry !== 'index.html') {
|
|
58
|
+
console.log(` HTML: ${c.cyan}${htmlEntry}${c.reset}`);
|
|
59
|
+
}
|
|
60
|
+
console.log(` Live Reload: ${c.green}enabled${c.reset} (SSE)`);
|
|
61
|
+
console.log(` Overlay: ${c.green}enabled${c.reset} (syntax + runtime + ZQueryError)`);
|
|
62
|
+
if (noIntercept) {
|
|
63
|
+
console.log(` Intercept: ${c.yellow}disabled${c.reset} (--no-intercept)`);
|
|
64
|
+
}
|
|
65
|
+
console.log(` Watching: all files in ${watchDirCount} director${watchDirCount === 1 ? 'y' : 'ies'}`);
|
|
66
|
+
console.log(` ${rule}`);
|
|
67
|
+
console.log(` Press Ctrl+C to stop\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { logCSS, logReload, logError, printBanner };
|