voodoojs 0.6.0 → 0.6.2
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 +65 -13
- package/dist/index.cjs +33 -8
- package/dist/index.js +33 -8
- package/dist/voodoo.full.js +33 -8
- package/dist/voodoo.full.min.js +35 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
No mandatory build step · No runtime dependencies · No Virtual DOM · No configuration required
|
|
6
6
|
|
|
7
|
+
[Website](https://kwy404.github.io/Voodoo.js/) ·
|
|
8
|
+
[Playground](https://kwy404.github.io/Voodoo.js/playground.html) ·
|
|
9
|
+
[Components](https://kwy404.github.io/Voodoo.js/components.html) ·
|
|
10
|
+
[Examples](https://kwy404.github.io/Voodoo.js/examples/) ·
|
|
11
|
+
[Documentation](https://kwy404.github.io/Voodoo.js/docs/) ·
|
|
12
|
+
[GitHub](https://github.com/kwy404/Voodoo.js)
|
|
13
|
+
|
|
7
14
|
---
|
|
8
15
|
|
|
9
16
|
## Install
|
|
@@ -15,14 +22,16 @@ npm install voodoojs
|
|
|
15
22
|
Or drop it into a page, with nothing else:
|
|
16
23
|
|
|
17
24
|
```html
|
|
18
|
-
<script src="https://cdn.jsdelivr.net/npm/voodoojs/dist/voodoo.min.js" defer></script>
|
|
25
|
+
<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.6/dist/voodoo.min.js" defer></script>
|
|
19
26
|
|
|
20
27
|
<div v-data="{ count: 0 }">
|
|
21
28
|
<button @click="count++">Clicked { count } times</button>
|
|
22
29
|
</div>
|
|
23
30
|
```
|
|
24
31
|
|
|
25
|
-
That page is a complete application. There is no build step, no bundler and no
|
|
32
|
+
That page is a complete application. There is no build step, no bundler and no
|
|
33
|
+
configuration. The tag is pinned to the `0.6` line, so patch releases arrive
|
|
34
|
+
without an edit; pin the exact version if you would rather approve each one.
|
|
26
35
|
|
|
27
36
|
## Two ways to write it
|
|
28
37
|
|
|
@@ -45,30 +54,73 @@ effect(() => console.log(state.count));
|
|
|
45
54
|
state.count++;
|
|
46
55
|
```
|
|
47
56
|
|
|
57
|
+
## What you get
|
|
58
|
+
|
|
59
|
+
Reactivity, components, a router, HTTP, forms with validation and masks, input
|
|
60
|
+
masks, i18n, charts, motion, sound, a devtools inspector, and 29 ready-made
|
|
61
|
+
components that need no registration — writing the tag is the whole usage.
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<v-button variant="primary">Save</v-button>
|
|
65
|
+
<v-input label="Email" type="email"></v-input>
|
|
66
|
+
<v-table :columns="cols" :rows="people"></v-table>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## No eval, anywhere
|
|
70
|
+
|
|
71
|
+
Expressions run through a lexer, a Pratt parser and an interpreter, never `eval`
|
|
72
|
+
or `new Function`. That is why the library works under a strict Content Security
|
|
73
|
+
Policy with no `unsafe-eval`, which a browser test proves by loading it under
|
|
74
|
+
`script-src 'self'` and asserting no policy violation is raised.
|
|
75
|
+
|
|
76
|
+
## It cleans up after itself
|
|
77
|
+
|
|
78
|
+
Once a directive is installed, its attribute is read into memory and removed from
|
|
79
|
+
the document. What ships to the page is ordinary HTML, with no framework residue
|
|
80
|
+
in the inspector. Turn it off with `V.config.cleanAttributes = false` if you need
|
|
81
|
+
the attributes for something else.
|
|
82
|
+
|
|
48
83
|
## Builds
|
|
49
84
|
|
|
50
|
-
| File | What it carries |
|
|
51
|
-
| --- | --- |
|
|
52
|
-
| `dist/voodoo.core.min.js` | Reactivity, expressions, DOM engine, components, core directives |
|
|
53
|
-
| `dist/voodoo.min.js` | The above plus forms, validation, masks, UI and HTTP
|
|
54
|
-
| `dist/voodoo.full.min.js` | Everything: charts, motion, router, i18n, sound
|
|
85
|
+
| File | What it carries | Gzip |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `dist/voodoo.core.min.js` | Reactivity, expressions, DOM engine, components, core directives | 46 KB |
|
|
88
|
+
| `dist/voodoo.min.js` | The above plus forms, validation, masks, UI and HTTP | 83 KB |
|
|
89
|
+
| `dist/voodoo.full.min.js` | Everything: charts, motion, router, i18n, sound, devtools | 129 KB |
|
|
55
90
|
|
|
56
|
-
Module entry points are
|
|
91
|
+
Module entry points are published for bundlers, in ESM and CJS, with types:
|
|
57
92
|
|
|
58
93
|
```js
|
|
59
94
|
import { reactive } from 'voodoojs/reactivity';
|
|
60
95
|
import { http } from 'voodoojs/http';
|
|
61
96
|
```
|
|
62
97
|
|
|
63
|
-
|
|
98
|
+
## Performance
|
|
64
99
|
|
|
65
|
-
|
|
100
|
+
A 1,000-row keyed list, median of 30 samples, against the same document, every
|
|
101
|
+
framework bundled production and minified. Lower is better.
|
|
102
|
+
|
|
103
|
+
| | create 1k | update 1 in 10 | clear 1k |
|
|
104
|
+
| --- | ---: | ---: | ---: |
|
|
105
|
+
| vanilla JS | 48.74 | 7.52 | 21.06 |
|
|
106
|
+
| Preact 10.29.8 | 91.62 | 2.59 | 29.48 |
|
|
107
|
+
| **Voodoo.js** | **97.70** | **5.42** | **30.44** |
|
|
108
|
+
| React 19.2.8 | 100.23 | 4.63 | 33.59 |
|
|
109
|
+
| Vue 3.5.42 | 110.56 | 19.21 | 31.65 |
|
|
110
|
+
| Solid 1.9.15 | 111.63 | 0.91 | 19.99 |
|
|
111
|
+
| Alpine 3.17.1 | 179.47 | 104.51 | 31.39 |
|
|
66
112
|
|
|
67
|
-
|
|
113
|
+
Read honestly: hand-written vanilla still builds a list twice as fast, and Voodoo
|
|
114
|
+
is by far the largest bundle in that table. If size is your main constraint,
|
|
115
|
+
Alpine and Preact are the honest recommendation. Method and per-framework
|
|
116
|
+
adapters are in the repository.
|
|
68
117
|
|
|
69
|
-
|
|
118
|
+
## Documentation
|
|
70
119
|
|
|
71
|
-
|
|
120
|
+
Everything lives at **[kwy404.github.io/Voodoo.js](https://kwy404.github.io/Voodoo.js/)**:
|
|
121
|
+
a guide, an API reference, 13 working example applications, a playground with 26
|
|
122
|
+
runnable examples, and the component gallery. The site is built with Voodoo.js
|
|
123
|
+
itself and has no build step of its own.
|
|
72
124
|
|
|
73
125
|
## License
|
|
74
126
|
|
package/dist/index.cjs
CHANGED
|
@@ -7732,6 +7732,33 @@ function buildUrl(location2) {
|
|
|
7732
7732
|
const base = settings2.base === "/" ? "" : settings2.base.replace(/\/$/, "");
|
|
7733
7733
|
return `${base}${suffix}` || "/";
|
|
7734
7734
|
}
|
|
7735
|
+
var historyRefused = false;
|
|
7736
|
+
var writingHash = false;
|
|
7737
|
+
function writeUrl(state2, url2, replace) {
|
|
7738
|
+
if (!historyRefused) {
|
|
7739
|
+
try {
|
|
7740
|
+
if (replace) window.history.replaceState(state2, "", url2);
|
|
7741
|
+
else window.history.pushState(state2, "", url2);
|
|
7742
|
+
return;
|
|
7743
|
+
} catch (error) {
|
|
7744
|
+
if (!(error instanceof Error) || error.name !== "SecurityError") throw error;
|
|
7745
|
+
historyRefused = true;
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
if (settings2.mode !== "hash") return;
|
|
7749
|
+
const marker = url2.indexOf("#");
|
|
7750
|
+
if (marker < 0) return;
|
|
7751
|
+
const hash = url2.slice(marker);
|
|
7752
|
+
if (window.location.hash === hash) return;
|
|
7753
|
+
writingHash = true;
|
|
7754
|
+
try {
|
|
7755
|
+
window.location.hash = hash;
|
|
7756
|
+
} finally {
|
|
7757
|
+
setTimeout(() => {
|
|
7758
|
+
writingHash = false;
|
|
7759
|
+
}, 0);
|
|
7760
|
+
}
|
|
7761
|
+
}
|
|
7735
7762
|
function compileRoute(pattern, record) {
|
|
7736
7763
|
const clean = pattern === "*" ? "*" : normalizePath(pattern);
|
|
7737
7764
|
const raw = clean === "*" ? ["*"] : clean.split("/").filter(Boolean);
|
|
@@ -7918,8 +7945,7 @@ async function navigate(target2, options = {}) {
|
|
|
7918
7945
|
const key = uid("rota");
|
|
7919
7946
|
const historyState = { ...options.state ?? {}, [HISTORY_KEY]: key };
|
|
7920
7947
|
const url2 = buildUrl(destination);
|
|
7921
|
-
|
|
7922
|
-
else window.history.pushState(historyState, "", url2);
|
|
7948
|
+
writeUrl(historyState, url2, options.replace === true);
|
|
7923
7949
|
currentKey = key;
|
|
7924
7950
|
applyLocation(destination);
|
|
7925
7951
|
if (options.scroll !== false) scheduleScroll(destination, from, null);
|
|
@@ -7932,17 +7958,14 @@ async function navigate(target2, options = {}) {
|
|
|
7932
7958
|
return true;
|
|
7933
7959
|
}
|
|
7934
7960
|
async function onHistoryChange(event) {
|
|
7961
|
+
if (writingHash) return;
|
|
7935
7962
|
const { path, query: query2, hash } = readLocation();
|
|
7936
7963
|
const destination = locationFor(path, query2, hash);
|
|
7937
7964
|
const from = snapshot();
|
|
7938
7965
|
if (destination.fullPath === from.fullPath) return;
|
|
7939
7966
|
const verdict = await runGuards(destination, from);
|
|
7940
7967
|
if (verdict === false) {
|
|
7941
|
-
|
|
7942
|
-
{ [HISTORY_KEY]: currentKey },
|
|
7943
|
-
"",
|
|
7944
|
-
buildUrl(from)
|
|
7945
|
-
);
|
|
7968
|
+
writeUrl({ [HISTORY_KEY]: currentKey }, buildUrl(from), true);
|
|
7946
7969
|
devtoolsBus.emit("navigation", {
|
|
7947
7970
|
from: from.fullPath,
|
|
7948
7971
|
to: destination.fullPath,
|
|
@@ -7985,6 +8008,8 @@ function stopRouter() {
|
|
|
7985
8008
|
window.removeEventListener("popstate", historyListener);
|
|
7986
8009
|
window.removeEventListener("hashchange", historyListener);
|
|
7987
8010
|
window.removeEventListener("beforeunload", saveScroll);
|
|
8011
|
+
historyRefused = false;
|
|
8012
|
+
writingHash = false;
|
|
7988
8013
|
}
|
|
7989
8014
|
async function enterInitialRoute() {
|
|
7990
8015
|
if (typeof window === "undefined") return;
|
|
@@ -8005,7 +8030,7 @@ async function enterInitialRoute() {
|
|
|
8005
8030
|
break;
|
|
8006
8031
|
}
|
|
8007
8032
|
currentKey = uid("rota");
|
|
8008
|
-
|
|
8033
|
+
writeUrl({ [HISTORY_KEY]: currentKey }, buildUrl(destination), true);
|
|
8009
8034
|
applyLocation(destination);
|
|
8010
8035
|
if (destination.hash) scheduleScroll(destination, from, null);
|
|
8011
8036
|
settings2.afterEach?.(snapshot(), from);
|
package/dist/index.js
CHANGED
|
@@ -128,6 +128,33 @@ function buildUrl(location) {
|
|
|
128
128
|
const base = settings.base === "/" ? "" : settings.base.replace(/\/$/, "");
|
|
129
129
|
return `${base}${suffix}` || "/";
|
|
130
130
|
}
|
|
131
|
+
var historyRefused = false;
|
|
132
|
+
var writingHash = false;
|
|
133
|
+
function writeUrl(state2, url2, replace) {
|
|
134
|
+
if (!historyRefused) {
|
|
135
|
+
try {
|
|
136
|
+
if (replace) window.history.replaceState(state2, "", url2);
|
|
137
|
+
else window.history.pushState(state2, "", url2);
|
|
138
|
+
return;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (!(error instanceof Error) || error.name !== "SecurityError") throw error;
|
|
141
|
+
historyRefused = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (settings.mode !== "hash") return;
|
|
145
|
+
const marker = url2.indexOf("#");
|
|
146
|
+
if (marker < 0) return;
|
|
147
|
+
const hash = url2.slice(marker);
|
|
148
|
+
if (window.location.hash === hash) return;
|
|
149
|
+
writingHash = true;
|
|
150
|
+
try {
|
|
151
|
+
window.location.hash = hash;
|
|
152
|
+
} finally {
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
writingHash = false;
|
|
155
|
+
}, 0);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
131
158
|
function compileRoute(pattern, record) {
|
|
132
159
|
const clean = pattern === "*" ? "*" : normalizePath(pattern);
|
|
133
160
|
const raw = clean === "*" ? ["*"] : clean.split("/").filter(Boolean);
|
|
@@ -314,8 +341,7 @@ async function navigate(target, options = {}) {
|
|
|
314
341
|
const key = uid("rota");
|
|
315
342
|
const historyState = { ...options.state ?? {}, [HISTORY_KEY]: key };
|
|
316
343
|
const url2 = buildUrl(destination);
|
|
317
|
-
|
|
318
|
-
else window.history.pushState(historyState, "", url2);
|
|
344
|
+
writeUrl(historyState, url2, options.replace === true);
|
|
319
345
|
currentKey = key;
|
|
320
346
|
applyLocation(destination);
|
|
321
347
|
if (options.scroll !== false) scheduleScroll(destination, from, null);
|
|
@@ -328,17 +354,14 @@ async function navigate(target, options = {}) {
|
|
|
328
354
|
return true;
|
|
329
355
|
}
|
|
330
356
|
async function onHistoryChange(event) {
|
|
357
|
+
if (writingHash) return;
|
|
331
358
|
const { path, query: query2, hash } = readLocation();
|
|
332
359
|
const destination = locationFor(path, query2, hash);
|
|
333
360
|
const from = snapshot();
|
|
334
361
|
if (destination.fullPath === from.fullPath) return;
|
|
335
362
|
const verdict = await runGuards(destination, from);
|
|
336
363
|
if (verdict === false) {
|
|
337
|
-
|
|
338
|
-
{ [HISTORY_KEY]: currentKey },
|
|
339
|
-
"",
|
|
340
|
-
buildUrl(from)
|
|
341
|
-
);
|
|
364
|
+
writeUrl({ [HISTORY_KEY]: currentKey }, buildUrl(from), true);
|
|
342
365
|
devtoolsBus.emit("navigation", {
|
|
343
366
|
from: from.fullPath,
|
|
344
367
|
to: destination.fullPath,
|
|
@@ -381,6 +404,8 @@ function stopRouter() {
|
|
|
381
404
|
window.removeEventListener("popstate", historyListener);
|
|
382
405
|
window.removeEventListener("hashchange", historyListener);
|
|
383
406
|
window.removeEventListener("beforeunload", saveScroll);
|
|
407
|
+
historyRefused = false;
|
|
408
|
+
writingHash = false;
|
|
384
409
|
}
|
|
385
410
|
async function enterInitialRoute() {
|
|
386
411
|
if (typeof window === "undefined") return;
|
|
@@ -401,7 +426,7 @@ async function enterInitialRoute() {
|
|
|
401
426
|
break;
|
|
402
427
|
}
|
|
403
428
|
currentKey = uid("rota");
|
|
404
|
-
|
|
429
|
+
writeUrl({ [HISTORY_KEY]: currentKey }, buildUrl(destination), true);
|
|
405
430
|
applyLocation(destination);
|
|
406
431
|
if (destination.hash) scheduleScroll(destination, from, null);
|
|
407
432
|
settings.afterEach?.(snapshot(), from);
|
package/dist/voodoo.full.js
CHANGED
|
@@ -7855,6 +7855,33 @@ Suggestion: attribute expressions accept a single value. If the logic spans more
|
|
|
7855
7855
|
const base = settings2.base === "/" ? "" : settings2.base.replace(/\/$/, "");
|
|
7856
7856
|
return `${base}${suffix}` || "/";
|
|
7857
7857
|
}
|
|
7858
|
+
var historyRefused = false;
|
|
7859
|
+
var writingHash = false;
|
|
7860
|
+
function writeUrl(state2, url2, replace) {
|
|
7861
|
+
if (!historyRefused) {
|
|
7862
|
+
try {
|
|
7863
|
+
if (replace) window.history.replaceState(state2, "", url2);
|
|
7864
|
+
else window.history.pushState(state2, "", url2);
|
|
7865
|
+
return;
|
|
7866
|
+
} catch (error) {
|
|
7867
|
+
if (!(error instanceof Error) || error.name !== "SecurityError") throw error;
|
|
7868
|
+
historyRefused = true;
|
|
7869
|
+
}
|
|
7870
|
+
}
|
|
7871
|
+
if (settings2.mode !== "hash") return;
|
|
7872
|
+
const marker = url2.indexOf("#");
|
|
7873
|
+
if (marker < 0) return;
|
|
7874
|
+
const hash = url2.slice(marker);
|
|
7875
|
+
if (window.location.hash === hash) return;
|
|
7876
|
+
writingHash = true;
|
|
7877
|
+
try {
|
|
7878
|
+
window.location.hash = hash;
|
|
7879
|
+
} finally {
|
|
7880
|
+
setTimeout(() => {
|
|
7881
|
+
writingHash = false;
|
|
7882
|
+
}, 0);
|
|
7883
|
+
}
|
|
7884
|
+
}
|
|
7858
7885
|
function compileRoute(pattern, record) {
|
|
7859
7886
|
const clean = pattern === "*" ? "*" : normalizePath(pattern);
|
|
7860
7887
|
const raw = clean === "*" ? ["*"] : clean.split("/").filter(Boolean);
|
|
@@ -8045,8 +8072,7 @@ Suggestion: attribute expressions accept a single value. If the logic spans more
|
|
|
8045
8072
|
const key = uid("rota");
|
|
8046
8073
|
const historyState = { ...(_a2 = options.state) != null ? _a2 : {}, [HISTORY_KEY]: key };
|
|
8047
8074
|
const url2 = buildUrl(destination);
|
|
8048
|
-
|
|
8049
|
-
else window.history.pushState(historyState, "", url2);
|
|
8075
|
+
writeUrl(historyState, url2, options.replace === true);
|
|
8050
8076
|
currentKey = key;
|
|
8051
8077
|
applyLocation(destination);
|
|
8052
8078
|
if (options.scroll !== false) scheduleScroll(destination, from, null);
|
|
@@ -8060,17 +8086,14 @@ Suggestion: attribute expressions accept a single value. If the logic spans more
|
|
|
8060
8086
|
}
|
|
8061
8087
|
async function onHistoryChange(event) {
|
|
8062
8088
|
var _a2, _b;
|
|
8089
|
+
if (writingHash) return;
|
|
8063
8090
|
const { path, query: query2, hash } = readLocation();
|
|
8064
8091
|
const destination = locationFor(path, query2, hash);
|
|
8065
8092
|
const from = snapshot();
|
|
8066
8093
|
if (destination.fullPath === from.fullPath) return;
|
|
8067
8094
|
const verdict = await runGuards(destination, from);
|
|
8068
8095
|
if (verdict === false) {
|
|
8069
|
-
|
|
8070
|
-
{ [HISTORY_KEY]: currentKey },
|
|
8071
|
-
"",
|
|
8072
|
-
buildUrl(from)
|
|
8073
|
-
);
|
|
8096
|
+
writeUrl({ [HISTORY_KEY]: currentKey }, buildUrl(from), true);
|
|
8074
8097
|
devtoolsBus.emit("navigation", {
|
|
8075
8098
|
from: from.fullPath,
|
|
8076
8099
|
to: destination.fullPath,
|
|
@@ -8113,6 +8136,8 @@ Suggestion: attribute expressions accept a single value. If the logic spans more
|
|
|
8113
8136
|
window.removeEventListener("popstate", historyListener);
|
|
8114
8137
|
window.removeEventListener("hashchange", historyListener);
|
|
8115
8138
|
window.removeEventListener("beforeunload", saveScroll);
|
|
8139
|
+
historyRefused = false;
|
|
8140
|
+
writingHash = false;
|
|
8116
8141
|
}
|
|
8117
8142
|
async function enterInitialRoute() {
|
|
8118
8143
|
var _a2;
|
|
@@ -8134,7 +8159,7 @@ Suggestion: attribute expressions accept a single value. If the logic spans more
|
|
|
8134
8159
|
break;
|
|
8135
8160
|
}
|
|
8136
8161
|
currentKey = uid("rota");
|
|
8137
|
-
|
|
8162
|
+
writeUrl({ [HISTORY_KEY]: currentKey }, buildUrl(destination), true);
|
|
8138
8163
|
applyLocation(destination);
|
|
8139
8164
|
if (destination.hash) scheduleScroll(destination, from, null);
|
|
8140
8165
|
(_a2 = settings2.afterEach) == null ? void 0 : _a2.call(settings2, snapshot(), from);
|