Un micro-framework reactivo para la web. Sin Virtual DOM, sin magia negra — solo JavaScript corriendo directo en el DOM.
No se recomienda probar en proyectos serios en producción ni nada.
Era una prueba de concepto que quise desarrollar con fines educativos e investigativos. Quería entender cómo funcionaba esa "caja" negra en muchos frameworks reactivos como React, Vue y Angular, que muchos de nosotros utilizamos y asumimos que lo que ocurre atrás es magia.
También es una carta de amor a mi gata Moonie :)
La mayoría de los frameworks modernos traen un compilador, un runtime de 40kb, un diffing algorithm y tres capas de abstracción para hacer lo que el browser ya sabe hacer solo. Moonie apuesta por lo contrario: reactividad granular que toca exactamente lo que cambió, nada más.
En la documentación podrás ver todo lo necesario para construir con Moonie. Desde ejemplos hasta API Reference, y eventualmente un Playground.
Pokédex: Es una APP desarrollada usando como stack Moonie, Vite y TailwindCSS. Integra la PokeAPI.
npm:
npm install github:Alwexis/Mooniepnpm:
pnpm add github:Alwexis/Moonievalue() — el átomo básico. Un contenedor reactivo para cualquier valor primitivo.
const count = value(0)
count.get() // 0
count.set(1) // notifica a todos los efectos que dependen de countreactive() — igual que value pero para objetos. Usa Proxy internamente, así que cualquier propiedad que leas dentro de un effect se trackea automáticamente.
const user = reactive({ name: 'Luna', age: 25 })
user.name = 'Sol' // reactivocomputed() — valores derivados. Se recalcula solo cuando sus dependencias cambian, y solo si alguien lo necesita (lazy).
const double = computed(() => count.get() * 2)
double.get() // siempre fresco, nunca recalcula de máseffect() — ejecuta una función y la re-ejecuta cada vez que alguna de sus dependencias cambia.
effect(() => {
console.log('el count es:', count.get())
})watch() — como effect pero solo dispara cuando el valor cambia, no en la ejecución inicial. Te da el valor anterior y el nuevo.
watch(count, (newVal, oldVal) => {
console.log(`cambió de ${oldVal} a ${newVal}`)
})onMount(() => {
// el componente ya está en el DOM
})
onUnmount(() => {
// el componente fue removido, limpia tus listeners
})Rutas declarativas con soporte para parámetros dinámicos.
const routes: Route[] = [
{ path: '/', component: Home },
{ path: '/pokemon/:id', component: PokemonDetail },
]Montaje del Router.
mount("#app").router(routes);Parámetros de rutas.
// dentro del componente
const params = useParams()
params.id // el valor del parámetroNavegación.
navigate('/pokemon/25') // programmatic navigationMoonie compila archivos .mn a llamadas h() en build time. La sintaxis es HTML con esteroides.
<script>
import { value } from '@moonie/reactive/value'
const count = value(0)
</script>
<template>
<div>
<p>{{ count.get() }}</p>
<button @click="count.set(count.get() + 1)">+1</button>
</div>
</template>Directivas:
@if (user.get()) {
<p>Hola {{ user.get().name }}</p>
} @else {
<p>No hay usuario</p>
}@for (item of items.get(); key: item.id) {
<div>{{ item.name }}</div>
} @empty {
<p>Lista vacía</p>
}Binding reactivo:
<input :value="search.get()" @input="search.set($event.target.value)" />
<div :class="active.get() ? 'bg-blue-500' : 'bg-gray-200'"></div>El runtime completo pesa menos de 5kb gzipped.
Experimental. La API puede cambiar. Úsalo bajo tu propio riesgo y disfrútalo.
