Skip to content

Commit 2f40ae5

Browse files
Brooooooklynclaude
andauthored
feat: implement JIT compilation output for transformAngularFile (#98)
* feat: implement JIT compilation output for transformAngularFile When `jit: true` is passed, the compiler now produces Angular JIT-compatible output instead of AOT-compiled code. This matches the output format of Angular CLI's JitCompilation class: - Decorator downleveling via `__decorate` from tslib - templateUrl/styleUrl replaced with `angular:jit:template:file;` imports - Constructor params emitted as `static ctorParameters` for runtime DI - Class restructured to `let X = class X {}; X = __decorate([...], X);` - Templates are NOT compiled (runtime JIT compiler handles that) - Import elision disabled (ctor param types needed at runtime) Closes #97 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: JIT transform correctly downlevels member decorators and union types Fixes two bugs identified in code review: 1. (High) Member decorators (@input, @output, @HostBinding, etc.) were removed from class bodies without being emitted as `static propDecorators`. Angular's JIT runtime reads `propDecorators` to discover inputs/outputs/ queries at runtime — without it, data binding silently breaks. Now emits: static propDecorators = { myInput: [{ type: Input }], myOutput: [{ type: Output }], }; 2. (Medium) `extract_type_name_from_annotation` only skipped TSNullKeyword in union types. For `undefined | T` or `null | undefined | T`, the function encountered TSUndefinedKeyword first, immediately recursed and returned None, never reaching the actual type. Fixed to try each union member and return the first resolvable name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(jit): align union type resolution with Angular's typeReferenceToExpression Angular's reference filters only `null` literal types from union types and requires exactly one non-null type to remain. Previously we iterated all union members and returned the first resolvable one, which is more permissive than Angular's behavior. With Angular-aligned semantics: - `T | null` → resolves to T (1 non-null type remains) - `undefined | T` → unresolvable (2 non-null types remain) - `null | undefined | T` → unresolvable (2 non-null types remain) Unresolvable types emit `{ type: undefined }` matching Angular's `paramType || ts.factory.createIdentifier('undefined')` fallback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 03650a7 commit 2f40ae5

11 files changed

+1426
-3
lines changed

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 729 additions & 3 deletions
Large diffs are not rendered by default.

crates/oxc_angular_compiler/tests/integration_test.rs

Lines changed: 499 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Component } from '@angular/core';
6+
import { __decorate } from "tslib";
7+
8+
let AppComponent = class AppComponent {
9+
title = 'app';
10+
};
11+
AppComponent = __decorate([
12+
Component({
13+
selector: 'app-root',
14+
template: '<h1>Hello</h1>',
15+
})
16+
], AppComponent);
17+
export { AppComponent };
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Component } from '@angular/core';
6+
import { TitleService } from './title.service';
7+
import { __decorate } from "tslib";
8+
9+
let AppComponent = class AppComponent {
10+
constructor(private titleService: TitleService) {}
11+
12+
static ctorParameters = () => [
13+
{ type: TitleService }
14+
];
15+
};
16+
AppComponent = __decorate([
17+
Component({
18+
selector: 'app-root',
19+
template: '<h1>Hello</h1>',
20+
})
21+
], AppComponent);
22+
export { AppComponent };
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Directive, Input } from '@angular/core';
6+
import { __decorate } from "tslib";
7+
8+
let HighlightDirective = class HighlightDirective {
9+
color: string = 'yellow';
10+
11+
static propDecorators = {
12+
color: [{ type: Input }]
13+
};
14+
};
15+
HighlightDirective = __decorate([
16+
Directive({
17+
selector: '[appHighlight]',
18+
standalone: true,
19+
})
20+
], HighlightDirective);
21+
export { HighlightDirective };
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Component, signal } from '@angular/core';
6+
import { RouterOutlet } from '@angular/router';
7+
import { Lib1 } from 'lib1';
8+
import { TitleService } from './title.service';
9+
import { __decorate } from "tslib";
10+
import __NG_CLI_RESOURCE__0 from "angular:jit:template:file;./app.html";
11+
import __NG_CLI_RESOURCE__1 from "angular:jit:style:file;./app.css";
12+
13+
let App = class App {
14+
titleService;
15+
title = signal('app');
16+
constructor(titleService: TitleService) {
17+
this.titleService = titleService;
18+
this.title.set(this.titleService.getTitle());
19+
}
20+
21+
static ctorParameters = () => [
22+
{ type: TitleService }
23+
];
24+
};
25+
App = __decorate([
26+
Component({
27+
selector: 'app-root',
28+
imports: [RouterOutlet, Lib1],
29+
template: __NG_CLI_RESOURCE__0,
30+
styles: [__NG_CLI_RESOURCE__1],
31+
})
32+
], App);
33+
export { App };
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Component } from '@angular/core';
6+
import { __decorate } from "tslib";
7+
8+
let AppComponent = class AppComponent {};
9+
AppComponent = __decorate([
10+
Component({
11+
selector: 'app-root',
12+
template: '<h1>Hello</h1>',
13+
standalone: true,
14+
})
15+
], AppComponent);
16+
export { AppComponent };
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Directive, Input, Output, HostBinding, EventEmitter } from '@angular/core';
6+
import { __decorate } from "tslib";
7+
8+
let HighlightDirective = class HighlightDirective {
9+
color: string = 'yellow';
10+
title: string = '';
11+
colorChange = new EventEmitter<string>();
12+
isActive = false;
13+
14+
static propDecorators = {
15+
color: [{ type: Input }],
16+
title: [{ type: Input, args: ['aliasName'] }],
17+
colorChange: [{ type: Output }],
18+
isActive: [{ type: HostBinding, args: ['class.active'] }]
19+
};
20+
};
21+
HighlightDirective = __decorate([
22+
Directive({
23+
selector: '[appHighlight]',
24+
})
25+
], HighlightDirective);
26+
export { HighlightDirective };
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Component } from '@angular/core';
6+
import { __decorate } from "tslib";
7+
import __NG_CLI_RESOURCE__0 from "angular:jit:style:file;./app.css";
8+
9+
let AppComponent = class AppComponent {};
10+
AppComponent = __decorate([
11+
Component({
12+
selector: 'app-root',
13+
template: '<h1>Hello</h1>',
14+
styles: [__NG_CLI_RESOURCE__0],
15+
})
16+
], AppComponent);
17+
export { AppComponent };
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
source: crates/oxc_angular_compiler/tests/integration_test.rs
3+
expression: result.code
4+
---
5+
import { Component } from '@angular/core';
6+
import { __decorate } from "tslib";
7+
import __NG_CLI_RESOURCE__0 from "angular:jit:template:file;./app.html";
8+
9+
let AppComponent = class AppComponent {};
10+
AppComponent = __decorate([
11+
Component({
12+
selector: 'app-root',
13+
template: __NG_CLI_RESOURCE__0,
14+
standalone: true,
15+
})
16+
], AppComponent);
17+
export { AppComponent };

0 commit comments

Comments
 (0)