01 product · compiler
Next.js ships React to the browser and hopes the runtime is fast enough. Svelte hides the runtime but still carries a scheduler. Vesk moves the work to build time — your app is proven good before a byte leaves the server.
01 · preprocess
Statement-mode clauses (
; key
/
; index
),
<style>
hoisting, island
{#client}
tagging — offset-preserving, no regex.
02 · parse
Full TypeScript superset.
component
, JSX-as-statement,
&[track]
atoms, generics — all typed, all surfaced to tsc via
vskToTsx
.
03 · IR
Validated, dispatch-friendly:
StaticNode
,
MapRegion
,
TrackDecl
, Islands. One IR, two web emitters + Kotlin.
04 · codegen
Server HTML chunks + client DOM + hydration markers; or Kotlin via
vesk-native-compiler
. Static subtrees get zero hydration runtime.
| Capability | Vesk | Next.js / Vite + React | SvelteKit |
|---|---|---|---|
| Virtual DOM in production | None — direct DOM writes | Yes (React reconciler) | Scheduler + runtime helpers |
| Zero-JS pages | Proven at build, 0 B | Always ships runtime | Partial, manual |
| Event delegation | AOT — one listener per type | Synthetic + per-tree | Per-component |
| Static subtree cost | No marker, no JS | Hydrated anyway | Reduced but present |
| Cross-platform | Web + Kotlin (one IR) | Web only | Web only |
| Typecheck |
vesk typecheck
via tsc
|
tsc separate | svelte-check |
component Counter() {
let &[count] = track(0)
<button onClick={() => count++}>Count: {count}</button>
}
One model, no hooks, no useState. &[] is sugar — compiler rewrites to get/set and per-cell effects.
// no VDOM
const $el = document.createElement('button')
effect(() => $el.textContent = `Count: ${get(count)}`)
$el.addEventListener('click', () => set(count, get(count)+1))
O(1) text update, not O(n) VDOM diff. Static neighbors never re-render.