SolidMVC
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 Solid, 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. SolidMVC keeps both: you write JSX with ordinary imports, and organize it with modules and DI.
import { Component, Controller, View, inject, signal } from '@solid-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
| Package | What it is |
|---|---|
@solid-mvc/core | The framework: injector, decorators (@Module, @Component, @Injectable, @Controller), View classes, signals, context, router. One dependency: solid-js. |
@solid-mvc/atoll | Facade 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. |
@solid-mvc/atoll-island | The 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 Angular | From Solid |
|---|---|
| Modules that own providers and declare components | Components 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 state | Signals for fine-grained reactivity |
Standalone components (@Component imports) | Class construction replaces per-render hook calls |
The whole Solid ecosystem, unchanged
render() returns ordinary Solid JSX — anything that works in a Solid app works here, no "framework bindings" version of the library:
- Any Solid component — icon sets, UI kits, form controls — drops straight into a view's JSX. Control flow stays idiomatic:
<For>,<Show>, context, portals. - Fine-grained by construction — the framework's signals wrap Solid's own reactive graph, so reads inside JSX update exactly the DOM that read them — no re-render, no diff.
- Anything else is two moves: provide the client object through DI, bridge its
subscribeinto a signal. DOM-bound libraries (charts, editors, maps) get a three-line ref wrapper — see Integrating libraries.
The enterprise demo proves it in practice: TanStack Query Core, ECharts, zod, date-fns, lucide-solid 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 SolidMVC 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
- Quickstart — first app in ten minutes
- Dependency injection — the scoping model everything builds on
- Views & lifecycle — class-based components
- Islands — worker-rendered UI
- Live demos — running apps embedded in this site