Splitting the Compiler from the Runtime in Levelo JS
Yesterday was focused on improving the architecture of Levelo JS rather than adding flashy features.
One of the biggest changes was separating the Vite compiler from the core framework.
Previously, the compiler lived inside the main levelojs package. Now it has been extracted into its own package:
Installation:
npm install levelojs vite-plugin-levelojs
or
pnpm add levelojs vite-plugin-levelojs
This makes the runtime cleaner, easier to maintain, and allows the compiler to evolve independently.
What's New
Batch Updates
Levelo JS now includes a new batch() API.
batch(() => {
setCount(v => v + 1);
setUser("MotionMind");
setCount(v => v + 1);
});
Multiple state updates are grouped together, reducing unnecessary reactive executions and improving performance.
Reactive Input Tracking
Reactive value binding for form elements has been improved.
Previously, updating a signal didn't automatically synchronize the value of inputs in every case.
Now developers can simply write:
const [message, setMessage] = state("");
<input
value={message()}
onInput={(e) => setMessage(e.target.value)}
/>
The input stays synchronized with the reactive state automatically.
Dedicated Vite Plugin
The compiler is now available as an official package:
vite-plugin-levelojs
It compiles JSX into optimized h() calls at build time, eliminating the need for a Virtual DOM.
Runtime Refactoring
The internal JSX runtime has also been reorganized.
The h() factory has been extracted into its own runtime module for better code organization and maintainability.
Verified in a Real Project
After publishing the new packages, everything was tested inside a real Vite application.
The compiler, runtime, JSX transformation, and reactivity all worked as expected.
This update isn't about adding new UI features.
It's about making the foundation stronger before building bigger things on top of it.
More improvements are coming soon.
GitHub: https://github.com/motionmind2007/levelojs
Documentation: https://levelojs.motionmind.me

Top comments (2)
Interesting read! I'm curious if this split primarily aims at reducing the runtime footprint,
Thanks! Reducing the runtime footprint was definitely one of the benefits, but it wasn't the primary goal.
The main reason for splitting the compiler was to make LeveloJS bundler-independent. We don't want developers to be limited to Vite forever. In the future, we're planning to support other bundlers (such as Webpack) through dedicated compiler plugins, allowing developers to choose the tooling that best fits their workflow.
Another reason is that compiler features are growing independently from the runtime. For example, better compile-time error reporting, showing the exact source line, code highlighting, and other developer experience improvements require additional compiler logic. Keeping that in a separate package makes the runtime cleaner while allowing the compiler to evolve without affecting it.
So, a smaller runtime is a nice benefit—but the bigger goal is flexibility, scalability, and a better developer experience.