zero-query 0.6.3 → 0.8.6
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 +39 -29
- package/cli/commands/build.js +113 -4
- package/cli/commands/bundle.js +392 -29
- package/cli/commands/create.js +1 -1
- package/cli/commands/dev/devtools/index.js +56 -0
- package/cli/commands/dev/devtools/js/components.js +49 -0
- package/cli/commands/dev/devtools/js/core.js +409 -0
- package/cli/commands/dev/devtools/js/elements.js +413 -0
- package/cli/commands/dev/devtools/js/network.js +166 -0
- package/cli/commands/dev/devtools/js/performance.js +73 -0
- package/cli/commands/dev/devtools/js/router.js +105 -0
- package/cli/commands/dev/devtools/js/source.js +132 -0
- package/cli/commands/dev/devtools/js/stats.js +35 -0
- package/cli/commands/dev/devtools/js/tabs.js +79 -0
- package/cli/commands/dev/devtools/panel.html +95 -0
- package/cli/commands/dev/devtools/styles.css +244 -0
- package/cli/commands/dev/index.js +29 -4
- package/cli/commands/dev/logger.js +6 -1
- package/cli/commands/dev/overlay.js +428 -2
- package/cli/commands/dev/server.js +42 -5
- package/cli/commands/dev/watcher.js +59 -1
- package/cli/help.js +8 -5
- package/cli/scaffold/{scripts → app}/app.js +16 -23
- package/cli/scaffold/{scripts → app}/components/about.js +4 -4
- package/cli/scaffold/{scripts → app}/components/api-demo.js +1 -1
- package/cli/scaffold/{scripts → app}/components/contacts/contacts.css +0 -7
- package/cli/scaffold/{scripts → app}/components/contacts/contacts.html +3 -3
- package/cli/scaffold/app/components/home.js +137 -0
- package/cli/scaffold/{scripts → app}/routes.js +1 -1
- package/cli/scaffold/{scripts → app}/store.js +6 -6
- package/cli/scaffold/assets/.gitkeep +0 -0
- package/cli/scaffold/{styles/styles.css → global.css} +4 -2
- package/cli/scaffold/index.html +12 -11
- package/cli/utils.js +111 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +1122 -158
- package/dist/zquery.min.js +3 -16
- package/index.d.ts +129 -1290
- package/index.js +15 -10
- package/package.json +7 -6
- package/src/component.js +172 -49
- package/src/core.js +359 -18
- package/src/diff.js +256 -58
- package/src/expression.js +33 -3
- package/src/reactive.js +37 -5
- package/src/router.js +243 -7
- package/tests/component.test.js +886 -0
- package/tests/core.test.js +977 -0
- package/tests/diff.test.js +525 -0
- package/tests/errors.test.js +162 -0
- package/tests/expression.test.js +482 -0
- package/tests/http.test.js +289 -0
- package/tests/reactive.test.js +339 -0
- package/tests/router.test.js +649 -0
- package/tests/store.test.js +379 -0
- package/tests/utils.test.js +512 -0
- package/types/collection.d.ts +383 -0
- package/types/component.d.ts +217 -0
- package/types/errors.d.ts +103 -0
- package/types/http.d.ts +81 -0
- package/types/misc.d.ts +179 -0
- package/types/reactive.d.ts +76 -0
- package/types/router.d.ts +161 -0
- package/types/ssr.d.ts +49 -0
- package/types/store.d.ts +107 -0
- package/types/utils.d.ts +142 -0
- package/cli/commands/dev.old.js +0 -520
- package/cli/scaffold/scripts/components/home.js +0 -137
- /package/cli/scaffold/{scripts → app}/components/contacts/contacts.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/counter.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/not-found.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/todos.js +0 -0
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src=".github/images/logo.svg" alt="zQuery logo" width="300" height="300">
|
|
2
|
+
<img src=".github/images/logo-animated.svg" alt="zQuery logo" width="300" height="300">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">zQuery</h1>
|
|
@@ -15,19 +15,21 @@
|
|
|
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 ~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
|
-
| **
|
|
25
|
-
| **
|
|
26
|
-
| **
|
|
27
|
-
| **
|
|
24
|
+
| **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`, `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, `z-to-top` scroll modifier (`instant`/`smooth`), sub-route history substates (`pushSubstate`/`onSubstate`) |
|
|
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, time-travel (undo/redo/history) |
|
|
29
|
+
| **Selectors & DOM** | jQuery-like chainable selectors, traversal, DOM manipulation, events, animation |
|
|
28
30
|
| **HTTP** | Fetch wrapper with auto-JSON, interceptors, timeout/abort, base URL |
|
|
29
|
-
| **Reactive** | Deep proxy reactivity, Signals, computed values, effects |
|
|
30
31
|
| **Utils** | debounce, throttle, pipe, once, sleep, escapeHtml, uuid, deepClone, deepMerge, storage/session wrappers, event bus |
|
|
32
|
+
| **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 |
|
|
31
33
|
|
|
32
34
|
---
|
|
33
35
|
|
|
@@ -61,15 +63,19 @@ The dev server includes a **full-screen error overlay** that surfaces errors dir
|
|
|
61
63
|
- **Runtime errors** — uncaught exceptions and unhandled promise rejections are captured and displayed in the same overlay with a cleaned-up stack trace.
|
|
62
64
|
- The overlay **auto-clears** when you fix the error and save. Press `Esc` or click `×` to dismiss manually.
|
|
63
65
|
|
|
66
|
+
#### Floating Toolbar & Inspector
|
|
67
|
+
|
|
68
|
+
A compact toolbar appears in the bottom-right corner showing live request/render counters and a DOM button. Click any counter to open a **dark-themed DevTools inspector** as a popup — or visit `http://localhost:<port>/_devtools` for a standalone split-view panel with four tabs: **Elements** (live DOM tree with component badges, source viewer, expand/collapse), **Network** (fetch log with JSON viewer), **Components** (live state cards), and **Performance** (render timeline with timing metrics).
|
|
69
|
+
|
|
64
70
|
### Alternative: Manual Setup (No npm)
|
|
65
71
|
|
|
66
|
-
If you prefer **zero tooling**, download `dist/
|
|
72
|
+
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.
|
|
67
73
|
|
|
68
74
|
```bash
|
|
69
75
|
git clone https://github.com/tonywied17/zero-query.git
|
|
70
76
|
cd zero-query
|
|
71
77
|
npx zquery build
|
|
72
|
-
# → dist/
|
|
78
|
+
# → dist/zquery.min.js (~91 KB)
|
|
73
79
|
```
|
|
74
80
|
|
|
75
81
|
### Include in HTML
|
|
@@ -80,9 +86,9 @@ npx zquery build
|
|
|
80
86
|
<head>
|
|
81
87
|
<meta charset="UTF-8">
|
|
82
88
|
<title>My App</title>
|
|
83
|
-
<link rel="stylesheet" href="
|
|
84
|
-
<script src="
|
|
85
|
-
<script type="module" src="
|
|
89
|
+
<link rel="stylesheet" href="global.css">
|
|
90
|
+
<script src="zquery.min.js"></script>
|
|
91
|
+
<script type="module" src="app/app.js"></script>
|
|
86
92
|
</head>
|
|
87
93
|
<body>
|
|
88
94
|
<nav>
|
|
@@ -97,7 +103,7 @@ npx zquery build
|
|
|
97
103
|
### Boot Your App
|
|
98
104
|
|
|
99
105
|
```js
|
|
100
|
-
//
|
|
106
|
+
// app/app.js
|
|
101
107
|
import './components/home.js';
|
|
102
108
|
import './components/about.js';
|
|
103
109
|
import './components/contacts/contacts.js';
|
|
@@ -109,7 +115,7 @@ $.router({ el: '#app', routes, fallback: 'not-found' });
|
|
|
109
115
|
### Define a Component
|
|
110
116
|
|
|
111
117
|
```js
|
|
112
|
-
//
|
|
118
|
+
// app/components/home.js
|
|
113
119
|
$.component('home-page', {
|
|
114
120
|
state: () => ({ count: 0 }),
|
|
115
121
|
increment() { this.state.count++; },
|
|
@@ -132,9 +138,8 @@ That's it — a fully working SPA with the dev server's live-reload.
|
|
|
132
138
|
```
|
|
133
139
|
my-app/
|
|
134
140
|
index.html
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
zQuery.min.js ← only needed for manual setup; dev server auto-resolves
|
|
141
|
+
global.css
|
|
142
|
+
app/
|
|
138
143
|
app.js
|
|
139
144
|
routes.js
|
|
140
145
|
store.js
|
|
@@ -149,14 +154,17 @@ my-app/
|
|
|
149
154
|
contacts.js
|
|
150
155
|
contacts.html
|
|
151
156
|
contacts.css
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
assets/
|
|
158
|
+
scripts/ ← third-party JS (e.g. zquery.min.js for manual setup)
|
|
159
|
+
styles/ ← additional stylesheets, fonts, etc.
|
|
154
160
|
```
|
|
155
161
|
|
|
156
162
|
- One component per file inside `components/`.
|
|
157
163
|
- Names **must contain a hyphen** (Web Component convention): `home-page`, `app-counter`, etc.
|
|
158
164
|
- Components with external templates or styles can use a subfolder (e.g. `contacts/contacts.js` + `contacts.html` + `contacts.css`).
|
|
159
165
|
- `app.js` is the single entry point — import components, create the store, and boot the router.
|
|
166
|
+
- `global.css` lives next to `index.html` for easy access; the bundler hashes it into `global.<hash>.min.css` for production.
|
|
167
|
+
- `assets/` holds static files that get copied to `dist/` as-is.
|
|
160
168
|
|
|
161
169
|
---
|
|
162
170
|
|
|
@@ -172,7 +180,7 @@ npx zquery bundle
|
|
|
172
180
|
npx zquery bundle my-app/
|
|
173
181
|
|
|
174
182
|
# Or pass a direct entry file (skips auto-detection)
|
|
175
|
-
npx zquery bundle my-app/
|
|
183
|
+
npx zquery bundle my-app/app/main.js
|
|
176
184
|
```
|
|
177
185
|
|
|
178
186
|
Output goes to `dist/` next to your `index.html`:
|
|
@@ -183,7 +191,8 @@ dist/
|
|
|
183
191
|
index.html
|
|
184
192
|
z-app.<hash>.js
|
|
185
193
|
z-app.<hash>.min.js
|
|
186
|
-
|
|
194
|
+
global.<hash>.min.css
|
|
195
|
+
assets/
|
|
187
196
|
local/ ← open from disk (file://) — no server needed
|
|
188
197
|
index.html
|
|
189
198
|
z-app.<hash>.js
|
|
@@ -196,7 +205,8 @@ dist/
|
|
|
196
205
|
| --- | --- | --- |
|
|
197
206
|
| `--out <path>` | `-o` | Custom output directory |
|
|
198
207
|
| `--index <file>` | `-i` | Index HTML file (default: auto-detected) |
|
|
199
|
-
| `--minimal` | `-m` | Only output HTML
|
|
208
|
+
| `--minimal` | `-m` | Only output HTML, bundled JS, and global CSS (skip static assets) |
|
|
209
|
+
| `--global-css <path>` | | Override global CSS input file (default: first `<link>` in HTML) |
|
|
200
210
|
|
|
201
211
|
### What the Bundler Does
|
|
202
212
|
|
|
@@ -204,7 +214,7 @@ dist/
|
|
|
204
214
|
1. **HTML files** — `index.html` is checked first, then other `.html` files (root + one level deep).
|
|
205
215
|
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.
|
|
206
216
|
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(`.
|
|
207
|
-
4. **Convention fallbacks** — `scripts/app.js`, `src/app.js`, `js/app.js`, `app.js`, `main.js`.
|
|
217
|
+
4. **Convention fallbacks** — `app/app.js`, `scripts/app.js`, `src/app.js`, `js/app.js`, `app.js`, `main.js`.
|
|
208
218
|
2. Resolves all `import` statements and topologically sorts dependencies
|
|
209
219
|
3. Strips `import`/`export` syntax, wraps in an IIFE
|
|
210
220
|
4. Embeds zQuery library and inlines `templateUrl` / `styleUrl` / `pages` files
|
|
@@ -246,8 +256,8 @@ location / {
|
|
|
246
256
|
| `$.create` | Element factory |
|
|
247
257
|
| `$.ready` `$.on` `$.off` | DOM ready, global event delegation & direct listeners |
|
|
248
258
|
| `$.fn` | Collection prototype (extend it) |
|
|
249
|
-
| `$.component` `$.mount` `$.mountAll` `$.getInstance` `$.destroy` `$.components` | Component system |
|
|
250
|
-
| `$.morph` | DOM morphing engine —
|
|
259
|
+
| `$.component` `$.mount` `$.mountAll` `$.getInstance` `$.destroy` `$.components` `$.prefetch` | Component system |
|
|
260
|
+
| `$.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 |
|
|
251
261
|
| `$.safeEval` | CSP-safe expression evaluator (replaces `eval` / `new Function`) |
|
|
252
262
|
| `$.style` | Dynamically load global stylesheet file(s) at runtime |
|
|
253
263
|
| `$.router` `$.getRouter` | SPA router |
|
|
@@ -260,16 +270,16 @@ location / {
|
|
|
260
270
|
| `$.param` `$.parseQuery` | URL utils |
|
|
261
271
|
| `$.storage` `$.session` | Storage wrappers |
|
|
262
272
|
| `$.bus` | Event bus |
|
|
263
|
-
| `$.version` | Library version |
|
|
273
|
+
| `$.version` | Library version |\n| `$.libSize` | Minified bundle size string (e.g. `\"~91 KB\"`) |
|
|
264
274
|
| `$.meta` | Build metadata (populated by CLI bundler) |
|
|
265
275
|
| `$.noConflict` | Release `$` global |
|
|
266
276
|
|
|
267
277
|
| CLI Command | Description |
|
|
268
278
|
| --- | --- |
|
|
269
279
|
| `zquery create [dir]` | Scaffold a new project (index.html, components, store, styles) |
|
|
270
|
-
| `zquery dev [root]` | Dev server with live-reload &
|
|
280
|
+
| `zquery dev [root]` | Dev server with live-reload, CSS hot-swap, error overlay, floating toolbar & inspector panel (port 3100). Visit `/_devtools` for the standalone panel. `--index` for custom HTML, `--bundle` for bundled mode, `--no-intercept` to skip CDN intercept. |
|
|
271
281
|
| `zquery bundle [dir\|file]` | Bundle app into a single IIFE file. Accepts dir or direct entry file. |
|
|
272
|
-
| `zquery build` | Build the zQuery library (`dist/
|
|
282
|
+
| `zquery build` | Build the zQuery library (`dist/zquery.min.js`) |
|
|
273
283
|
| `zquery --help` | Show CLI usage |
|
|
274
284
|
|
|
275
285
|
For full method signatures, options, and examples, see **[API.md](API.md)**.
|
|
@@ -278,7 +288,7 @@ For full method signatures, options, and examples, see **[API.md](API.md)**.
|
|
|
278
288
|
|
|
279
289
|
## Editor Support
|
|
280
290
|
|
|
281
|
-
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
|
|
291
|
+
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 185+ 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.
|
|
282
292
|
|
|
283
293
|
---
|
|
284
294
|
|
package/cli/commands/build.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const zlib = require('zlib');
|
|
12
13
|
const { minify, sizeKB } = require('../utils');
|
|
13
14
|
|
|
14
15
|
function buildLibrary() {
|
|
@@ -34,7 +35,7 @@ function buildLibrary() {
|
|
|
34
35
|
code = code.replace(/^import\s+[\s\S]*?from\s+['"].*?['"];?\s*$/gm, '');
|
|
35
36
|
code = code.replace(/^export\s+(default\s+)?/gm, '');
|
|
36
37
|
code = code.replace(/^export\s*\{[\s\S]*?\};\s*$/gm, '');
|
|
37
|
-
return `// --- ${file} ${'
|
|
38
|
+
return `// --- ${file} ${'-'.repeat(60 - file.length)}\n${code.trim()}`;
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
let indexCode = fs.readFileSync(path.join(process.cwd(), 'index.js'), 'utf-8');
|
|
@@ -42,18 +43,126 @@ function buildLibrary() {
|
|
|
42
43
|
indexCode = indexCode.replace(/^export\s*\{[\s\S]*?\};\s*$/gm, '');
|
|
43
44
|
indexCode = indexCode.replace(/^export\s+(default\s+)?/gm, '');
|
|
44
45
|
|
|
45
|
-
const banner = `/**\n * zQuery (zeroQuery) v${VERSION}\n * Lightweight Frontend Library\n * https://github.com/tonywied17/zero-query\n * (c) ${new Date().getFullYear()} Anthony Wiedman
|
|
46
|
+
const banner = `/**\n * zQuery (zeroQuery) v${VERSION}\n * Lightweight Frontend Library\n * https://github.com/tonywied17/zero-query\n * (c) ${new Date().getFullYear()} Anthony Wiedman - MIT License\n */`;
|
|
46
47
|
|
|
47
|
-
const bundle = `${banner}\n(function(global) {\n 'use strict';\n\n${parts.join('\n\n')}\n\n// --- index.js (assembly) ${'
|
|
48
|
+
const bundle = `${banner}\n(function(global) {\n 'use strict';\n\n${parts.join('\n\n')}\n\n// --- index.js (assembly) ${'-'.repeat(42)}\n${indexCode.trim().replace("'__VERSION__'", `'${VERSION}'`)}\n\n})(typeof window !== 'undefined' ? window : globalThis);\n`;
|
|
48
49
|
|
|
49
50
|
fs.writeFileSync(OUT_FILE, bundle, 'utf-8');
|
|
50
|
-
|
|
51
|
+
const minified = minify(bundle, banner);
|
|
52
|
+
fs.writeFileSync(MIN_FILE, minified, 'utf-8');
|
|
53
|
+
|
|
54
|
+
// Inject actual minified library size into both outputs
|
|
55
|
+
const libSizeKB = Math.round(Buffer.from(minified).length / 1024);
|
|
56
|
+
const libSizeStr = `~${libSizeKB} KB`;
|
|
57
|
+
const outContent = fs.readFileSync(OUT_FILE, 'utf-8').replace("'__LIB_SIZE__'", `'${libSizeStr}'`);
|
|
58
|
+
const minContent = minified.replace("'__LIB_SIZE__'", `'${libSizeStr}'`);
|
|
59
|
+
fs.writeFileSync(OUT_FILE, outContent, 'utf-8');
|
|
60
|
+
fs.writeFileSync(MIN_FILE, minContent, 'utf-8');
|
|
51
61
|
|
|
52
62
|
const elapsed = Date.now() - start;
|
|
53
63
|
console.log(` ✓ dist/zquery.js (${sizeKB(fs.readFileSync(OUT_FILE))} KB)`);
|
|
54
64
|
console.log(` ✓ dist/zquery.min.js (${sizeKB(fs.readFileSync(MIN_FILE))} KB)`);
|
|
55
65
|
console.log(` Done in ${elapsed}ms\n`);
|
|
56
66
|
|
|
67
|
+
// --- Create dist/zquery.dist.zip -----------------------------------------
|
|
68
|
+
const root = process.cwd();
|
|
69
|
+
const zipFiles = [
|
|
70
|
+
{ src: OUT_FILE, name: 'zquery.js' },
|
|
71
|
+
{ src: MIN_FILE, name: 'zquery.min.js' },
|
|
72
|
+
{ src: path.join(root, 'LICENSE'), name: 'LICENSE' },
|
|
73
|
+
{ src: path.join(root, 'API.md'), name: 'API.md' },
|
|
74
|
+
{ src: path.join(root, 'README.md'), name: 'README.md' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Minimal ZIP builder (deflate via zlib, no external deps)
|
|
78
|
+
function buildZip(entries) {
|
|
79
|
+
const localHeaders = [];
|
|
80
|
+
const centralHeaders = [];
|
|
81
|
+
let offset = 0;
|
|
82
|
+
|
|
83
|
+
for (const { name, data } of entries) {
|
|
84
|
+
const nameBytes = Buffer.from(name, 'utf-8');
|
|
85
|
+
const compressed = zlib.deflateRawSync(data, { level: 9 });
|
|
86
|
+
const crc = crc32(data);
|
|
87
|
+
const compLen = compressed.length;
|
|
88
|
+
const uncompLen = data.length;
|
|
89
|
+
|
|
90
|
+
// Local file header
|
|
91
|
+
const local = Buffer.alloc(30 + nameBytes.length);
|
|
92
|
+
local.writeUInt32LE(0x04034b50, 0); // signature
|
|
93
|
+
local.writeUInt16LE(20, 4); // version needed
|
|
94
|
+
local.writeUInt16LE(0, 6); // flags
|
|
95
|
+
local.writeUInt16LE(8, 8); // compression: deflate
|
|
96
|
+
local.writeUInt16LE(0, 10); // mod time
|
|
97
|
+
local.writeUInt16LE(0, 12); // mod date
|
|
98
|
+
local.writeUInt32LE(crc, 14);
|
|
99
|
+
local.writeUInt32LE(compLen, 18);
|
|
100
|
+
local.writeUInt32LE(uncompLen, 22);
|
|
101
|
+
local.writeUInt16LE(nameBytes.length, 26);
|
|
102
|
+
local.writeUInt16LE(0, 28); // extra field length
|
|
103
|
+
nameBytes.copy(local, 30);
|
|
104
|
+
|
|
105
|
+
localHeaders.push(Buffer.concat([local, compressed]));
|
|
106
|
+
|
|
107
|
+
// Central directory header
|
|
108
|
+
const central = Buffer.alloc(46 + nameBytes.length);
|
|
109
|
+
central.writeUInt32LE(0x02014b50, 0);
|
|
110
|
+
central.writeUInt16LE(20, 4); // version made by
|
|
111
|
+
central.writeUInt16LE(20, 6); // version needed
|
|
112
|
+
central.writeUInt16LE(0, 8); // flags
|
|
113
|
+
central.writeUInt16LE(8, 10); // compression: deflate
|
|
114
|
+
central.writeUInt16LE(0, 12); // mod time
|
|
115
|
+
central.writeUInt16LE(0, 14); // mod date
|
|
116
|
+
central.writeUInt32LE(crc, 16);
|
|
117
|
+
central.writeUInt32LE(compLen, 20);
|
|
118
|
+
central.writeUInt32LE(uncompLen, 24);
|
|
119
|
+
central.writeUInt16LE(nameBytes.length, 28);
|
|
120
|
+
central.writeUInt16LE(0, 30); // extra field length
|
|
121
|
+
central.writeUInt16LE(0, 32); // comment length
|
|
122
|
+
central.writeUInt16LE(0, 34); // disk number
|
|
123
|
+
central.writeUInt16LE(0, 36); // internal attrs
|
|
124
|
+
central.writeUInt32LE(0, 38); // external attrs
|
|
125
|
+
central.writeUInt32LE(offset, 42); // local header offset
|
|
126
|
+
nameBytes.copy(central, 46);
|
|
127
|
+
centralHeaders.push(central);
|
|
128
|
+
|
|
129
|
+
offset += local.length + compressed.length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const centralDir = Buffer.concat(centralHeaders);
|
|
133
|
+
const eocd = Buffer.alloc(22);
|
|
134
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
135
|
+
eocd.writeUInt16LE(0, 4); // disk number
|
|
136
|
+
eocd.writeUInt16LE(0, 6); // central dir disk
|
|
137
|
+
eocd.writeUInt16LE(entries.length, 8); // entries on disk
|
|
138
|
+
eocd.writeUInt16LE(entries.length, 10); // total entries
|
|
139
|
+
eocd.writeUInt32LE(centralDir.length, 12);
|
|
140
|
+
eocd.writeUInt32LE(offset, 16); // central dir offset
|
|
141
|
+
eocd.writeUInt16LE(0, 20); // comment length
|
|
142
|
+
|
|
143
|
+
return Buffer.concat([...localHeaders, centralDir, eocd]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function crc32(buf) {
|
|
147
|
+
let crc = 0xFFFFFFFF;
|
|
148
|
+
for (let i = 0; i < buf.length; i++) {
|
|
149
|
+
crc ^= buf[i];
|
|
150
|
+
for (let j = 0; j < 8; j++) {
|
|
151
|
+
crc = (crc >>> 1) ^ ((crc & 1) ? 0xEDB88320 : 0);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return (crc ^ 0xFFFFFFFF) >>> 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const entries = zipFiles
|
|
158
|
+
.filter(f => fs.existsSync(f.src))
|
|
159
|
+
.map(f => ({ name: f.name, data: fs.readFileSync(f.src) }));
|
|
160
|
+
|
|
161
|
+
const zipBuf = buildZip(entries);
|
|
162
|
+
const zipPath = path.join(DIST, 'zquery.dist.zip');
|
|
163
|
+
fs.writeFileSync(zipPath, zipBuf);
|
|
164
|
+
console.log(` ✓ dist/zquery.dist.zip (${sizeKB(zipBuf)} KB) — ${entries.length} files`);
|
|
165
|
+
|
|
57
166
|
return { DIST, OUT_FILE, MIN_FILE };
|
|
58
167
|
}
|
|
59
168
|
|