← Menut · Comparison with other frameworks →

Inside Menut: reactivity without a compiler

Why the framework exists, how it works, and what it chose to give up.

Menut is Weblin's answer to a specific question: what if a page needs real client-side state — a wizard, a filtered list, a counter — but you still refuse a build step? Here's how it's built, and why.

One file, one object: window._

Every other reactive micro-framework isolates state per component (x-data in Alpine, v-scope in Petite-Vue). Menut puts all of it in a single global, window._.

This is a deliberate rejection of encapsulation-by-default. The trade-off is real: a global namespace can collide, and it doesn't enforce component boundaries the way scoped state does. What it buys back: you can open devtools, type _, and see the entire application state in one object — no hunting through DOM nodes to find where a value lives. For small-to-medium apps (the ones Menut targets — it's explicitly not trying to replace Vue/React for large SPAs), that transparency is worth more than the isolation.

Granular reactivity, not a virtual DOM

When a value in window._ changes, Menut updates only the DOM nodes bound to that value — no diffing, no re-render pass, no virtual tree to reconcile.

This is the same fine-grained approach Vue 3's reactivity core and Solid popularized, but implemented directly rather than imported. Petite-Vue gets this by depending on @vue/reactivity; Menut rewrites the essence of it in-file so there's no dependency to vendor, version, or audit. The cost of that choice is maintenance burden on the Menut side — every edge case Vue's reactivity team already solved has to be re-solved here. The benefit is a single file with zero supply-chain surface.

define-components: declarative, with a build-free escape hatch

Components can be defined inline in the page or as external Single File Components — but "SFC" here means an HTML file with a <script> and a <template>, not a compiled .vue file. The browser parses it; nothing transforms it first.

This is where the unsafe-eval requirement comes from: component <script> blocks and binding expressions execute via new Function, because there's no compile step to turn them into safe, pre-analyzed code ahead of time. It's an honest trade — say so up front, since it's the one real adoption blocker for CSP-strict environments.

Why not just use Alpine or Petite-Vue?

Both are excellent and mature. The reason to reach for Menut instead is philosophical as much as technical: it's built to be read, not just used — small enough that a developer can audit the entire reactivity implementation in the time it takes to read one file, rather than trusting an external dependency's internals. If that's not a priority for your project, Alpine or Petite-Vue are equally valid, more battle-tested choices — and the next comparison table makes that trade-off explicit rather than pretending Menut is strictly better.

A framework you can read is a framework you can trust. A framework you can't read is a framework you depend on.