PreactMVC

A frontend framework that takes Angular's architecture and renders it with React's ergonomics: modules, dependency injection, and decorators around class-based JSX views on Preact, with signal reactivity and worker-rendered islands built in.

The idea

Angular got application shape right — modules that own providers, injectors that scope services, decorators that declare intent, an MVC split between views and controllers. React got rendering right — components are values, JSX composes freely, and imports resolve identity instead of a template registry. PreactMVC keeps both: you write JSX with ordinary imports, and organize it with modules and DI.

counter.view.tsx
import { Component, Controller, View, inject, signal } from '@preact-mvc/core';

@Controller()
class CounterController {
  count = signal(0);
  inc = () => this.count.value++;
}

// A view is a class. Injection happens once at mount — no hooks in render().
@Component({ controllers: [CounterController] })
export class CounterView extends View {
  private c = inject(CounterController);
  render() {
    return <button onClick={this.c.inc}>count: {this.c.count.value}</button>;
  }
}

Three packages

PackageWhat it is
@preact-mvc/coreThe framework: injector, decorators (@Module, @Component, @Injectable, @Controller), View classes, signals, context, router. One dependency: preact.
@preact-mvc/atollFacade over @atolljs/islands + @atolljs/core: the op protocol, the proxy DOM a worker renders into, the worker runtime, the island contracts, and the main-thread driver that replays ops onto real DOM.
@preact-mvc/atoll-islandThe binding package: <Island/> shell components on the main thread, defineViewPolyWorker/viewIslandApp on the worker side. The same View classes render in both places.

What it borrows from each side

From AngularFrom React/Preact
Modules that own providers and declare componentsComponents are values you import, not registered selectors
Hierarchical injectors, provider scopes, inject()JSX everywhere — no template language, no compile step
Decorators for metadata (@Module, @Component)Freedom of composition — undeclared views mount anywhere
MVC split: controllers own interaction stateSignals for fine-grained reactivity
Standalone components (@Component imports)Class construction replaces per-render hook calls

The whole Preact ecosystem, unchanged

render() returns ordinary Preact vnodes, so anything that works in a Preact app works here — no adapters, no "framework bindings" version of the library:

  • Any Preact component — icon sets, UI kits, form controls — drops straight into a view's JSX. Function components keep their hooks; preact/compat unlocks the React ecosystem the same way it does in any Preact app.
  • Any signal-aware library — the framework's signals are @preact/signals, so libraries built on them subscribe with zero bridging.
  • Anything else is two moves: provide the client object through DI, bridge its subscribe into a signal. DOM-bound libraries (charts, editors, maps) get a three-line function-component wrapper — see Integrating libraries.

The enterprise demo proves it in practice: TanStack Query, ECharts, zod, date-fns, lucide icons, and a Tailwind/shadcn component layer all run through those moves. Inside worker islands the same View classes render too — even ECharts runs there, emitting SVG through the proxy DOM.

And one thing neither has

Worker-rendered islands. A <Island app="ticker" worker={...} /> mounts a real PreactMVC view tree inside a Web Worker — the same View classes, signals, and DI — while the shell sees an ordinary element that ticks, emits events, and never blocks the main thread. See Islands.

Where to go next