Skip to content

PerryTS/angular

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Feasibility Study: Perry Drop-in for Angular (+ Ionic)

Context

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).


Executive Summary

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

What the React Precedent Tells Us

perry-react/src/index.ts works because:

  1. JSX is resolved at compile time — Perry's compiler transforms <div> into createElement("div", ...) calls before the module runs. No template parser is needed at runtime.
  2. React's mental model is functional — components are plain functions, no decorator metadata processing required.
  3. packageAliases is the magic shim — Perry rewires import { useState } from "react" to perry-react transparently.
  4. The rendering strategy is simple: build widget tree synchronously, clear + rebuild on state change.

Angular: Fundamental Differences vs React

1. Template Compilation (Biggest Blocker)

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)

2. Decorator Metadata (Partially Supported)

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.

3. Dependency Injection

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).

4. Zone.js / Change Detection

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.

5. RxJS

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 rxjsperry-rxjs (a thin reactive wrapper over Perry's State)

Ionic: Much More Tractable

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 → Perry Widget Mapping (Natural Fit)

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 Wrappers

@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.


Recommended Approach: Pragmatic Two-Track

Track 1: perry-angular — Class/DI Patterns (High Feasibility)

What it covers:

  • @Component, @Injectable, @Directive, @NgModule, @Input, @Output decorators
  • 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"
    }
  }
}

Track 2: perry-ionic — High-Level Component Mapping (High Feasibility)

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"
    }
  }
}

Perry Prerequisites / Gaps to Close

  1. Decorator object literal parsingperry-hir/src/lower.rs needs to support @Component({ selector: 'foo', ... }) style metadata (currently only scalar literal args work). This is Rust work in Perry core.

  2. Class instantiation from decorator registry — perry-angular needs to intercept decorated classes at module init time and register them. Perry supports module-level init code, so this is doable.

  3. No template compiler needed (Track 1) — by requiring render() instead of template strings, Perry's existing TypeScript compilation handles everything.

  4. RxJS minimal stub — for apps that use Observable, Subject, BehaviorSubject. ~200 lines of TypeScript backed by Perry's State reactive system.


Ionic as Priority

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.


Implementation Phasing

Phase 1 — perry-ionic standalone (2–3 weeks)

  • 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

Phase 2 — perry-angular DI/lifecycle core (2–3 weeks)

  • @Component, @Injectable, @Input, @Output, NgModule
  • DI container with constructor injection
  • Lifecycle interfaces
  • bootstrapApplication entry point
  • Requires Perry core fix: decorator object literal parsing in lower.rs

Phase 3 — Angular + Ionic integration (1–2 weeks)

  • Wire perry-ionic into perry-angular's component registry
  • Ionic components usable as Angular components
  • Demo: TodoMVC-style Ionic Angular app → Perry native

Phase 4 — Template mini-compiler (optional, 1–2 months, Rust work)

  • 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

Files to Create

/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

Perry Core File to Modify (prerequisite)

  • /Users/amlug/projects/perry/crates/perry-hir/src/lower.rslower_decorators function (lines ~723-760) — extend to parse object literal decorator arguments

Verdict

Yes, Perry can provide a drop-in replacement for Angular/Ionic, with the following caveats:

  1. Full template compilation is a significant Rust engineering effort — defer to Phase 4
  2. Ionic is immediately feasible and is the best first target given native widget alignment
  3. Angular DI/class patterns are feasible once Perry's decorator parser supports object literals
  4. 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.

About

Drop-in replacement for @angular/core and @ionic/angular — compile Angular/Ionic apps to native with Perry

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors