@analogjs/angular-compiler 2.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/COMPILER.md +474 -0
  2. package/README.md +5 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +5 -0
  5. package/src/index.js +6 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/ast-translator.d.ts +40 -0
  8. package/src/lib/ast-translator.js +243 -0
  9. package/src/lib/ast-translator.js.map +1 -0
  10. package/src/lib/compile.d.ts +24 -0
  11. package/src/lib/compile.js +630 -0
  12. package/src/lib/compile.js.map +1 -0
  13. package/src/lib/defer.d.ts +23 -0
  14. package/src/lib/defer.js +158 -0
  15. package/src/lib/defer.js.map +1 -0
  16. package/src/lib/dts-reader.d.ts +25 -0
  17. package/src/lib/dts-reader.js +326 -0
  18. package/src/lib/dts-reader.js.map +1 -0
  19. package/src/lib/hmr.d.ts +12 -0
  20. package/src/lib/hmr.js +49 -0
  21. package/src/lib/hmr.js.map +1 -0
  22. package/src/lib/index.d.ts +5 -0
  23. package/src/lib/index.js +6 -0
  24. package/src/lib/index.js.map +1 -0
  25. package/src/lib/jit-metadata.d.ts +11 -0
  26. package/src/lib/jit-metadata.js +201 -0
  27. package/src/lib/jit-metadata.js.map +1 -0
  28. package/src/lib/jit-transform.d.ts +21 -0
  29. package/src/lib/jit-transform.js +207 -0
  30. package/src/lib/jit-transform.js.map +1 -0
  31. package/src/lib/js-emitter.d.ts +8 -0
  32. package/src/lib/js-emitter.js +317 -0
  33. package/src/lib/js-emitter.js.map +1 -0
  34. package/src/lib/metadata.d.ts +36 -0
  35. package/src/lib/metadata.js +465 -0
  36. package/src/lib/metadata.js.map +1 -0
  37. package/src/lib/registry.d.ts +32 -0
  38. package/src/lib/registry.js +177 -0
  39. package/src/lib/registry.js.map +1 -0
  40. package/src/lib/resource-inliner.d.ts +17 -0
  41. package/src/lib/resource-inliner.js +109 -0
  42. package/src/lib/resource-inliner.js.map +1 -0
  43. package/src/lib/style-ast.d.ts +8 -0
  44. package/src/lib/style-ast.js +110 -0
  45. package/src/lib/style-ast.js.map +1 -0
  46. package/src/lib/styles.d.ts +13 -0
  47. package/src/lib/styles.js +60 -0
  48. package/src/lib/styles.js.map +1 -0
  49. package/src/lib/utils.d.ts +8 -0
  50. package/src/lib/utils.js +71 -0
  51. package/src/lib/utils.js.map +1 -0
  52. package/vite.config.d.ts +2 -0
  53. package/vite.config.js +19 -0
  54. package/vite.config.js.map +1 -0
