The framework that stays in your HTML
Reactivity, components, HTTP, forms and a UI kit, written as attributes on the markup you already have. One script tag. No build step, no configuration, no dependencies at runtime.
And JSX, in a plain .html file. No compiler, no bundler, no
transform. { fruits.map(f => (<li>{ f }</li>)) } written
straight into the markup, reactive, under a strict CSP.
Try it in the playground.
<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.13.0/dist/voodoo.full.min.js" defer></script>
<div v-data="{ n: 0 }">
<output>{ n }</output>
<button @click="n--">-</button>
<button @click="n++">+</button>
<p>double: { n * 2 }</p>
</div>
double: 0 · 0 clicks so far
Why
Three things it does differently
Nothing to compile
The script tag is the whole installation. No bundler, no transpiler, no config
file, no JSX. Open the HTML file and it works, including from
file://.
It cleans up after itself
Once a directive is installed, its attribute is read into memory and removed from the document. What ships to the page is ordinary HTML, with no framework residue in the inspector.
No eval, anywhere
Expressions run through a lexer, a Pratt parser and an interpreter, never
eval or new Function. That is why it works under a
strict CSP with no unsafe-eval, proven by a browser test.
The DOM afterwards
Your markup does not become framework output
Inspect the paragraph below with your browser's dev tools. The attributes that made it reactive are gone from the document; the binding is still live.
<p class="greeting"
v-data="{ name: 'Vudu' }"
v-text="'Hello, ' + name"></p>
<p class="greeting">Hello, Vudu</p>
Same counter
The same feature, four ways
A counter with two buttons, a derived value and a screen that keeps up. The result is identical in all four. What changes is how much you write, and how much you install before you start.
<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.13.0/dist/voodoo.full.min.js" defer></script>
<div v-data="{ n: 0 }">
<output>{ n }</output>
<button @click="n--">-</button>
<button @click="n++">+</button>
<p>double: { n * 2 }</p>
</div>
7 lines0 build steps0 dependencies
<div>
<output id="out">0</output>
<button id="dec">-</button>
<button id="inc">+</button>
<p id="dbl">double: 0</p>
</div>
<script>
let n = 0;
const render = () => {
out.textContent = n;
dbl.textContent = 'double: ' + n * 2;
};
inc.onclick = () => { n++; render(); };
dec.onclick = () => { n--; render(); };
</script>
18 lines0 build stepsevery update wired by hand
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<div id="app">
<output>0</output>
<button class="dec">-</button>
<button class="inc">+</button>
<p class="dbl">double: 0</p>
</div>
<script>
let n = 0;
function render() {
$('#app output').text(n);
$('#app .dbl').text('double: ' + n * 2);
}
$('#app .inc').on('click', () => { n++; render(); });
$('#app .dec').on('click', () => { n--; render(); });
</script>
19 lines1 dependencystill re-renders by hand
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">
<output>{{ n }}</output>
<button @click="n--">-</button>
<button @click="n++">+</button>
<p>double: {{ n * 2 }}</p>
</div>
<script>
Vue.createApp({
data: () => ({ n: 0 })
}).mount('#app');
</script>
13 lines1 dependencya mount call, and an app root
Voodoo did not invent reactivity. It put reactivity back inside the HTML without charging a build step for it.
Measured
How it performs against the others
Seven implementations of the same 1,000-row list, all bundled production and minified, run back to back in one process against the same document. Median of 30 samples, in milliseconds, lower is better. After each run every framework's DOM is compared with the hand-written baseline, so anything producing different output is excluded rather than credited with a fast time.
| Framework | Create 1k | Update 1 in 10 | Clear 1k |
|---|---|---|---|
| vanilla JS | 39.51 | 6.65 | 20.04 |
| Preact 10.29.8 | 71.03 | 2.73 | 30.68 |
| Voodoo.js 0.13.0 | 77.47 | 4.69 | 30.14 |
| Vue 3.5.42 | 78.72 | 14.29 | 32.84 |
| Solid 1.9.15 | 80.13 | 0.90 | 21.85 |
| React 19.2.8 | 81.22 | 4.65 | 33.55 |
| Alpine 3.17.1 | 157.06 | 111.29 | 32.76 |
Read honestly: on create Voodoo is third of seven, behind hand-written vanilla and Preact, and vanilla still builds a list twice as fast as we do. On update we are now faster than that hand-written baseline — 4.69 ms against 6.65 — and level with React, though Solid and Preact are ahead and they get there with a compiler. The update column is the noisiest of the three and should be read as a range rather than a ranking. Voodoo is also by far the largest bundle in this table, and if size is your main constraint, Alpine and Preact are the honest recommendation.
Get started
Two ways in, both about a minute
<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.13.0/dist/voodoo.full.min.js" defer></script>
That is the entire installation. The library starts itself once the page is
ready. Pinned to the 0.5 line, so patches arrive without an edit.
npm install voodoojs
ESM and CJS with types, plus separate entry points such as
voodoojs/reactivity and voodoojs/http when you want
only a piece.
In the box
Twenty-nine components you do not install
They are custom elements the library defines when it starts, so writing the tag is the whole usage. Everything below is running here, on this page.
<v-button variant="primary">Save</v-button>
<v-badge tone="success">Active</v-badge>
<v-progress value="70"></v-progress>
<v-rating value="4"></v-rating>
Dogfood
This site is built with Voodoo.js
Not a marketing line. The page you are reading has no build step and no framework
code of its own. The tabs above, the counter in the hero, the language picker, the
theme toggle, the mobile menu and the Ctrl+K palette are all directives on this
HTML, running from the same
voodoo.full.min.js the install section hands you.
View source, or press Ctrl+K and pick "View this page's source". If the framework could not carry a real site, you would find out here first.
- 1,197
- unit tests, plus 44 in real Chromium
- 0
- runtime dependencies
- 46 KB
- core build, gzipped
- 0
- build steps, here or in your project