150
Lines of reactivity core
Compare
BaseNative is a small runtime for the web platform itself: valid HTML templates that stay data instead of becoming code, no build step, no dependencies, nothing to eval — so a page can be generated, stored in a CMS or database, and served under a strict CSP. An honest look alongside React, Angular, and Svelte: where it wins, where it doesn't, and why that matters.
150
Lines of reactivity core
0
Core runtime dependencies
0
Build steps required
6
Core API primitives
BaseNative ships raw ES modules. No bundler, no tree-shaking needed — what you import is what runs. The runtime is one package you can read end to end.
| Measure | BaseNative | React 19 | Angular 22 | Svelte 5 |
|---|---|---|---|---|
| Runtime size (min+gz) | 9.9 KB | ~67.5 KB | ~65.7 KB | ~9.9 KB |
| Core production deps | 0 | 2 (react, react-dom) | 8 (@angular/* ×6, rxjs, tslib) | 0 |
| Build step | None | Required (JSX) | Required (TS + AOT) | Required (compiler) |
| Runtime source you can read | 2,775 lines | ~20K lines | ~51K lines | ~16K lines |
BaseNative's runtime is measured by scripts/bundle-size.js — the same gzipped
build CI holds to a 10 KB budget — so this row is the number the
package actually ships, not a target. Competitor figures are what a minimal hello-world app
ships through each framework's own production pipeline, min+gzip, measured 2026-10-20 against React 19.3.1 · Angular 22.2.3 · Svelte 5.57.2. Svelte has no fixed
runtime — its compiler ships only what the app uses — and at hello-world scale that lands
level with BaseNative's whole runtime; the tradeoff is the compiler it needs to get there, and
a figure that grows with the app.
"Zero dependencies" is a claim about @basenative/runtime, and only about it.
Across all published @basenative/* packages there are 7 third-party production dependencies in 5 packages —
server parses HTML, og-image rasterises cards,
wrangler-preset wraps Wrangler, and eslint-config extends ESLint.
Install only the runtime and you install nothing else; the rest of the toolkit is not
dependency-free.
BaseNative expressions are interpreted by a hand-written parser — a tokenizer, a
recursive-descent grammar, a scope object. There is no eval and no
Function constructor anywhere in the shipped runtime or server; the count is taken
from source on every build, and it is 0. A BaseNative page runs under
script-src 'self' with no 'unsafe-eval' — and unlike the compiled
frameworks, that does not depend on a build having happened first.
Because a template is never compiled into code, a template is data. It can live in a CMS, come out of a database, or be written by a model, and rendering it cannot turn it into an injection: the parser interprets a deliberate subset — property access, calls into the provided scope, operators, ternaries — and blocklists prototype access. React, Angular and Svelte are safe the opposite way: their templates become code at build time, which is exactly why a template has to go through the build before it can run at all.
| Measure | BaseNative | React 19 | Angular 22 | Svelte 5 |
|---|---|---|---|---|
| eval / Function constructor in shipped code | 0 | 0 — after its compiler runs | 0 — after AOT compilation | 0 — after its compiler runs |
| Template stays data at runtime | Yes — parsed, never compiled to code | No — JSX compiles to JavaScript | No — templates compile at build time | No — .svelte compiles to JavaScript |
| A stored template (CMS, database, model output) renders as-is | Yes — interpreted against a scope, prototype access blocklisted | No — it must go through the build first | No — it must go through the build first | No — it must go through the build first |
The narrow, measured version of that claim: an LLM wrote the same two apps twice, in BaseNative 2.1.0 and in React 19.3.1 — a server-rendered catalog and an interactive counter-and-list page, sharing one dataset, with identical behavior tests on both sides and every fix logged. With no build pipeline in the way, the BaseNative side needed no tooling fixes at all. Both implementations passed every test. What differed was the path, and the code-size hypothesis failed in public:
| Measure | BaseNative | React |
|---|---|---|
| Build and tooling fixes before tests passed | 0 | 4 |
| App-code fixes | 1 | 0 |
| Production dependencies (interactive task) | 1 | 3 |
| Hand-written lines (both tasks) | 95 | 114 |
| Approx. code tokens (both tasks) | 827 | 795 |
The code-size hypothesis failed: fewer lines, slightly more tokens, because HTML templates
carry closing tags. The 2,590-token llms.txt that teaches a
model the BaseNative API has no break-even through code savings at this task size; what it
buys is avoided errors. The run's only app-code bug was BaseNative's — an effect subscribed to
both a signal and a computed derived from it fired twice per change. The runtime now settles
that pattern in one run. The historical count of one app-code fix stands. The updated
scorecard is on /ai.
First run in October 2026, against BaseNative 0.6.4. Re-verified 2026-10-20 against the published 2.1.0 packages, with React still at 19.3.1 — no measured value changed.
Full method, results and the fix-by-fix log: the benchmark write-up.
Fewer primitives means less to learn and less to break. BaseNative's entire reactivity model fits on a whiteboard.
| Measure | BaseNative | React 19 | Angular 22 | Svelte 5 |
|---|---|---|---|---|
| Core concepts to learn | 6 | 15+ hooks | 30+ concepts | ~8 runes |
| Reactivity model | Signals (push) | Virtual DOM (pull) | Signals (push) | Runes/compiler (push) |
| Template syntax | Valid HTML | JSX (not HTML) | Extended HTML | Custom (.svelte) |
| TypeScript | Optional | Optional | Required | Optional |
The 6 primitives are signal(), computed(), effect(), batch(), hydrate(), render() — counted from the modules that export them.
A reactive counter shows each framework's mental model. BaseNative uses plain HTML with signal expressions — no compilation, no special file format.
<div :data="{ count: 0 }">
<p>Count: {{ count }}</p>
<button @click="count++">+1</button>
</div>
<script type="module">
import { hydrate } from '/basenative.js?v=72245475d1';
hydrate(document.body, { count: 0 });
</script>
Valid HTML. No build. Drop a script tag and go.
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>
+1
</button>
</div>
);
}
Requires JSX transpilation, a bundler, and react + react-dom installed.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count() }}</p>
<button (click)="count.set(count() + 1)">
+1
</button>
`,
})
export class CounterComponent {
count = signal(0);
}
Requires TypeScript, Angular CLI, AOT compilation, and the full Angular runtime.
<script>
let count = $state(0);
</script>
<p>Count: {count}</p>
<button onclick={() => count++}>+1</button>
Concise, but requires the Svelte compiler — no .svelte files run natively in browsers.
BaseNative includes server rendering and 4 built-in lazy hydration strategies out of the box. Most frameworks require extra packages or custom implementations for selective hydration. Server and client interpret the same expressions through the same shared parser, so the markup the server renders is the markup the client hydrates — there is no second template dialect to drift.
| Measure | BaseNative | React 19 | Angular 22 | Svelte 5 |
|---|---|---|---|---|
| SSR built-in | Yes | Via Next.js / manual | Yes (built-in SSR) | Via SvelteKit |
| Lazy hydration | 4 strategies built-in | Manual / React.lazy | @defer (partial) | Manual |
| Hydration strategies | idle, interaction, media, viewport | N/A | @defer on viewport | N/A |
| CSP-safe expressions | Yes (no eval/Function) | Yes (JSX compiled) | Yes (AOT compiled) | Yes (compiled) |
The place BaseNative is furthest behind. None of this is close, and pretending otherwise would waste your afternoon.
| Measure | BaseNative | React 19 | Angular 22 | Svelte 5 |
|---|---|---|---|---|
| Time to first render | Open HTML file | npm create vite@latest | ng new + ng serve | npx sv create |
| Hot reload | HMR (@basenative/hmr) | Fast Refresh | HMR | HMR |
| Ecosystem packages | Growing | Massive | Large | Moderate |
| Component model | Semantic HTML — W3C primitives are the component model | Components + hooks | Components + DI | Components |
| Community size | Solo project | Millions | Millions | Hundreds of thousands |
BaseNative isn't trying to replace React or Angular for every use case. Here's where it genuinely excels and where you should think twice.
The reactivity core is 150 lines. Read it, understand it, then decide.