package/COMPILER.md ADDED
@@ -0,0 +1,474 @@
1
+ # Analog Angular Compiler
2
+
3
+ A lightweight Angular compiler that transforms decorators and signal-based reactive APIs into Ivy static definitions. Designed for fast dev server compilation via Vite, without requiring a full TypeScript program.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @analogjs/angular-compiler
9
+ ```
10
+
11
+ Peer dependencies: `@angular/compiler` >=19, `@angular/compiler-cli` >=19, `@angular/build` >=19, `vite` >=6.
12
+
13
+ ## Entry Point
14
+
15
+ | Import | Exports | Use case |
16
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
17
+ | `@analogjs/angular-compiler` | `compile()`, `scanFile()`, `scanDtsFile()`, `scanPackageDts()`, `collectImportedPackages()`, `jitTransform()`, `inlineResourceUrls()`, `extractInlineStyles()` | Programmatic compiler API |
18
+
19
+ The compiler is integrated into `@analogjs/vite-plugin-angular` via the `experimental.useAnalogCompiler` flag — there is no separate Vite plugin entry point.
20
+
21
+ ## Usage
22
+
23
+ ### Via Analog Platform (recommended)
24
+
25
+ ```ts
26
+ import analog from '@analogjs/platform';
27
+
28
+ export default defineConfig({
29
+ plugins: [
30
+ analog({
31
+ vite: {
32
+ experimental: {
33
+ useAnalogCompiler: true,
34
+ },
35
+ },
36
+ }),
37
+ ],
38
+ });
39
+ ```
40
+
41
+ ### Programmatic API
42
+
43
+ ```ts
44
+ import {
45
+ compile,
46
+ scanFile,
47
+ scanDtsFile,
48
+ scanPackageDts,
49
+ collectImportedPackages,
50
+ jitTransform,
51
+ inlineResourceUrls,
52
+ extractInlineStyles,
53
+ } from '@analogjs/angular-compiler';
54
+
55
+ // Compile a single file
56
+ const result = compile(sourceCode, fileName, { registry });
57
+ // result.code — compiled JavaScript
58
+ // result.map — V3 source map
59
+ // result.resourceDependencies — external template/style paths read
60
+
61
+ // Scan a source file for Angular metadata (uses OXC Rust parser)
62
+ const entries = scanFile(code, fileName);
63
+ // entries: [{ selector, kind, className, fileName, inputs, outputs, ... }]
64
+
65
+ // Scan a .d.ts file for pre-compiled Angular declarations
66
+ const dtsEntries = scanDtsFile(dtsCode, fileName);
67
+ // Reads ɵɵDirectiveDeclaration/ComponentDeclaration/PipeDeclaration type params
68
+
69
+ // Scan all .d.ts files in an npm package
70
+ const pkgEntries = scanPackageDts('@angular/router', projectRoot);
71
+
72
+ // Collect bare-specifier package names from imports
73
+ const packages = collectImportedPackages(code, fileName);
74
+ // Set { '@angular/router', 'rxjs', ... }
75
+
76
+ // Inline templateUrl/styleUrl(s) in source code (OXC AST rewriting)
77
+ const inlined = inlineResourceUrls(sourceCode, fileName);
78
+
79
+ // Extract inline style strings for preprocessing (OXC parser)
80
+ const styles = extractInlineStyles(sourceCode, fileName);
81
+ ```
82
+
83
+ ## Architecture
84
+
85
+ ```
86
+ Source file (.ts)
87
+
88
+ ├─ vite-plugin-angular (buildStart, when useAnalogCompiler is true)
89
+ │ @angular/compiler-cli readConfiguration() → rootNames
90
+ │ OXC parser ──▶ scanFile() ──▶ ComponentRegistry
91
+ │ (selectors, inputs, outputs, pipes, NgModule exports)
92
+
93
+ └─ vite-plugin-angular (transform, per request)
94
+ collectImportedPackages() ──▶ scanPackageDts() ──▶ registry (lazy, once per package)
95
+ inlineResourceUrls() ──▶ OXC AST rewrite (templateUrl → template, styleUrl → styles)
96
+
97
+
98
+ compile(code, fileName, { registry })
99
+ ts.createSourceFile ──▶ extractMetadata / detectSignals / detectFieldDecorators
100
+
101
+
102
+ @angular/compiler
103
+ parseTemplate()
104
+ compileComponentFromMetadata()
105
+ compileDirectiveFromMetadata()
106
+ compilePipeFromMetadata()
107
+ compileNgModule() / compileInjector()
108
+ compileFactoryFunction()
109
+ compileClassMetadata()
110
+
111
+
112
+ JSEmitter (Angular output AST → JavaScript strings, ~4x faster than ts.Printer)
113
+
114
+
115
+ MagicString (surgical edits on original source → JS + source map)
116
+ ```
117
+
118
+ The compiler is integrated into `@analogjs/vite-plugin-angular` as an alternative compilation path alongside ngtsc and the Angular Compilation API. When `experimental.useAnalogCompiler` is enabled, the vite plugin:
119
+
120
+ 1. Scans all source files with `scanFile()` at build start to populate the `ComponentRegistry`
121
+ 2. Inlines external resources (`templateUrl`, `styleUrl`) via OXC AST rewriting before compilation
122
+ 3. Compiles each file on-demand with `compile()` during Vite's transform phase (runs with `enforce: 'pre'`)
123
+ 4. Strips TypeScript syntax from the output via `vite.transformWithOxc()` (handles `import type`, generics, interfaces, `implements`, etc.)
124
+ 5. Appends HMR code for leaf components in dev mode
125
+ 6. Injects synthetic imports for NgModule-exported classes
126
+
127
+ The existing vite-plugin-angular plugins (build optimizer, router, vitest, etc.) continue to work alongside the analog compiler.
128
+
129
+ ## Source Files
130
+
131
+ | File | Purpose |
132
+ | --------------------- | ---------------------------------------------------------------------------------------------- |
133
+ | `compile.ts` | Single-file AOT compiler: Ivy codegen via `@angular/compiler` APIs |
134
+ | `registry.ts` | OXC-based file scanner, extracts selectors, inputs, outputs |
135
+ | `metadata.ts` | Decorator metadata extraction (`extractMetadata`, `detectSignals`, `detectFieldDecorators`) |
136
+ | `js-emitter.ts` | Angular output AST → JavaScript string emitter (~4x faster than `ts.Printer`) |
137
+ | `ast-translator.ts` | Angular output AST → TypeScript AST translator (for complex expressions) |
138
+ | `resource-inliner.ts` | OXC-based `templateUrl`/`styleUrl` inlining and inline style extraction |
139
+ | `dts-reader.ts` | OXC-based `.d.ts` scanner for pre-compiled Angular packages (selectors, inputs, outputs) |
140
+ | `jit-transform.ts` | JIT mode: decorator metadata arrays + constructor DI + signal downleveling + decorator removal |
141
+ | `jit-metadata.ts` | JIT metadata helpers (constructor parameters, property decorators) |
142
+ | `defer.ts` | `@defer` dependency map builder |
143
+ | `hmr.ts` | HMR code generation using `ɵɵreplaceMetadata` |
144
+ | `styles.ts` | Style preprocessing via Vite's `preprocessCSS` (OXC-based extraction) |
145
+ | `utils.ts` | Type-only import detection, class finder, `forwardRef` unwrapper |
146
+
147
+ ## What's Supported
148
+
149
+ ### Decorators
150
+
151
+ | Decorator | Static Fields | Notes |
152
+ | ------------- | ------------------------------------------ | ----------------------------------------------- |
153
+ | `@Component` | `ɵcmp`, `ɵfac`, `setClassMetadata` | Full template compilation with Ivy instructions |
154
+ | `@Directive` | `ɵdir`, `ɵfac`, `setClassMetadata` | Host bindings, listeners, inputs/outputs |
155
+ | `@Pipe` | `ɵpipe`, `ɵfac`, `setClassMetadata` | Pure and impure |
156
+ | `@Injectable` | `ɵprov`, `ɵfac`, `setClassMetadata` | `providedIn` variants |
157
+ | `@NgModule` | `ɵmod`, `ɵinj`, `ɵfac`, `setClassMetadata` | Declarations, exports, providers, bootstrap |
158
+
159
+ ### Field Decorators
160
+
161
+ | Decorator | Supported |
162
+ | ------------------------------------------------------------------ | --------- |
163
+ | `@Input()` / `@Input('alias')` / `@Input({ required, transform })` | Yes |
164
+ | `@Output()` / `@Output('alias')` | Yes |
165
+ | `@ViewChild(pred, opts)` / `@ViewChildren(pred, opts)` | Yes |
166
+ | `@ContentChild(pred, opts)` / `@ContentChildren(pred, opts)` | Yes |
167
+ | `@HostBinding('prop')` | Yes |
168
+ | `@HostListener('event', ['$event'])` | Yes |
169
+
170
+ ### Dependency Injection
171
+
172
+ | Feature | Supported |
173
+ | ------------------------------------- | ------------------------------------ |
174
+ | Constructor parameter injection | Yes (type annotations as tokens) |
175
+ | `@Inject(TOKEN)` | Yes |
176
+ | `@Optional()` | Yes |
177
+ | `@Self()` / `@SkipSelf()` / `@Host()` | Yes |
178
+ | `@Attribute('name')` | Yes |
179
+ | Type-only imports (`import type`) | Detected → `ɵɵinvalidFactory` |
180
+ | Class inheritance without constructor | `ɵɵgetInheritedFactory` |
181
+ | `forwardRef(() => X)` unwrapping | Yes (in imports, providers, queries) |
182
+
183
+ ### @Component API Coverage
184
+
185
+ | Property | Status |
186
+ | -------------------------- | ------------------------------------------------------------- |
187
+ | `selector` | Supported (auto-generated for selectorless routed components) |
188
+ | `template` | Supported |
189
+ | `templateUrl` | Supported (inlined at compile time) |
190
+ | `styles` (array or string) | Supported (ShadowCss emulated encapsulation) |
191
+ | `styleUrl` / `styleUrls` | Supported (inlined at compile time) |
192
+ | `standalone` | Supported (defaults `true` for Angular 19+) |
193
+ | `changeDetection` | Supported (OnPush / Default) |
194
+ | `encapsulation` | Supported (Emulated / None / ShadowDom) |
195
+ | `imports` | Supported (resolved via registry) |
196
+ | `providers` | Supported |
197
+ | `viewProviders` | Supported |
198
+ | `animations` | Supported (passed through) |
199
+ | `exportAs` | Supported |
200
+ | `preserveWhitespaces` | Supported |
201
+ | `host` | Supported (listeners, properties, attributes) |
202
+ | `schemas` | Passed through |
203
+
204
+ ### Signal APIs
205
+
206
+ | API | Supported |
207
+ | -------------------------------------------- | ----------------------------------------------------------------------- |
208
+ | `signal()` | Yes (preserved as-is) |
209
+ | `computed()` | Yes (preserved as-is) |
210
+ | `input()` / `input.required()` | Yes (signal input descriptors with required flag, transform extraction) |
211
+ | `model()` / `model.required()` | Yes (generates input + `Change` output) |
212
+ | `output()` | Yes |
213
+ | `viewChild()` / `viewChild.required()` | Yes (signal queries) |
214
+ | `viewChildren()` | Yes (signal queries) |
215
+ | `contentChild()` / `contentChild.required()` | Yes (signal queries) |
216
+ | `contentChildren()` | Yes (signal queries) |
217
+ | `inject()` | Yes (preserved as-is) |
218
+
219
+ ### Template Features
220
+
221
+ | Feature | Supported |
222
+ | ------------------------------------------------------------------------------------------------------- | --------- |
223
+ | `@if` / `@else if` / `@else` | Yes |
224
+ | `@for` with `track`, `@empty` | Yes |
225
+ | `@for` implicit variables (`$index`, `$first`, `$last`, `$even`, `$odd`, `$count`) | Yes |
226
+ | `@switch` / `@case` / `@default` | Yes |
227
+ | `@defer` with all triggers (`on viewport`, `on idle`, `on timer`, `on hover`, `on interaction`, `when`) | Yes |
228
+ | `@defer` sub-blocks (`@loading`, `@placeholder`, `@error`) with `minimum` | Yes |
229
+ | `@defer` lazy dependency loading via `import()` | Yes |
230
+ | Nested `@defer` inside control flow | Yes |
231
+ | `@let` declarations | Yes |
232
+ | `{{ interpolation }}` | Yes |
233
+ | `[property]` binding | Yes |
234
+ | `(event)` binding | Yes |
235
+ | `[(two-way)]` binding | Yes |
236
+ | `[class.name]` / `[style.prop]` | Yes |
237
+ | `<ng-content>` (multi-slot projection) | Yes |
238
+ | Pipes in templates (with args, chained) | Yes |
239
+
240
+ ### Cross-file Resolution
241
+
242
+ | Scenario | Supported |
243
+ | ---------------------------------------------- | ------------------------------------------------------------------------- |
244
+ | Component imports component | Yes (via registry, with input/output binding resolution) |
245
+ | Component imports directive | Yes (via registry) |
246
+ | Component imports pipe | Yes (via registry) |
247
+ | Component imports NgModule | Yes (recursively expanded, handles nested NgModules with cycle detection) |
248
+ | Library imports (e.g. `RouterOutlet`) | Yes (lazily scanned from package `.d.ts` via `scanPackageDts()`) |
249
+ | Library NgModule exports | Yes (`.d.ts` scanner reads `ɵɵNgModuleDeclaration` type params) |
250
+ | Same-file imports | Yes (file-local selector fallback) |
251
+ | Self-referencing (recursive) components | Yes (component added to its own declarations) |
252
+ | Signal input bindings (`[prop]="value"`) | Yes (registry extracts `input()`, `input.required()`, `@Input()`) |
253
+ | Signal output bindings (`(event)="handler()"`) | Yes (registry extracts `output()`, `model()`, `@Output()`) |
254
+ | Two-way binding (`[(prop)]="signal"`) | Yes (via `model()` input + output registration) |
255
+
256
+ ## What's Not Supported
257
+
258
+ | Feature | Reason |
259
+ | ---------------------------- | --------------------------------------------------------------- |
260
+ | Template type checking | Requires full `ts.Program`; use Angular Language Service in IDE |
261
+ | i18n / localization | Out of scope (future consideration) |
262
+ | Partial / linker compilation | Handled by separate plugin |
263
+ | Template source maps | Angular compiler doesn't propagate sourceSpan to output AST |
264
+ | Signal debug names | Not implemented |
265
+ | `setClassDebugInfo` | Not implemented |
266
+ | Spread imports (`...Module`) | Not implemented |
267
+
268
+ ## HMR (Hot Module Replacement)
269
+
270
+ Leaf components support true HMR via Angular's `ɵɵreplaceMetadata`. When a component file changes in dev mode:
271
+
272
+ 1. Vite hot-replaces the module
273
+ 2. The `import.meta.hot.accept` callback calls `ɵɵreplaceMetadata` with the new component definition
274
+ 3. Angular merges old/new definitions and recreates matching LViews without page reload
275
+
276
+ Root components (e.g. `App`) fall back to page reload since they can't be hot-replaced without re-bootstrapping the application. Non-Angular files use Vite's default HMR.
277
+
278
+ External template and style changes invalidate the parent `.ts` module, triggering re-compilation and HMR. Preprocessed styles are cached by mtime for fast re-compilation.
279
+
280
+ ## Source Maps
281
+
282
+ The compiler generates V3 source maps via `magic-string` using surgical edits on the original source. Class bodies, methods, and expressions stay at their original character positions — only removed decorators and inserted Ivy fields are new content. The source map is passed through Vite's transform pipeline which composes it with other transforms for end-to-end mapping in browser devtools.
283
+
284
+ ## Style Preprocessing
285
+
286
+ Both external and inline styles are preprocessed via Vite's `preprocessCSS` API:
287
+
288
+ - **External styles** (`.scss`, `.sass`, `.less`, `.styl` via `styleUrl`/`styleUrls`): extracted via OXC parser, read, preprocessed, and passed to the compiler via `resolvedStyles`
289
+ - **Inline styles** (`styles: [...]` or `styles: \`...\``): extracted via OXC parser (`extractInlineStyles`), preprocessed, and passed via `resolvedInlineStyles`
290
+ - **Template `<style>` tags**: Angular's `parseTemplate()` extracts `<style>` blocks from the HTML template. These are merged with the decorator-level `styles` array so both sources contribute to the compiled output.
291
+
292
+ The `inlineStyleLanguage` option (default: `'scss'`) controls the file extension used for inline style preprocessing. Set to `'css'` to disable inline preprocessing.
293
+
294
+ ## Comparison with Angular's Compilers
295
+
296
+ ### vs ngtsc (Angular's native compiler)
297
+
298
+ Both produce identical Ivy output because both call the same `@angular/compiler` APIs (`compileComponentFromMetadata`, `parseTemplate`, `compileFactoryFunction`, etc.). The template instructions are byte-for-byte equivalent.
299
+
300
+ | | ngtsc | This compiler |
301
+ | ------------------------------- | ---------------------------------------- | ------------------------------------------------------------ |
302
+ | Size | ~200,000+ lines | ~2,000 lines |
303
+ | Requires `ts.Program` | Yes (reads all files, resolves modules) | No |
304
+ | Type checking | Full TS + template type checking | None (use Angular Language Service) |
305
+ | Template compilation | Full Ivy instructions | Full Ivy instructions (same APIs) |
306
+ | Output format | `ɵɵdefineComponent` (final) | `ɵɵdefineComponent` (final) |
307
+ | Global analysis | Via type checker (full scope resolution) | Via tsconfig + OXC registry scan |
308
+ | Constructor DI | Full (via type checker) | Full (via AST parameter analysis) |
309
+ | `setClassMetadata` | Yes | Yes |
310
+ | Source maps | Yes (via TS emitter) | Yes (via MagicString surgical edits + inline string emitter) |
311
+ | HMR | Full (with tracking metadata) | Leaf components (root falls back to reload) |
312
+ | `@defer` lazy loading | Yes | Yes |
313
+ | SCSS preprocessing | Via `@angular/build` | Via Vite `preprocessCSS` + LmdbCacheStore |
314
+ | i18n | Full ICU extraction + localization | Not supported |
315
+ | Template type checking | Full (`strictTemplates`) | Not supported |
316
+ | Incremental compilation | `ts.Program` reuse | Per-file (Vite handles caching) |
317
+ | Diagnostic messages | Hundreds of template/binding errors | Unresolved selector warnings only |
318
+ | Partial compilation (libraries) | `ɵɵngDeclareComponent` | Not in scope |
319
+ | Declaration files (`.d.ts`) | Yes | Not in scope |
320
+
321
+ #### Performance
322
+
323
+ | Metric | ngtsc | This compiler |
324
+ | ---------------------------- | ------------------ | --------------- |
325
+ | Cold build (500 components) | 5-15s | <1s (on-demand) |
326
+ | Hot rebuild (1 file changed) | 200-500ms | 2-5ms |
327
+ | Dev server start | 3-10s | <1s |
328
+ | Registry scan (1000 files) | N/A (type checker) | ~37ms (OXC) |
329
+
330
+ #### The Tradeoff
331
+
332
+ ngtsc gives **compile-time safety** — wrong template bindings, missing inputs, and type mismatches are caught before the browser runs. This compiler gives **speed** — identical Ivy output, identical runtime behavior, but no compile-time template validation. With Angular Language Service running in the IDE, the developer experience is nearly identical — errors show as red squiggles in the editor instead of terminal output.
333
+
334
+ ### vs Angular Local Compilation
335
+
336
+ | | Local Compilation | This compiler |
337
+ | --------------------------- | -------------------------------- | ------------------------------- |
338
+ | Output format | `ɵɵngDeclareComponent` (partial) | `ɵɵdefineComponent` (final) |
339
+ | Linker required | Yes | No |
340
+ | Template compiled at | Link time | Compile time |
341
+ | Selector matching | Deferred to linker | Done via global analysis plugin |
342
+ | Cross-version compatibility | Yes (stable declaration format) | No (tied to Angular version) |
343
+ | Use case | Library publishing (npm) | Application dev server |
344
+ | Requires `ts.Program` | No | No |
345
+
346
+ ### vs esbuild/SWC (type stripping)
347
+
348
+ | | esbuild/SWC | This compiler |
349
+ | -------------------- | ------------------------------ | -------------------------------------- |
350
+ | TypeScript handling | Strip types only | Strip types + transform decorators |
351
+ | Angular awareness | None | Full (templates, signals, Ivy codegen) |
352
+ | Template compilation | N/A | Full Ivy instructions |
353
+ | Speed | ~0.01ms/file | ~0.5-2ms/file |
354
+ | Output | Valid JS (no Angular metadata) | Valid JS + Ivy static fields |
355
+
356
+ ## Performance
357
+
358
+ ### Per-file Compilation
359
+
360
+ | Component Complexity | Time |
361
+ | -------------------------------------------- | ------ |
362
+ | Simple (1 element, no signals) | ~0.5ms |
363
+ | Medium (control flow, signals, styles) | ~1.8ms |
364
+ | Complex (nested control flow, many bindings) | ~3ms |
365
+
366
+ In Vite dev mode, only requested files are compiled on demand. A typical page load compiles 10-20 files (~20-40ms total).
367
+
368
+ ### Compilation Breakdown (medium component)
369
+
370
+ | Phase | % of time | Tool |
371
+ | ------------------ | --------- | ----------------------------------- |
372
+ | Template parsing | ~46% | `@angular/compiler` (JS) |
373
+ | TypeScript parsing | ~29% | `ts.createSourceFile` (JS) |
374
+ | Code emission | ~25% | Inline string emitter + MagicString |
375
+
376
+ The dominant cost is Angular's template parser — JavaScript that can't be replaced with Rust without reimplementing the Angular template compiler.
377
+
378
+ ### Registry Cold Scan
379
+
380
+ | Files | OXC (parse + walk) | TypeScript (parse only) |
381
+ | ----- | ------------------ | ----------------------- |
382
+ | 200 | ~8ms | ~23ms |
383
+ | 500 | ~19ms | ~55ms |
384
+ | 1000 | ~37ms | ~55ms |
385
+
386
+ The registry scan uses OXC's native Rust parser for ~1.5x faster file scanning at build start.
387
+
388
+ ## Angular Version Compatibility
389
+
390
+ The compiler detects the installed `@angular/compiler` version at startup and adapts:
391
+
392
+ | Feature | Angular 19 | Angular 20+ | Angular 21+ |
393
+ | -------------------------- | ---------- | ------------------------ | -------------------- |
394
+ | `hasDirectiveDependencies` | Omitted | Set when imports present | Same |
395
+ | `externalStyles` | Omitted | Same | Available (not used) |
396
+ | All other APIs | Compatible | Compatible | Compatible |
397
+
398
+ Supported range: **Angular 19+**. Conformance tested against **Angular 17-21**.
399
+
400
+ ## Future Architecture (tsgo)
401
+
402
+ When TypeScript moves to `tsgo` (Go-based compiler), Angular will need to separate type checking from decorator transformation — the same split this compiler already makes:
403
+
404
+ ```
405
+ tsgo (Go) Angular Transform (JS)
406
+ ├─ Type stripping ├─ compile() per file
407
+ ├─ Module resolution ├─ @angular/compiler APIs
408
+ └─ Type checking └─ angular() plugin
409
+ └─ Registry scan (OXC/Rust)
410
+ ```
411
+
412
+ This compiler's architecture — single-file transforms using `@angular/compiler` with global analysis as a separate registry — is the likely direction for Angular's compiler when `tsgo` replaces `tsc`.
413
+
414
+ ## Test Suite
415
+
416
+ | File | Tests | Coverage |
417
+ | ------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
418
+ | `jit-transform.spec.ts` | 32 | Decorator conversion, member decorator removal (@Input, @Output, @ViewChild, @ContentChild), constructor parameter decorator removal (@Inject, @Optional), constructor DI, signal API downleveling, external resources, edge cases, ReflectionCapabilities integration |
419
+ | `integration.spec.ts` | 50 | Registry input/output extraction, cross-component binding, constant pool ordering, assignment precedence, templateUrl inlining, content projection, pipe chaining, template refs, two-way binding, computed signals, safe navigation, @defer triggers, @if alias, multi-component files, duplicate i0 prevention, OXC resource inlining, arrow object literal wrapping, .d.ts extraction, signal field preservation, decorator stripping, collectImportedPackages, non-Angular passthrough, template `<style>` merging, NgModule export expansion, member decorator removal, self-referencing components, .d.ts NgModule scanning, TS syntax preservation, lazy dependency arrays, directive compilation, recursive NgModule expansion, circular NgModule handling |
420
+ | `dts-reader.spec.ts` | 7 | Directive/component/pipe extraction from `.d.ts`, signal inputs, aliased inputs, multiple classes |
421
+ | `component.spec.ts` | 48 | All @Component features, signals (including required variants), control flow, defer, pipes, content projection, external resources, resource dependencies, providers, source maps |
422
+ | `ast-translator.spec.ts` | 43 | Every AST visitor method (expressions + statements), ngDevMode global |
423
+ | `decorator-fields.spec.ts` | 15 | @Input, @Output, @ViewChild, @ContentChild, @HostBinding, @HostListener field decorators |
424
+ | `constructor-di.spec.ts` | 8 | Constructor DI: @Inject, @Optional, inheritance, union types, multiple params |
425
+ | `error-handling.spec.ts` | 7 | Unknown decorators, undecorated classes, selectorless components, forwardRef, invalid templates |
426
+ | `registry.spec.ts` | 6 | All decorator types, multi-declaration, NgModule exports |
427
+ | `cross-file-resolution.spec.ts` | 5 | Cross-file component, pipe, directive resolution |
428
+ | `ngmodule.spec.ts` | 3 | Compilation, providers, export resolution |
429
+ | `injectable.spec.ts` | 3 | `providedIn` variants |
430
+ | `directive.spec.ts` | 2 | Host bindings, exportAs |
431
+ | `pipe.spec.ts` | 2 | Pure and impure |
432
+ | `compile.spec.ts` | 2 | Original smoke tests |
433
+ | `conformance.spec.ts` | 161 | Angular compliance test suite (v17-v21, 87%+ Ivy instruction match) |
434
+
435
+ ### Conformance Testing
436
+
437
+ The compiler is validated against Angular's official compliance test suite. A conformance test runner compares compiled Ivy instruction output against Angular's expected patterns with instruction normalization (`ɵɵtemplate`↔`ɵɵdomTemplate`, named↔anonymous functions).
438
+
439
+ #### Pass Rates by Angular Version
440
+
441
+ | Angular | Pass Rate | Tests |
442
+ | ------------------ | --------- | ----- |
443
+ | v17 (latest patch) | 93.2% | 120 |
444
+ | v18 (latest patch) | 82.9% | 143 |
445
+ | v19 (latest patch) | 83.5% | 151 |
446
+ | v20 (latest patch) | 91.5% | 155 |
447
+ | v21 (latest patch) | 91.8% | 160 |
448
+ | latest | 91.8% | 160 |
449
+ | next (v22.0.0) | 91.8% | 160 |
450
+
451
+ Remaining soft-failures are output formatting differences (`@defer` multi-file deps, named function patterns), not functional issues. All versions produce 0 hard test failures.
452
+
453
+ #### Running Conformance Tests
454
+
455
+ ```bash
456
+ # Specific major version (resolves latest patch)
457
+ bash packages/angular-compiler/scripts/setup-conformance.sh 19
458
+ ANGULAR_SOURCE_DIR=.angular-conformance npx vitest run packages/angular-compiler/src/lib/conformance.spec.ts
459
+
460
+ # Exact version
461
+ bash packages/angular-compiler/scripts/setup-conformance.sh 21.0.0
462
+
463
+ # Latest stable release (auto-detected via GitHub API)
464
+ bash packages/angular-compiler/scripts/setup-conformance.sh
465
+ bash packages/angular-compiler/scripts/setup-conformance.sh latest
466
+
467
+ # Latest prerelease (-next.N)
468
+ bash packages/angular-compiler/scripts/setup-conformance.sh next
469
+
470
+ # Local (auto-detects ~/projects/angular/angular if present)
471
+ npx vitest run packages/angular-compiler/src/lib/conformance.spec.ts
472
+ ```
473
+
474
+ CI runs a matrix of Angular 17, 18, 19, 20, 21, latest, and next on every push to `feat/angular-compiler` via `.github/workflows/conformance.yml`.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Analog
2
+
3
+ The fullstack meta-framework for Angular
4
+
5
+ Learn more at [analogjs.org](https://analogjs.org)
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@analogjs/angular-compiler",
3
+ "version": "2.5.0-beta.1",
4
+ "description": "Lightweight compiler for Angular",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "angular",
9
+ "compiler",
10
+ "vite",
11
+ "ivy",
12
+ "signals",
13
+ "analogjs"
14
+ ],
15
+ "author": "Brandon Roberts <robertsbt@gmail.com>",
16
+ "bugs": {
17
+ "url": "https://github.com/analogjs/analog/issues"
18
+ },
19
+ "homepage": "https://github.com/analogjs/analog#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/analogjs/analog.git"
23
+ },
24
+ "funding": {
25
+ "type": "github",
26
+ "url": "https://github.com/sponsors/brandonroberts"
27
+ },
28
+ "peerDependencies": {
29
+ "@angular/compiler": ">=19.0.0",
30
+ "@angular/compiler-cli": ">=19.0.0"
31
+ },
32
+ "dependencies": {
33
+ "magic-string": "^0.30.0",
34
+ "oxc-parser": "^0.121.0"
35
+ },
36
+ "exports": {
37
+ "./package.json": "./package.json",
38
+ ".": {
39
+ "types": "./src/index.d.ts",
40
+ "import": "./src/index.js",
41
+ "default": "./src/index.js"
42
+ }
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": false
47
+ },
48
+ "types": "./src/index.d.ts",
49
+ "module": "./src/index.js",
50
+ "main": "./src/index.js"
51
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { compile, type CompileResult, type CompileOptions, } from './lib/compile.js';
2
+ export { scanFile, type RegistryEntry, type ComponentRegistry, } from './lib/registry.js';
3
+ export { scanDtsFile, scanPackageDts, collectImportedPackages, } from './lib/dts-reader.js';
4
+ export { jitTransform, type JitTransformResult } from './lib/jit-transform.js';
5
+ export { inlineResourceUrls, extractInlineStyles, } from './lib/resource-inliner.js';
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { compile, } from './lib/compile.js';
2
+ export { scanFile, } from './lib/registry.js';
3
+ export { scanDtsFile, scanPackageDts, collectImportedPackages, } from './lib/dts-reader.js';
4
+ export { jitTransform } from './lib/jit-transform.js';
5
+ export { inlineResourceUrls, extractInlineStyles, } from './lib/resource-inliner.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/angular-compiler/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,GAGR,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,GAGT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,WAAW,EACX,cAAc,EACd,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAA2B,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,40 @@
1
+ import * as o from '@angular/compiler';
2
+ import * as ts from 'typescript';
3
+ export declare class AstTranslator implements o.ExpressionVisitor, o.StatementVisitor {
4
+ visitWritePropExpr(ast: any, context: any): ts.BinaryExpression;
5
+ visitInvokeMethodExpr(ast: any, context: any): ts.CallExpression;
6
+ visitWriteVarExpr(ast: any, context: any): ts.BinaryExpression;
7
+ visitWriteKeyExpr(ast: any, context: any): ts.BinaryExpression;
8
+ visitTaggedTemplateLiteralExpr(ast: any, context: any): ts.TaggedTemplateExpression;
9
+ visitReadVarExpr(ast: o.ReadVarExpr, context: any): ts.Identifier | ts.ThisExpression | ts.SuperExpression;
10
+ visitReadPropExpr(ast: o.ReadPropExpr, context: any): ts.PropertyAccessExpression;
11
+ visitReadKeyExpr(ast: o.ReadKeyExpr, context: any): ts.ElementAccessExpression;
12
+ visitLiteralExpr(ast: o.LiteralExpr, context: any): ts.StringLiteral | ts.NumericLiteral | ts.PrefixUnaryExpression | ts.TrueLiteral | ts.FalseLiteral | ts.VoidExpression | ts.NullLiteral;
13
+ visitLiteralArrayExpr(ast: o.LiteralArrayExpr, context: any): ts.ArrayLiteralExpression;
14
+ visitSpreadElementExpr(ast: o.SpreadElementExpr, context: any): ts.SpreadElement;
15
+ visitLiteralMapExpr(ast: o.LiteralMapExpr, context: any): ts.ObjectLiteralExpression;
16
+ visitInvokeFunctionExpr(ast: o.InvokeFunctionExpr, context: any): ts.CallExpression;
17
+ visitInstantiateExpr(ast: o.InstantiateExpr, context: any): ts.NewExpression;
18
+ visitTemplateLiteralExpr(ast: o.TemplateLiteralExpr, context: any): ts.TemplateExpression;
19
+ visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, context: any): ts.BinaryExpression;
20
+ visitConditionalExpr(ast: o.ConditionalExpr, context: any): ts.ConditionalExpression;
21
+ visitNotExpr(ast: o.NotExpr, context: any): ts.PrefixUnaryExpression;
22
+ visitTypeofExpr(ast: o.TypeofExpr, context: any): ts.TypeOfExpression;
23
+ visitUnaryOperatorExpr(ast: o.UnaryOperatorExpr, context: any): ts.PrefixUnaryExpression;
24
+ visitFunctionExpr(ast: o.FunctionExpr, context: any): ts.ArrowFunction;
25
+ visitArrowFunctionExpr(ast: o.ArrowFunctionExpr, context: any): ts.ArrowFunction;
26
+ visitDynamicImportExpr(ast: o.DynamicImportExpr, context: any): ts.CallExpression;
27
+ visitParenthesizedExpr(ast: o.ParenthesizedExpr, context: any): ts.ParenthesizedExpression;
28
+ visitCommaExpr(ast: o.CommaExpr, context: any): any;
29
+ visitVoidExpr(ast: o.VoidExpr, context: any): ts.VoidExpression;
30
+ visitLocalizedString(ast: o.LocalizedString, context: any): void;
31
+ visitRegularExpressionLiteral(ast: o.RegularExpressionLiteralExpr, context: any): ts.RegularExpressionLiteral;
32
+ visitTemplateLiteralElementExpr(ast: o.TemplateLiteralElementExpr, context: any): ts.StringLiteral;
33
+ visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, context: any): ts.Expression;
34
+ visitExternalExpr(ast: o.ExternalExpr, context: any): ts.Identifier | ts.PropertyAccessExpression;
35
+ visitDeclareVarStmt(stmt: o.DeclareVarStmt, context: any): ts.VariableStatement;
36
+ visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, context: any): ts.FunctionDeclaration;
37
+ visitExpressionStmt(stmt: o.ExpressionStatement, context: any): ts.ExpressionStatement;
38
+ visitReturnStmt(stmt: o.ReturnStatement, context: any): ts.ReturnStatement;
39
+ visitIfStmt(stmt: o.IfStmt, context: any): ts.IfStatement;
40
+ }