Perry already has a proven perry-react adapter (~620 lines of TypeScript) that acts as a drop-in replacement for react and react-dom via packageAliases. The question is whether the same approach can work for Angular — and specifically for Ionic (which sits on top of Angular as a high-level UI component library).
| Target | Feasibility | Key Blocker |
|---|---|---|
| Angular (full drop-in) | Low | Angular's template compiler (Ivy) — templates are strings that need a parser/codegen pass |
| Angular (partial, class-based DI patterns) | High | Perry already supports classes + decorators |
| Ionic components (mapped to Perry native widgets) | High | Ionic's ion-* components have clear 1:1 Perry widget analogues |
| Angular + Ionic together (pragmatic subset) | Medium | Doable with explicit widget functions instead of template strings |
perry-react/src/index.ts works because:
- JSX is resolved at compile time — Perry's compiler transforms
<div>intocreateElement("div", ...)calls before the module runs. No template parser is needed at runtime. - React's mental model is functional — components are plain functions, no decorator metadata processing required.
packageAliasesis the magic shim — Perry rewiresimport { useState } from "react"to perry-react transparently.- The rendering strategy is simple: build widget tree synchronously, clear + rebuild on state change.
Angular templates are strings inside @Component({ template: '<ion-button (click)="foo()">...' }). They contain:
- Property binding:
[value]="expr" - Event binding:
(click)="handler()" - Two-way:
[(ngModel)]="prop" - Structural directives:
*ngIf="cond",*ngFor="let x of xs" - Pipes:
{{ value | date }} - Template references:
#myRef
In a real Angular build, the Ivy compiler parses these strings at build time and emits ɵcmp factory functions. Without Perry implementing an Angular template compiler, templates remain opaque strings.
Options:
- A. Ignore template strings; require developers to implement a
render()method instead (breaks backward compat) - B. Perry adds a template mini-parser pass in Rust (large Rust work, ~2–3 months)
- C. Support only Angular's class/DI patterns with JSX/function-based views (pragmatic subset)
Perry's HIR already stores Decorator { name, args: Vec<Expr> } on functions and classes. Current limitation: only literal arguments are parsed (no object literals with computed properties). Angular decorators like @Component({ selector: 'app-foo', template: '...' }) need object literal support in Perry's decorator lowering (perry-hir/src/lower.rs:723-760).
Gap: Perry needs to extend lower_decorators to handle object literal arguments, not just scalars.
Angular's DI is a hierarchical injector system. At minimum we need:
- Service registry (token → singleton factory)
- Constructor parameter injection (via decorator metadata)
@Injectable(),inject(),InjectionToken
This is implementable in TypeScript within perry-angular (~150 lines).
Angular uses Zone.js to detect async operations and trigger change detection. Perry's single-threaded model with explicit State.onChange is a cleaner replacement. No zone.js needed — but means Angular apps using ChangeDetectorRef.detectChanges() explicitly work fine; apps relying on zone.js automatic detection need adaptation.
Angular's HttpClient, Router, and many core APIs return Observable<T>. RxJS is a large runtime (~50KB+). Options:
- Bundle a minimal RxJS stub (just
Subject,BehaviorSubject,of,from,map,filter) - Perry could alias
rxjs→perry-rxjs(a thin reactive wrapper over Perry'sState)
Ionic is a component library built on Angular (also React/Vue). Its components are web components (ion-button, ion-card, ion-list, ion-input, etc.). From a Perry perspective:
| Ionic Component | Perry Widget | Notes |
|---|---|---|
ion-button |
Button(label, onClick) |
Direct |
ion-input |
TextField(placeholder, onChange) |
+ type variants |
ion-toggle |
Toggle(label, onChange) |
Direct |
ion-range |
Slider(min, max, val, onChange) |
Direct |
ion-select |
Picker(onChange) |
Direct |
ion-textarea |
TextField(...) |
Multi-line |
ion-list / ion-item |
LazyVStack + child layout |
Direct |
ion-card |
VStack(8, []) + styled |
Styled VStack |
ion-label |
Text(content) |
Direct |
ion-icon |
ImageFile(icon-path) |
Or Text emoji |
ion-toolbar / ion-header |
NavStack bar |
Partial |
ion-content |
VStack(0, []) scroll |
Partial |
ion-tab-bar |
Platform tab bar | Needs investigation |
ion-alert |
Alert(...) |
If Perry has Alert |
ion-toast |
Native notification | Perry notifications |
ion-loading |
ProgressView() |
Direct |
ion-refresher |
Pull-to-refresh gesture | Native platform API |
The key insight: Ionic was designed to look native on mobile — so ion-button on iOS should look like a UIButton, which is exactly what Perry's Button() maps to on iOS. The semantic goals are aligned.
@ionic/angular exports Angular component wrappers around each web component. Perry-angular would intercept these same imports and return lightweight wrapper functions/classes that call Perry UI primitives directly — no web components involved.
What it covers:
@Component,@Injectable,@Directive,@NgModule,@Input,@Outputdecorators- Simple DI container (constructor injection, singletons)
- Lifecycle interfaces:
OnInit,OnDestroy,OnChanges bootstrapApplication(RootComponent)entry point- No template string parsing — components expose a
render()method (TypeScript function, not template string)
How it works:
// Developer writes:
@Component({ selector: 'app-root' })
export class AppComponent implements OnInit {
@Input() title = 'My App'
count = 0
ngOnInit() { /* setup */ }
// Instead of template string, expose render():
render() {
return VStack(8, [
Text(this.title),
Button("Click", () => { this.count++ })
])
}
}This is not a 1:1 drop-in, but it enables Angular architectural patterns (DI, decorators, lifecycle) with Perry native rendering.
packageAliases:
{
"perry": {
"packageAliases": {
"@angular/core": "perry-angular",
"@angular/common": "perry-angular",
"@angular/forms": "perry-angular",
"@angular/platform-browser": "perry-angular"
}
}
}What it covers:
Maps @ionic/angular and @ionic/core component imports to Perry UI widget factory functions. Developers import { IonButton, IonInput } and get back Perry widget constructors.
// Developer writes:
import { IonButton, IonInput, IonList } from '@ionic/angular'
// Gets Perry-native widgets that behave like Ionic components
const btn = IonButton({ label: 'Submit', onClick: () => {} })packageAliases:
{
"perry": {
"packageAliases": {
"@ionic/angular": "perry-ionic",
"@ionic/core": "perry-ionic"
}
}
}-
Decorator object literal parsing —
perry-hir/src/lower.rsneeds to support@Component({ selector: 'foo', ... })style metadata (currently only scalar literal args work). This is Rust work in Perry core. -
Class instantiation from decorator registry — perry-angular needs to intercept decorated classes at module init time and register them. Perry supports module-level
initcode, so this is doable. -
No template compiler needed (Track 1) — by requiring
render()instead of template strings, Perry's existing TypeScript compilation handles everything. -
RxJS minimal stub — for apps that use
Observable,Subject,BehaviorSubject. ~200 lines of TypeScript backed by Perry'sStatereactive system.
Given the alignment between Ionic's component semantics and Perry's native widget library, Ionic is the higher-value target for a first release:
- Ionic users are already building mobile-first apps → Perry targets iOS/Android
- Ionic's component API is high-level and stable
- Each
ion-*component maps cleanly to a Perry native widget - The implementation is mostly TypeScript (~400-600 lines for perry-ionic)
A perry-ionic adapter that supports ~20 core Ionic components would enable real-world Ionic apps to compile to native with Perry, which is a compelling story.
- Map 20 core Ionic components to Perry widgets
- Standalone (no Angular dependency) — works as function calls
- Demo: A real Ionic-style app compiled to iOS/Android native
@Component,@Injectable,@Input,@Output,NgModule- DI container with constructor injection
- Lifecycle interfaces
bootstrapApplicationentry point- Requires Perry core fix: decorator object literal parsing in
lower.rs
- Wire
perry-ionicintoperry-angular's component registry - Ionic components usable as Angular components
- Demo: TodoMVC-style Ionic Angular app → Perry native
- Perry adds a template parser for Angular's
template:string syntax - Enables closer 1:1 Angular source compatibility
- Needed for true "drop-in" without source changes
/Users/amlug/projects/perry-ionic/
├── package.json # nativeModule, packageAliases for @ionic/angular
└── src/
└── index.ts # ~500 lines: IonButton, IonInput, IonList, etc.
/Users/amlug/projects/perry-angular/
├── package.json # nativeModule, packageAliases for @angular/*
└── src/
└── index.ts # ~800 lines: decorators, DI, lifecycle, bootstrap
/Users/amlug/projects/perry/crates/perry-hir/src/lower.rs—lower_decoratorsfunction (lines ~723-760) — extend to parse object literal decorator arguments
Yes, Perry can provide a drop-in replacement for Angular/Ionic, with the following caveats:
- Full template compilation is a significant Rust engineering effort — defer to Phase 4
- Ionic is immediately feasible and is the best first target given native widget alignment
- Angular DI/class patterns are feasible once Perry's decorator parser supports object literals
- The pattern mirrors perry-react: a thin TypeScript shim that maps framework concepts to Perry UI primitives, activated via
packageAliases
The work is well-defined and builds on proven patterns already in the codebase.