bitpush
8 hours ago
Hows this different from htmx?
8 hours ago
Hows this different from htmx?
8 hours ago
I've been building a JavaScript framework called *Voodoo.js*. I'd rather get torn apart in the comments than hear that it looks cool.
It started with one question: how much JavaScript do we really need for everyday interactive interfaces?
For a lot of apps I don't want a component tree, a bundler, five libraries and state managed elsewhere, all wired back into HTML. Sometimes I just want to keep writing HTML.
## The 30-second version
```html <div v-data="{ count: 0 }"> <button @click="count--">-</button> <strong>{ count }</strong> <button @click="count++">+</button> </div> ```
No `querySelector`. No `addEventListener`. No Virtual DOM. Voodoo observes the real DOM and connects HTML directives to a fine-grained reactive system built on `Proxy`.
## Why another JS framework?
Voodoo isn't trying to replace React, Vue or Svelte. The target is projects where the server already renders the HTML — Laravel, Django, Rails, PHP, Go templates — plus small frontends where a full SPA is overkill.
Philosophically it sits near Alpine.js, HTMX and petite-vue. The difference I was chasing: HTTP, forms, validation, state, components, routing and reactivity should feel like one system, not five glued together.
## Declarative HTTP
Instead of wiring a button by hand:
```js button.addEventListener('click', async () => { if (!confirm('Delete user?')) return await fetch('/api/users/42', { method: 'DELETE' }) showToast('User deleted') }) ```
You declare the intent:
```html <button v-delete="/api/users/42" v-confirm="Delete this user?" v-toast-success="User deleted"> Delete </button> ```
Forms work the same way:
```html <form v-submit="/api/users" v-validate v-toast-success="User saved"> <input name="email" type="email" required> <button type="submit">Save</button> </form> ```
The goal isn't to remove JavaScript. It's to stop writing JavaScript that only restates what the HTML already says. When you need more, plain JS is right there.
## No `eval()`, no `new Function()`
The lazy way to implement `@click="count++"` is `eval()`. Voodoo does neither: expressions go through a real engine — Lexer → Pratt Parser → AST → Interpreter — parsed and interpreted by Voodoo itself, so the framework controls what syntax is supported and what expressions can reach.
## Fine-grained reactivity
The reactive layer uses `Proxy`, tracking dependencies per object and per property. No Virtual DOM diff — the real DOM is patched directly. The API should feel familiar:
```js reactive() ref() computed() watch() watchEffect() effectScope() toRaw() markRaw() ```
It runs from a plain `<script>` tag with no build step, or as ESM: `import { reactive } from 'voodoojs/reactivity'`.
## Where it fits
Server-rendered HTML + Voodoo.js → reactive interface. In Laravel:
```blade @foreach($users as $user) <button v-delete="/users/{{ $user->id }}" v-confirm="Delete user?"> Delete </button> @endforeach ```
The backend keeps rendering the app. No separate frontend application.
## The honest part
What began as an experiment grew into reactivity, directives, components, stores, HTTP, forms, validation, routing, i18n, charts, devtools and a CLI. That growth is now the problem. It doesn't need more features — it needs stability: security hardening, parser edge cases, tests, API stability, benchmarks, docs.
I'd rather ship a small set of features people can trust in production than a huge one nobody can.
## What I'm asking for
If you work with Alpine.js, HTMX, Vue, petite-vue, Stimulus or Livewire:
1. What would stop you from using this? 2. Which parts of the API feel intuitive? 3. Which feel unnecessary? 4. What would you need before calling it production-ready?
If something looks questionable in the architecture, API or security model, tell me. That's the feedback I need.
*GitHub:* https://github.com/kwy404/Voodoo.js