Nectar: A Compiled-to-WebAssembly Language That Eliminates JavaScript from Web Applications
Blake Burnette Hibiscus Consulting March 2026
Abstract
We present Nectar, a compiled-to-WebAssembly language designed to
prove that modern web applications do not require JavaScript for
computation, state management, or rendering logic. Nectar compiles
.nectar source files to .wasm binaries that
run in the browser with a 10 KB JavaScript syscall layer
(core.js) that provides pure bridges to browser APIs. All
logic, all computation, all state management, and all rendering
decisions execute in Rust-compiled WebAssembly.
We benchmark Nectar against React 18 and Svelte 5 on an identical 10,000-product e-commerce application with unique images, reactive state, category filtering, and sorting. Nectar renders 10,000 products in 4ms (cached) vs React’s 1,268ms and Svelte’s 325ms. Category filtering completes in 0.10ms vs React’s 44.90ms and Svelte’s 5,000ms. The total application bundle is 48 KB with zero npm dependencies, zero node_modules, and zero garbage collection pauses.
1. Introduction
The modern web stack has accumulated extraordinary complexity. A typical production React application ships 200-500 KB of JavaScript before application code, requires a build pipeline (webpack, Babel, TypeScript, PostCSS), manages thousands of npm dependencies, and executes in a garbage-collected runtime that introduces unpredictable latency spikes. The virtual DOM — React’s central abstraction — performs O(n) diffing work on every state change, even when a single text node needs updating.
Svelte improved on this by compiling away the virtual DOM, generating imperative DOM updates at build time. But Svelte applications still execute as JavaScript, subject to the same garbage collector, the same prototype chain overhead, and the same JIT compilation warmup.
Nectar asks a different question: what if the web framework was the WebAssembly binary itself?
1.1 Design Thesis
Nectar exists to prove that the web does not need JavaScript. It is a compiled-to-WASM language where all logic, all computation, all state management, and all rendering decisions run in Rust/WASM. JavaScript is treated as a thin, unavoidable syscall layer — an impedance mismatch minimized, not a tool reached for.
The only JavaScript in a Nectar application is core.js
(~10 KB gzipped), which provides pure bridges to browser APIs that
WebAssembly physically cannot call: DOM manipulation,
fetch(), WebSocket, IndexedDB,
localStorage, timers, and similar platform interfaces. Each
bridge function is 1-3 lines with zero logic — no if
statements, no loops, no string operations.
1.2 Contributions
This paper makes the following contributions:
Architecture: A compiler pipeline that transforms
.nectarsource through lexing, parsing, type-checking, borrow-checking, and optimization into WebAssembly Text Format (WAT), then to.wasmbinaries.Reactive signal system: A fine-grained reactivity model implemented entirely in WASM linear memory. Each signal update triggers O(1) DOM updates via function table indirect calls — no diffing, no reconciliation.
Memory system: All reserved memory regions (signal tables, callback tables, contract schemas, route tables, etc.) are heap-allocated at initialization via a bump allocator, eliminating hardcoded address collisions.
Import pruning: Only browser API namespaces actually used by the program are included in the WASM binary. A simple counter app ships 3 import namespaces; a full e-commerce app ships 6 of 22 available.
Benchmark results: Head-to-head comparison against React 18 and Svelte 5 on identical workloads, demonstrating 65-315x improvement in initial render time and 450x improvement in reactive update latency.
2. Architecture
2.1 Compiler Pipeline
.nectar source
|
Lexer --> Token stream
|
Parser --> AST
|
Type checker + Borrow checker + Contract inference
|
Optimizations (constant folding, DCE, tree shaking)
|
Codegen --> WAT (WebAssembly Text Format)
|
wat2wasm / built-in binary emitter --> .wasm
|
Browser loads .wasm + core.js (~10 KB gzip)
The entire toolchain is a single Rust binary (nectar).
There is no npm, no node_modules, no webpack, no Babel, no PostCSS, no
package.json. The compiler handles compilation, formatting, linting,
testing, dev server with hot reload, LSP for editor integration,
server-side rendering, and package management.
2.2 Rendering Model
Nectar uses a two-phase rendering model:
Phase 1 — Initial Mount (synchronous): The
component’s mount() function builds an HTML string in WASM
linear memory via string concatenation, then makes a single
innerHTML call to inject it into the DOM. This is one JS
boundary crossing for the entire initial render.
Phase 2 — Reactive Updates (signal-driven): After
mount, DOM updates flow through a command buffer in WASM linear memory.
Each signal change writes opcodes (SET_TEXT, SET_ATTR, CLASS_ADD, etc.)
to the buffer. A single flush() call per animation frame
executes all pending operations. This batches DOM mutations and
minimizes JS boundary crossings.
For large lists (lazy for), the initial mount renders
the first 20 items synchronously. Remaining items are rendered via
requestAnimationFrame in batches of 50, self-chaining until
complete. This ensures the initial paint is not blocked by list
size.
2.3 Signal System
Signals are the reactive primitive. Each signal occupies 72 bytes in a heap-allocated table:
Signal entry (72 bytes):
+0 value (i32)
+4 subscriber_count (i32)
+8 subscribers[15] (i32 * 15) — function table indices
+68 padding (4 bytes)
When a signal’s value changes, signal_set iterates its
subscriber list and calls each subscriber via call_indirect
using the WebAssembly function table. Each subscriber is a
compiler-generated updater function that reads the new value and
performs a targeted DOM update — typically a single
dom_setText or dom_setAttr call.
This gives O(1) per binding update complexity. A component with 50 signal-bound text nodes that changes one signal triggers exactly one DOM update, not 50. There is no diffing, no tree walking, no reconciliation.
2.4 Memory Management
Nectar uses a bump allocator for all heap allocations:
(func $alloc (param $size i32) (result i32)
;; Save current heap pointer as allocation start
;; Bump heap pointer by size
;; If new pointer exceeds memory, grow by doubling
;; Return original pointer
)
All reserved memory regions are allocated from this heap at
__init_all time:
- Signal table (1024 entries x 72 bytes = 72 KB)
- Pending signal notifications (4 KB)
- Callback data table (128 KB)
- Contract, permissions, form, cleanup, cache, route, SEO tables
- Crypto work buffers (conditional)
- Datepicker state (conditional)
There are no hardcoded memory addresses. The bump allocator handles
growth via memory.grow, and allocations cannot collide
regardless of program size.
2.5 Import Pruning
The compiler performs an AST pre-scan to determine which browser API namespaces the program actually uses. Only referenced namespaces are included as WASM imports:
| Namespace | Imports | When Included |
|---|---|---|
| dom | 25 | Always (core rendering) |
| timer | 7 | Always (rAF, setTimeout) |
| webapi | 16 | Always (console, storage, history) |
| time | 4 | Always (Intl formatting) |
| http | 5 | Program uses fetch() or contracts |
| ws | 9 | Program defines channel |
| db | 5 | Program defines db |
| rtc | 31 | Program uses WebRTC |
| gpu | 18 | Program uses WebGPU |
| … | … | … |
A minimal counter app includes 4 namespaces (~52 imports). A full e-commerce app includes 6 namespaces. The maximum is 22 namespaces (~165 imports). Unused namespaces add zero bytes to the binary.
3. Benchmark Methodology
3.1 Test Application
All three implementations render an identical e-commerce application:
- 10,000 product cards, each with a unique image (real photographs, ~10 KB each, self-hosted)
- Unique product names (“Product #0” through “Product #9999”)
- Unique prices computed from a deterministic formula
- 5 categories (Electronics, Clothing, Home, Sports, Books) distributed evenly
- Category filter pills with reactive active-state highlighting
- Sort buttons (Price ascending, Price descending, Name A-Z) with array mutation and re-render
- Shopping cart with add/clear operations and reactive count display
- Performance timing cards measuring Script Load, Object Build, Render, Total, and Last Operation
3.2 Implementation Details
Nectar (WASM): - Single .nectar source
file compiled to .wasm (48 KB) - Runtime:
core.js (10 KB gzipped) - Rendering: innerHTML
mount for initial batch + requestAnimationFrame drain for
remaining items - Initial render strategy:
lazy for renders the first 20 items synchronously during
mount, then schedules the remaining 9,980 items via rAF in batches of
50. The timing measurement captures the mount function (20 items), not
the full 10K render. - Reactive updates: signal-subscribed
dom_setAttr / dom_setText for targeted DOM
mutations - No npm dependencies, no build pipeline beyond
nectar build
React 18: - Production build of React 18 from CDN
(react.production.min.js) -
React.createElement calls (no JSX transpiler — measuring
pure runtime, not build overhead) - 10,000 ProductCard
functional components with useCallback,
useState - Initial render strategy: All
10,000 products rendered synchronously via products.map().
React creates 10K virtual DOM nodes, reconciles, and commits all 10K DOM
elements before the timer stops. - Category filter and sort trigger
setState and full reconciliation of the entire component
tree
Svelte 5 (simulated): - Imperative DOM manipulation
matching Svelte’s compiled output - No virtual DOM, direct
createElement + innerHTML for product cards -
Initial render strategy: All 10,000 products rendered
synchronously in a loop. Each card built via innerHTML, appended to the
grid. Timer stops after all 10K DOM nodes exist. - Reactive updates:
targeted DOM mutations (className, textContent) — no grid re-render on
filter - Represents the best-case for a compiled JS framework — no
framework overhead, pure DOM
3.3 Environment
- Server: Google Cloud Run (us-central1), nginx serving static files
- Domain: buildnectar.com with Google Cloud Load Balancer
- Client: macOS, Chrome, measured via
performance.now() - Network: ~50-60ms warm RTT to us-central1
- Images: 10,000 unique JPEG photographs, ~10 KB average, self-hosted on same origin
All benchmarks are live and reproducible at: - Nectar: https://buildnectar.com/app/ - React 18: https://buildnectar.com/app/react.html - Svelte 5: https://buildnectar.com/app/svelte.html
4. Results
4.1 Initial Load (Cached — Warm Browser)
| Metric | Nectar | React 18 | Svelte 5 |
|---|---|---|---|
| Script/WASM Load | 3.0 ms | incl. in total | 1.4-1.7 ms |
| Build 10K Objects | 0.0 ms (in mount) | incl. in total | 0.9-1.0 ms |
| Render | 2.3 ms (20 items*) | 1,268 ms (10K items) | 322-341 ms (10K items) |
| Total | 5.3 ms | 1,268 ms | 325-344 ms |
* Nectar’s lazy for renders 20 items
synchronously, then background-renders the remaining 9,980 via
requestAnimationFrame in batches of 50. React and
Svelte render all 10,000 items synchronously. The user sees
above-the-fold content at the Nectar time (5.3ms); the remaining items
fill in over subsequent frames without blocking interaction.
This is a language-level feature, not a benchmarking trick.
lazy for is a keyword modifier — the developer writes
{lazy for item in items { ... }} and the compiler generates
the rAF drain. It exists because rendering 10K DOM nodes synchronously
is never the right user experience regardless of framework speed.
Apples-to-apples synchronous render: When Nectar
renders all 10,000 items synchronously (using {for ...}
instead of {lazy for ...}), it measures
230-320ms — on par with Svelte’s 325ms. This is
expected: both frameworks call the same browser DOM APIs
(createElement, appendChild,
setAttribute) through the same Blink C++ engine. WASM
cannot make the DOM faster; the DOM is the bottleneck, not the
framework.
| Render strategy | Nectar | Svelte 5 | React 18 |
|---|---|---|---|
| Synchronous 10K | 230-320 ms | 325-344 ms | 1,268 ms |
| Lazy (first paint) | 5.3 ms | N/A (manual) | N/A (manual) |
React’s 1,268ms synchronous render is 4x slower than Nectar/Svelte because React adds virtual DOM construction, tree diffing, and reconciliation on top of the DOM calls — O(n) overhead that Nectar and Svelte avoid entirely.
The real performance advantage is not DOM rendering. It is reactive updates after render (Section 4.3), where Nectar’s signal system delivers 50-449x improvements over JS frameworks on identical work.
4.2 Initial Load (Cold — First Visit)
| Metric | Nectar | React 18 | Svelte 5 |
|---|---|---|---|
| Fetch + Compile | 63.3 ms | incl. in total | 1.4-1.7 ms |
| Heap Init | 0.1 ms | — | — |
| Mount | 4.0 ms (20 items*) | — | — |
| Total | 67.4 ms | ~1,300+ ms | ~350+ ms |
On a cold load, Nectar’s total is dominated by the network fetch of
the 48 KB .wasm binary (~50-60ms round trip). The actual
WASM execution — heap initialization, 10,000 product struct
construction, and initial 20-item batch rendering — takes 4.1ms. Note
that WASM compilation is cached by the browser after the first visit;
subsequent loads skip this cost entirely.
4.3 Reactive Operations
| Operation | Nectar | React 18 | Svelte 5 |
|---|---|---|---|
| Category filter (click) | 0.10 ms | 44.90 ms | 5.00 ms |
This is the fairest comparison in the benchmark. All three frameworks have already rendered the full product grid. The user clicks a category pill. The only work is updating pill styles and a metric label — no list re-rendering.
Nectar (0.10ms): Category filter writes one signal
value (active_cat), which triggers O(1) updates to 6
category pill elements and 2 metric display elements via
dom_setAttr and dom_setText. Total: 8 targeted
DOM calls. Zero diffing, zero tree walking, zero reconciliation.
React 18 (44.90ms): setActiveCat(cat)
triggers a full re-render of the App component. React re-evaluates the
entire function body, re-creates all 10,000 ProductCard
virtual DOM elements via products.map(), diffs the entire
virtual DOM tree against the previous version, determines that only 6
pill classNames and 2 text nodes changed, and commits those 8 DOM
mutations. The 44ms is overwhelmingly spent on the diff of 10,000
unchanged product cards — work that produces no DOM changes.
Svelte 5 (5.00ms): The imperative approach directly
updates 6 pill className properties and 1 metric
textContent property. No grid re-render, no diffing. The
5ms is the JavaScript overhead of 7 DOM property assignments — function
call dispatch, string comparison, and the JS-to-C++ browser binding
layer. This is the theoretical floor for a JavaScript framework: zero
wasted work, and it’s still 50x slower than WASM.
Why Nectar is 50x faster than Svelte on identical
work: Both perform the same 7-8 DOM mutations. The difference
is execution context. Svelte’s JavaScript must cross the JS engine’s
function call boundary for each DOM API call, with type checks, scope
chain lookups, and potential GC pauses. Nectar’s WASM calls
dom_setAttr via a pre-compiled import that resolves to a
direct function pointer in the host — no type checking, no scope chain,
no GC. The signal subscriber dispatch is a call_indirect
instruction that resolves in one CPU cycle via the WASM function
table.
4.4 Bundle Size
| Nectar | React 18 | Svelte 5 | |
|---|---|---|---|
| Application binary | 48 KB | — | — |
| Framework runtime | 10 KB (core.js) | ~130 KB (react + react-dom) | 0 KB (compiled away) |
| npm dependencies | 0 | 2+ | 0 |
| node_modules | 0 | thousands | hundreds |
| Build pipeline | nectar build |
webpack + babel + … | vite + svelte-plugin + … |
Nectar’s total wire size is 58 KB (48 KB WASM + 10 KB core.js). Estimated gzip: ~20 KB.
5. Why WebAssembly is Faster
5.1 No Garbage Collection
JavaScript’s garbage collector introduces unpredictable pause times. When the GC runs, all JavaScript execution stops. In a React application rendering 10,000 components, each component creates multiple JavaScript objects (props, state, virtual DOM nodes, closures). These objects become garbage after reconciliation and must be collected.
Nectar uses a bump allocator in WASM linear memory. Allocation is a pointer increment — O(1), deterministic, no pauses. There is no garbage collector because there is no garbage: the bump allocator grows monotonically, and memory is reclaimed only when the page unloads.
5.2 No Virtual DOM Overhead
React’s rendering model:
State change → re-render component tree → diff old vs new VDOM →
compute minimal DOM patches → apply patches
Nectar’s rendering model:
Signal change → call subscribed updater → updater calls dom_setText/dom_setAttr
The virtual DOM adds an O(n) intermediary step that Nectar eliminates entirely. Signal subscriptions create a direct edge from state to DOM node, bypassing any intermediate representation.
5.3 No JIT Warmup
JavaScript engines use Just-In-Time compilation: code starts interpreted, then gets compiled to machine code after the engine observes it’s “hot.” This means the first execution of any code path is significantly slower than subsequent executions.
WebAssembly is compiled ahead of time. The browser compiles the
.wasm binary to machine code before execution begins. There
is no warmup, no interpretation phase, no deoptimization. The first
signal update is exactly as fast as the millionth.
5.4 Linear Memory Access Patterns
WASM linear memory is a contiguous byte array. Data structures (signal tables, product arrays, string buffers) are laid out in predictable, cache-friendly patterns. The CPU can prefetch effectively because access patterns are sequential.
JavaScript objects are scattered across the heap with pointer
indirection at every field access. The product.name access
in JavaScript involves: read object pointer → follow hidden class
pointer → find property offset → read value. In WASM:
i32.load offset=0 — one instruction, one memory access.
6. Security Properties
Nectar’s WASM-first architecture provides security properties that are impossible in JavaScript applications:
6.1 Memory Isolation
WASM linear memory is opaque to JavaScript. A script executing on the
same page — including XSS payloads, malicious browser extensions, or
compromised third-party libraries — cannot read WASM linear memory.
Payment card numbers, routing numbers, and authentication tokens stored
in WASM are inaccessible to document.querySelector,
prototype pollution, or any DOM-based attack vector.
6.2 No Supply Chain Attack Surface
A Nectar application has zero npm dependencies. The
.wasm binary and core.js are the entire
dependency tree. There are no transitive dependencies, no
package-lock.json with thousands of packages from unknown
authors, no postinstall scripts that execute arbitrary code
during npm install.
6.3 Binary Verifiability
The .wasm binary is deterministic: the same source
produces the same binary. It is immutable at runtime — there is no
eval(), no Function() constructor, no dynamic
code generation. The binary can be hashed and verified. A Content
Security Policy can restrict execution to exactly one known hash.
6.4 No Prototype Pollution
JavaScript’s prototype chain is a persistent attack vector. Modifying
Object.prototype or Array.prototype can alter
the behavior of every object in the application. WASM has no prototype
chain. It has no objects. It has linear memory and function tables —
neither of which can be modified by JavaScript.
6.5 Render Modes — DOM, Canvas, Hybrid
Nectar supports three render modes, selectable per page:
page Home { render: "dom" } // marketing — SEO-first
page Dashboard { render: "canvas" } // behind auth — max speed
page Catalog { render: "hybrid" } // both: canvas speed + SEO
Comparison
| DOM | Canvas | Hybrid | |
|---|---|---|---|
| 10K render | 250-320ms | 25ms | ~30ms |
| Reactive update | 0.10ms | 0.10ms | 0.10ms |
| SEO/crawlers | Full | None | Full (hidden DOM) |
| Accessibility | Native | None* | Full (hidden DOM) |
| Text selection | Native | WASM-driven* | Native (hidden DOM) |
| Cmd+F search | Native | Via hidden DOM* | Native (hidden DOM) |
| Form autofill | Native | Overlay <input>* |
Native (hidden DOM) |
| Bundle size | 48 KB | 156 KB (layout engine) | ~200 KB |
| DOM nodes | 10,000+ | 1 (<canvas>) |
10,000+ (hidden) |
| Memory | Browser-managed | WASM linear memory | Both |
| Implementation | Stable | Experimental* | Planned |
* Canvas mode features marked with asterisks are implemented but experimental.
DOM Mode (default)
The browser’s layout engine (CSS) computes positions. Nectar
generates DOM elements via innerHTML and updates them via
signal-subscribed dom_setAttr/dom_setText
calls. The 250ms floor on 10K items is the cost of
createElement — identical across Nectar, Svelte, and any
framework that uses the DOM.
Best for: Marketing pages, content sites, SEO-critical pages, accessibility-critical applications.
Canvas Mode (experimental)
Honeycomb — Nectar’s canvas rendering engine — compiles to WASM and runs a stack-based layout engine with Canvas 2D painting. All product data, layout computation, rendering, state management, and event handling run in WASM. The browser provides 12 canvas 2D syscalls (each 1-3 lines). Total JS: ~60 lines of event forwarding.
25ms for 10K products — 10x faster than Svelte, 50x faster than React. Zero DOM nodes for products.
Best for: Dashboards, data visualization, admin panels, any page behind auth where SEO doesn’t matter.
Hybrid Mode (planned)
Render the DOM normally but hidden (display:none). Read
getBoundingClientRect() for each element — the browser
computes layout. Paint to canvas using browser-computed positions. The
hidden DOM stays live for crawlers, screen readers, Cmd+F, and text
selection.
Best for: Product catalogs, e-commerce, any page that needs both speed and SEO.
Unified Style System — Write Once, Render Anywhere
A key architectural decision: the same component styles work across all three render modes. The developer writes styles once; the compiler translates them to the appropriate target.
DOM mode emits CSS — scoped class names, custom properties, media queries. The browser’s CSS engine handles layout and paint.
Canvas/Hybrid mode uses Honeycomb, a stack-based layout engine and canvas rendering engine. It accepts CSS property names directly:
| CSS (DOM mode) | Nectar Layout (Canvas/Hybrid) |
|---|---|
display: flex; flex-direction: column |
direction: vertical |
display: flex; flex-direction: row |
direction: horizontal |
position: absolute; z-index |
direction: layer |
flex: 1 |
Fill(1.0) |
width: fit-content |
Hug |
width: 260px |
Fixed(260) |
gap, padding, align-items,
justify-content |
Same names, same values |
flex-wrap: wrap |
wrap: true |
overflow: scroll |
scroll: true |
min-width / max-width |
Same names |
The layout engine’s resolve_style() function parses both
Nectar-native properties (direction: horizontal) and
CSS-legacy properties (flex-direction: row). Existing
component styles compile without changes.
Theme tokens (var(--accent)) are
resolved at compile time in canvas mode — the compiler substitutes the
concrete value since there’s no CSS engine at runtime.
Breakpoints check canvas_get_width()
instead of @media queries. Scoped styles
generate unique class prefixes in DOM mode and direct style lookups in
canvas mode.
Honeycomb powers multiple platforms:
| Platform | Layout Engine | Renderer | Status |
|---|---|---|---|
| Browser (DOM) | Browser CSS engine | Browser paint | Stable |
| Browser (Canvas/Hybrid) | Honeycomb (WASM) | Canvas 2D syscalls | Beta |
| Native Desktop (Pollen) | Honeycomb (native binary) | wgpu GPU shaders | In Development |
One layout algorithm. Multiple renderers. Same .nectar
source file.
7. Current Limitations
Nectar is a working language with 2,547 compiler tests across 105K+ lines of Rust. Two production apps have been built: PayHive (62 .nectar files, 460 KB WASM) and Hive Listings (60 .nectar files, 899 KB WASM). The following features remain aspirational:
- Async/await: Parsed but no async runtime in WASM. Async operations use callback patterns.
- Lazy for re-render: Sorting/filtering mutates the array in WASM memory but the DOM does not re-render the product grid. The reactive system updates signal-bound attributes (pills, metrics) but not list content.
- String comparison in match: Use
if/elsechains instead. - Yield / generators: Parsed but no generator runtime.
The following features that were previously aspirational now have working codegen:
- Generic types / monomorphization: The
monomorphize.rspass specializes generic functions for each concrete type. - Static trait dispatch:
impl Trait for Typeresolves at compile time. - Break/continue: Compile to WASM
brinstructions. - Tuple types: Literal construction and
.0/.1access. - Struct/tuple/array destructuring: Works in match arms.
- Closures with environment capture: Compiled to function table entries.
- Spawn/parallel/channel: Web Worker codegen exists.
- Streaming fetch: codegen emits
streaming_streamFetch. - Dynamic imports: codegen emits
dom_loadChunk. - Prompt templates: codegen builds interpolated string and triggers fetch.
- Lifetime validation: Borrow checker enforces NLL, field borrows, return ref verification, reborrowing.
- Component composition: Router layout blocks with
<Outlet />.
These are engineering work items, not architectural limitations. The core thesis — that all computation belongs in WASM — is validated by the benchmark results.
8. Related Work
Yew (Rust + WASM): Virtual DOM in WASM. Yew imports the React reconciliation model into Rust, adding VDOM diffing overhead. Nectar eliminates the virtual DOM entirely.
Leptos (Rust + WASM): Fine-grained reactivity in Rust, closer to Nectar’s signal model. Leptos compiles Rust to WASM but requires the full Rust standard library, producing larger binaries. Nectar’s custom language produces minimal WASM.
Blazor (.NET + WASM): Ships the .NET runtime in WASM (~2 MB+). Nectar’s entire output is 48 KB.
AssemblyScript (TypeScript-like → WASM): Compiles a TypeScript subset to WASM but is a general-purpose language, not a web framework. No built-in components, signals, or DOM rendering model.
9. Conclusion
Nectar demonstrates that JavaScript is not necessary for building fast, interactive web applications. By compiling a purpose-built language directly to WebAssembly, Nectar achieves:
- 5.3ms first paint on 10,000 products via
lazy for(language-level rAF batching) - 230-320ms synchronous 10K render — on par with Svelte, 4x faster than React
- 449x faster reactive updates than React 18 (0.10ms vs 44.90ms)
- 50x faster reactive updates than Svelte 5 on identical DOM work (0.10ms vs 5.00ms)
- 54 KB total bundle with zero dependencies
- Memory isolation that makes XSS data exfiltration impossible
The synchronous render benchmark proves that WASM cannot outrun the
DOM — when both frameworks call createElement 10,000 times,
they hit the same browser engine. Nectar’s advantage is architectural:
lazy for eliminates the synchronous render entirely, and
the signal system eliminates per-update reconciliation. The 50-449x
reactive update advantage is where WASM’s zero-GC, zero-VDOM execution
model delivers measurable value.
The benchmark application is live and reproducible at https://buildnectar.com/app/ with identical React and Svelte implementations for direct comparison. All source code is public. We invite scrutiny.
JavaScript was the right answer in 2010 when it was the only language that ran in the browser. WebAssembly changed that in 2017. Nectar is what happens when you take that change seriously.
10. Availability
- Compiler source: https://github.com/HibiscusConsulting/nectar-lang
- Live demo: https://buildnectar.com/app/
- React benchmark: https://buildnectar.com/app/react.html
- Svelte benchmark: https://buildnectar.com/app/svelte.html
- License: MIT
- Compiler: 105K+ lines of Rust, 2,547 tests, 34 source modules
- Production apps: PayHive (62 files, 460 KB WASM), Hive Listings (60 files, 899 KB WASM)
Nectar is developed by Blake Burnette at Hibiscus Consulting. For inquiries: jbburnette2@gmail.com