@analogjs/vite-plugin-angular 2.5.0-beta.9 → 2.5.1-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 (82) hide show
  1. package/README.md +24 -0
  2. package/package.json +23 -5
  3. package/src/lib/angular-vite-plugin.d.ts +12 -1
  4. package/src/lib/angular-vite-plugin.js +102 -300
  5. package/src/lib/angular-vite-plugin.js.map +1 -1
  6. package/src/lib/angular-vitest-plugin.js +2 -2
  7. package/src/lib/angular-vitest-plugin.js.map +1 -1
  8. package/src/lib/compiler/angular-version.d.ts +19 -0
  9. package/src/lib/compiler/angular-version.js +42 -0
  10. package/src/lib/compiler/angular-version.js.map +1 -0
  11. package/src/lib/compiler/class-field-lowering.d.ts +23 -0
  12. package/src/lib/compiler/class-field-lowering.js +213 -0
  13. package/src/lib/compiler/class-field-lowering.js.map +1 -0
  14. package/src/lib/compiler/compile.d.ts +44 -0
  15. package/src/lib/compiler/compile.js +1160 -0
  16. package/src/lib/compiler/compile.js.map +1 -0
  17. package/src/lib/compiler/constants.d.ts +18 -0
  18. package/src/lib/compiler/constants.js +48 -0
  19. package/src/lib/compiler/constants.js.map +1 -0
  20. package/src/lib/compiler/debug.d.ts +22 -0
  21. package/src/lib/compiler/debug.js +35 -0
  22. package/src/lib/compiler/debug.js.map +1 -0
  23. package/src/lib/compiler/defer.d.ts +47 -0
  24. package/src/lib/compiler/defer.js +203 -0
  25. package/src/lib/compiler/defer.js.map +1 -0
  26. package/src/lib/compiler/dts-reader.d.ts +35 -0
  27. package/src/lib/compiler/dts-reader.js +526 -0
  28. package/src/lib/compiler/dts-reader.js.map +1 -0
  29. package/src/lib/compiler/hmr.d.ts +16 -0
  30. package/src/lib/compiler/hmr.js +80 -0
  31. package/src/lib/compiler/hmr.js.map +1 -0
  32. package/src/lib/compiler/index.d.ts +7 -0
  33. package/src/lib/compiler/index.js +8 -0
  34. package/src/lib/compiler/index.js.map +1 -0
  35. package/src/lib/compiler/jit-metadata.d.ts +14 -0
  36. package/src/lib/compiler/jit-metadata.js +224 -0
  37. package/src/lib/compiler/jit-metadata.js.map +1 -0
  38. package/src/lib/compiler/jit-transform.d.ts +24 -0
  39. package/src/lib/compiler/jit-transform.js +269 -0
  40. package/src/lib/compiler/jit-transform.js.map +1 -0
  41. package/src/lib/compiler/js-emitter.d.ts +10 -0
  42. package/src/lib/compiler/js-emitter.js +502 -0
  43. package/src/lib/compiler/js-emitter.js.map +1 -0
  44. package/src/lib/compiler/metadata.d.ts +57 -0
  45. package/src/lib/compiler/metadata.js +894 -0
  46. package/src/lib/compiler/metadata.js.map +1 -0
  47. package/src/lib/compiler/registry.d.ts +49 -0
  48. package/src/lib/compiler/registry.js +273 -0
  49. package/src/lib/compiler/registry.js.map +1 -0
  50. package/src/lib/compiler/resource-inliner.d.ts +21 -0
  51. package/src/lib/compiler/resource-inliner.js +200 -0
  52. package/src/lib/compiler/resource-inliner.js.map +1 -0
  53. package/src/lib/compiler/style-ast.d.ts +8 -0
  54. package/src/lib/compiler/style-ast.js +110 -0
  55. package/src/lib/compiler/style-ast.js.map +1 -0
  56. package/src/lib/compiler/styles.d.ts +13 -0
  57. package/src/lib/compiler/styles.js +60 -0
  58. package/src/lib/compiler/styles.js.map +1 -0
  59. package/src/lib/compiler/test-helpers.d.ts +7 -0
  60. package/src/lib/compiler/test-helpers.js +28 -0
  61. package/src/lib/compiler/test-helpers.js.map +1 -0
  62. package/src/lib/compiler/type-elision.d.ts +26 -0
  63. package/src/lib/compiler/type-elision.js +313 -0
  64. package/src/lib/compiler/type-elision.js.map +1 -0
  65. package/src/lib/compiler/utils.d.ts +10 -0
  66. package/src/lib/compiler/utils.js +95 -0
  67. package/src/lib/compiler/utils.js.map +1 -0
  68. package/src/lib/fast-compile-plugin.d.ts +28 -0
  69. package/src/lib/fast-compile-plugin.js +420 -0
  70. package/src/lib/fast-compile-plugin.js.map +1 -0
  71. package/src/lib/utils/plugin-config.d.ts +45 -0
  72. package/src/lib/utils/plugin-config.js +89 -0
  73. package/src/lib/utils/plugin-config.js.map +1 -0
  74. package/src/lib/utils/safe-module-paths.d.ts +16 -0
  75. package/src/lib/utils/safe-module-paths.js +26 -0
  76. package/src/lib/utils/safe-module-paths.js.map +1 -0
  77. package/src/lib/utils/virtual-ids.d.ts +4 -0
  78. package/src/lib/utils/virtual-ids.js +30 -0
  79. package/src/lib/utils/virtual-ids.js.map +1 -0
  80. package/src/lib/utils/virtual-resources.d.ts +19 -0
  81. package/src/lib/utils/virtual-resources.js +47 -0
  82. package/src/lib/utils/virtual-resources.js.map +1 -0
@@ -0,0 +1,1160 @@
1
+ import * as ts from 'typescript';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as o from '@angular/compiler';
5
+ import MagicString from 'magic-string';
6
+ import { parseSync } from 'oxc-parser';
7
+ import { ConstantPool, compileComponentFromMetadata, compileDirectiveFromMetadata, compilePipeFromMetadata, compileNgModule, compileInjector, R3NgModuleMetadataKind, R3SelectorScopeMode, FactoryTarget, compileFactoryFunction, parseTemplate, makeBindingParser, parseHostBindings, ParseSourceFile, ParseLocation, ParseSourceSpan, compileClassMetadata, compileDeclareComponentFromMetadata, compileDeclareDirectiveFromMetadata, compileDeclarePipeFromMetadata, compileDeclareNgModuleFromMetadata, compileDeclareInjectorFromMetadata, compileDeclareInjectableFromMetadata, compileDeclareFactoryFunction, compileDeclareClassMetadata, } from '@angular/compiler';
8
+ import { findAllClasses } from './utils.js';
9
+ import { ANGULAR_DECORATORS, FIELD_DECORATORS } from './constants.js';
10
+ import { detectTypeOnlyImportNames, elideTypeOnlyImportsMagicString, } from './type-elision.js';
11
+ import { emitAngularExpr, emitAngularStmt, setEmitterSourceFile, setEmitterSourceCode, } from './js-emitter.js';
12
+ import { lowerClassFields } from './class-field-lowering.js';
13
+ import { extractMetadata, collectStringConstants, detectSignals, detectFieldDecorators, extractConstructorDeps, } from './metadata.js';
14
+ import { buildDeferDependencyMap } from './defer.js';
15
+ import { debugCompile } from './debug.js';
16
+ import { ANGULAR_MAJOR } from './angular-version.js';
17
+ function hasExportModifier(node) {
18
+ return (ts.canHaveModifiers(node) &&
19
+ ts
20
+ .getModifiers(node)
21
+ ?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) === true);
22
+ }
23
+ /**
24
+ * Collect identifier names referenced in eagerly-evaluated code only.
25
+ * Skips function/arrow/class bodies so that lazy references
26
+ * (e.g. `const TOKEN = () => LaterClass`) are not treated as dependencies.
27
+ */
28
+ function collectIdentifiers(node) {
29
+ const ids = new Set();
30
+ function walk(n) {
31
+ if (ts.isIdentifier(n)) {
32
+ ids.add(n.text);
33
+ }
34
+ // Stop recursing into lazily-evaluated scopes
35
+ if (ts.isFunctionLike(n) || ts.isClassLike(n)) {
36
+ return;
37
+ }
38
+ ts.forEachChild(n, walk);
39
+ }
40
+ ts.forEachChild(node, walk);
41
+ return ids;
42
+ }
43
+ /** Extract the names defined by a top-level statement. */
44
+ function getDefinedNames(stmt) {
45
+ if (ts.isVariableStatement(stmt)) {
46
+ const names = [];
47
+ for (const decl of stmt.declarationList.declarations) {
48
+ collectBindingNames(decl.name, names);
49
+ }
50
+ return names;
51
+ }
52
+ if (ts.isFunctionDeclaration(stmt) && stmt.name) {
53
+ return [stmt.name.text];
54
+ }
55
+ if (ts.isClassDeclaration(stmt) && stmt.name) {
56
+ return [stmt.name.text];
57
+ }
58
+ return [];
59
+ }
60
+ /** Recursively collect all bound identifiers from a binding name/pattern. */
61
+ function collectBindingNames(name, out) {
62
+ if (ts.isIdentifier(name)) {
63
+ out.push(name.text);
64
+ }
65
+ else if (ts.isObjectBindingPattern(name) ||
66
+ ts.isArrayBindingPattern(name)) {
67
+ for (const el of name.elements) {
68
+ if (!ts.isOmittedExpression(el)) {
69
+ collectBindingNames(el.name, out);
70
+ }
71
+ }
72
+ }
73
+ }
74
+ export function compile(sourceCode, fileName, optionsOrRegistry) {
75
+ // Backward compat: accept ComponentRegistry directly
76
+ const opts = optionsOrRegistry instanceof Map
77
+ ? { registry: optionsOrRegistry }
78
+ : optionsOrRegistry || {};
79
+ const registry = opts.registry;
80
+ const resolvedStyles = opts.resolvedStyles;
81
+ const resolvedInlineStyles = opts.resolvedInlineStyles;
82
+ const useDefineForClassFields = opts.useDefineForClassFields ?? false;
83
+ const isPartial = opts.compilationMode === 'partial';
84
+ const startTimeMs = debugCompile.enabled ? performance.now() : 0;
85
+ debugCompile('compile %s (mode=%s, registry=%d entries)', fileName, isPartial ? 'partial' : 'full', registry?.size ?? 0);
86
+ const origSourceFile = ts.createSourceFile(fileName, sourceCode, ts.ScriptTarget.Latest, true);
87
+ // OXC parse for metadata extraction (faster than TS for decorator/signal analysis)
88
+ const { program: oxcProgram } = parseSync(fileName, sourceCode);
89
+ const oxcClassMap = new Map();
90
+ for (const stmt of oxcProgram.body) {
91
+ const decl = stmt.type === 'ExportNamedDeclaration' ||
92
+ stmt.type === 'ExportDefaultDeclaration'
93
+ ? stmt.declaration
94
+ : stmt;
95
+ if (decl &&
96
+ (decl.type === 'ClassDeclaration' || decl.type === 'ClassExpression') &&
97
+ decl.id?.name) {
98
+ oxcClassMap.set(decl.id.name, decl);
99
+ }
100
+ }
101
+ // Collect module-level string constants so decorator metadata can resolve
102
+ // template-literal interpolations like `template: \`<div class="${tw}">x</div>\``.
103
+ const stringConsts = collectStringConstants(oxcProgram);
104
+ const constantPool = new ConstantPool();
105
+ const resourceDependencies = [];
106
+ const parseFile = new ParseSourceFile(sourceCode, fileName);
107
+ const parseLoc = new ParseLocation(parseFile, 0, 0, 0);
108
+ const typeSourceSpan = new ParseSourceSpan(parseLoc, parseLoc);
109
+ const typeOnlyImports = detectTypeOnlyImportNames(sourceCode);
110
+ const importSpecifierByName = new Map();
111
+ const importedNames = new Set();
112
+ for (const stmt of origSourceFile.statements) {
113
+ if (!ts.isImportDeclaration(stmt) ||
114
+ !stmt.importClause?.namedBindings ||
115
+ !ts.isNamedImports(stmt.importClause.namedBindings)) {
116
+ continue;
117
+ }
118
+ const moduleSpecifier = stmt.moduleSpecifier.text;
119
+ // Explicit `import type { X }` — declaration-level type-only import
120
+ const isDeclarationTypeOnly = stmt.importClause.isTypeOnly;
121
+ for (const element of stmt.importClause.namedBindings.elements) {
122
+ const localName = element.name.text;
123
+ importedNames.add(localName);
124
+ importSpecifierByName.set(localName, moduleSpecifier);
125
+ // Explicit `import { type X }` — specifier-level type-only import.
126
+ // Both forms erase the symbol at runtime, so they cannot be used as
127
+ // DI tokens. Add them to typeOnlyImports so extractConstructorDeps
128
+ // surfaces ɵɵinvalidFactory() instead of emitting a broken
129
+ // ɵɵdirectiveInject(X) referring to a non-existent value.
130
+ if (isDeclarationTypeOnly || element.isTypeOnly) {
131
+ typeOnlyImports.add(localName);
132
+ }
133
+ }
134
+ }
135
+ // Inject 'import * as i0 from "@angular/core"'
136
+ const sourceFile = injectAngularImport(origSourceFile);
137
+ // Build a file-local selector map as fallback when no external registry is provided.
138
+ // Skip the expensive extractMetadata scan when a registry covers all classes.
139
+ const localSelectors = new Map();
140
+ if (!registry) {
141
+ for (const [clsName, oxcNode] of oxcClassMap) {
142
+ const decs = oxcNode.decorators || [];
143
+ if (decs.length > 0) {
144
+ const meta = extractMetadata(decs[0], sourceCode, stringConsts);
145
+ if (meta?.selector) {
146
+ localSelectors.set(clsName, meta.selector.split(',')[0].trim());
147
+ }
148
+ }
149
+ }
150
+ }
151
+ const bindingParser = isPartial ? undefined : makeBindingParser();
152
+ setEmitterSourceFile(origSourceFile);
153
+ setEmitterSourceCode(sourceCode);
154
+ const classResults = [];
155
+ // Per-class hoisted helper statements returned via R3CompiledExpression.statements
156
+ // (e.g. nested template helper functions for `@if`/`@for`/`@switch` blocks).
157
+ // Older Angular majors (≤19) return these on `cmp.statements` rather than
158
+ // pushing them into the shared ConstantPool, so we collect them here and
159
+ // emit them alongside the constant-pool helpers in step 3. Without this,
160
+ // any component with control-flow blocks references undefined symbols like
161
+ // `MyComponent_Conditional_0_Template`.
162
+ const hoistedHelpers = [];
163
+ // Track synthetic imports needed for NgModule export expansion.
164
+ // Maps exported class name → import specifier.
165
+ const syntheticImports = new Map();
166
+ // ANGULAR_DECORATORS imported from utils
167
+ for (const node of findAllClasses(origSourceFile)) {
168
+ const decorators = ts.getDecorators(node);
169
+ if (!decorators || decorators.length === 0)
170
+ continue;
171
+ const className = node.name?.text;
172
+ if (!className)
173
+ continue;
174
+ const angularDecorators = decorators.filter((dec) => {
175
+ if (!ts.isCallExpression(dec.expression))
176
+ return false;
177
+ const name = dec.expression.expression.getText(origSourceFile);
178
+ return ANGULAR_DECORATORS.has(name);
179
+ });
180
+ if (angularDecorators.length === 0)
181
+ continue;
182
+ const ivyCode = [];
183
+ let targetType = FactoryTarget.Injectable;
184
+ // Store resolved resources per decorator for metadata inlining
185
+ const resolvedResources = new Map();
186
+ const classRef = {
187
+ value: new o.WrappedNodeExpr(className),
188
+ type: new o.WrappedNodeExpr(className),
189
+ };
190
+ // Detect `class Foo extends Bar` so the directive/component def
191
+ // gets `usesInheritance: true`. Angular's compiler turns that into
192
+ // `features: [InheritDefinitionFeature]`, which is what causes the
193
+ // runtime to merge inputs/outputs/queries/host bindings from the
194
+ // base class. Without it, derived directives like
195
+ // `BrnPopover extends BrnDialog` lose every input declared on the
196
+ // base, and any host directive that references those inputs by
197
+ // public name fails at runtime with NG0311.
198
+ const extendsClause = node.heritageClauses?.find((h) => h.token === ts.SyntaxKind.ExtendsKeyword);
199
+ const usesInheritance = !!extendsClause && extendsClause.types.length > 0;
200
+ // Look up the corresponding OXC class node for metadata extraction
201
+ const oxcNode = oxcClassMap.get(className);
202
+ let classCompileError = null;
203
+ angularDecorators.forEach((dec) => {
204
+ const decoratorName = dec.expression.expression.getText(origSourceFile);
205
+ // Find the matching OXC decorator by name
206
+ const oxcDec = oxcNode?.decorators?.find((d) => {
207
+ const expr = d.expression;
208
+ return (expr?.type === 'CallExpression' && expr.callee?.name === decoratorName);
209
+ });
210
+ const meta = extractMetadata(oxcDec, sourceCode, stringConsts);
211
+ const sigs = oxcNode
212
+ ? detectSignals(oxcNode, sourceCode)
213
+ : { inputs: {}, outputs: {}, viewQueries: [], contentQueries: [] };
214
+ const fields = oxcNode
215
+ ? detectFieldDecorators(oxcNode, sourceCode)
216
+ : {
217
+ inputs: {},
218
+ outputs: {},
219
+ viewQueries: [],
220
+ contentQueries: [],
221
+ hostProperties: {},
222
+ hostListeners: {},
223
+ };
224
+ const hostBindings = parseHostBindings(meta.hostRaw || {});
225
+ const hostMetadata = {
226
+ attributes: hostBindings.attributes,
227
+ listeners: { ...hostBindings.listeners, ...fields.hostListeners },
228
+ properties: { ...hostBindings.properties, ...fields.hostProperties },
229
+ specialAttributes: hostBindings.specialAttributes,
230
+ };
231
+ switch (decoratorName) {
232
+ case 'Component': {
233
+ targetType = FactoryTarget.Component;
234
+ if (!meta.selector) {
235
+ meta.selector = `ng-component-${className.toLowerCase()}`;
236
+ }
237
+ const declarations = [];
238
+ // Dedupe by class name across direct imports, tuple-barrel
239
+ // expansion, and NgModule export expansion so the final
240
+ // `dependencies: () => [...]` list never contains the same
241
+ // class twice. Otherwise Angular's runtime throws NG0300
242
+ // ("Multiple components match …") when a file lists both
243
+ // `NgIcon` and a barrel like `HlmIconImports = [HlmIcon, NgIcon]`
244
+ // that re-exports it, or when two NgModules re-export the same
245
+ // directive (e.g. FormsModule + ReactiveFormsModule →
246
+ // DefaultValueAccessor).
247
+ const seenDeclarationNames = new Set();
248
+ // Expand `imports` entries that are not directives themselves
249
+ // (e.g. NgModules and tuple barrels like `HlmSelectImports =
250
+ // [HlmSelect, HlmSelectContent, ...] as const`) into the
251
+ // underlying directive class names. Returns the original entry
252
+ // when it doesn't match either expansion case.
253
+ const collectExpandedImports = (depClassName, visited) => {
254
+ if (visited.has(depClassName))
255
+ return [];
256
+ visited.add(depClassName);
257
+ const entry = registry?.get(depClassName);
258
+ if (!entry)
259
+ return [depClassName];
260
+ if (entry.kind === 'ngmodule' && entry.exports) {
261
+ const out = [];
262
+ for (const name of entry.exports) {
263
+ out.push(...collectExpandedImports(name, visited));
264
+ }
265
+ return out;
266
+ }
267
+ if (entry.kind === 'tuple' && entry.members) {
268
+ const out = [];
269
+ for (const name of entry.members) {
270
+ out.push(...collectExpandedImports(name, visited));
271
+ }
272
+ return out;
273
+ }
274
+ return [depClassName];
275
+ };
276
+ for (const dep of Array.isArray(meta.imports) ? meta.imports : []) {
277
+ const depNode = dep.node;
278
+ const depClassName = typeof depNode === 'string'
279
+ ? depNode
280
+ : (depNode?.name ?? depNode?.type === 'Identifier')
281
+ ? depNode.name
282
+ : sourceCode.slice(depNode?.start ?? 0, depNode?.end ?? 0);
283
+ const registryEntry = registry?.get(depClassName);
284
+ // Tuple barrel: `imports: [HlmSelectImports]` where the
285
+ // const is `[HlmSelect, HlmSelectContent, ...] as const`.
286
+ // Expand into the underlying directive declarations and
287
+ // track synthetic imports for any classes not already
288
+ // referenced in this file.
289
+ if (registryEntry?.kind === 'tuple' && registryEntry.members) {
290
+ const expanded = collectExpandedImports(depClassName, new Set());
291
+ const tupleSpecifier = importSpecifierByName.get(depClassName);
292
+ for (const memberName of expanded) {
293
+ const memberEntry = registry?.get(memberName);
294
+ if (!memberEntry || memberEntry.kind === 'tuple')
295
+ continue;
296
+ const memberRef = new o.WrappedNodeExpr(memberName);
297
+ if (memberEntry.sourcePackage || tupleSpecifier) {
298
+ if (!importedNames.has(memberName)) {
299
+ syntheticImports.set(memberName, memberEntry.sourcePackage || tupleSpecifier);
300
+ }
301
+ }
302
+ const kind = memberEntry.kind === 'pipe' ? 1 : 0;
303
+ const memberDecl = {
304
+ type: memberRef,
305
+ selector: memberEntry.selector,
306
+ kind,
307
+ ...(kind === 1 ? { name: memberEntry.pipeName } : {}),
308
+ };
309
+ if (memberEntry.inputs) {
310
+ memberDecl.inputs = Object.values(memberEntry.inputs).map((i) => i.bindingPropertyName);
311
+ }
312
+ if (memberEntry.outputs) {
313
+ memberDecl.outputs = Object.values(memberEntry.outputs);
314
+ }
315
+ if (!seenDeclarationNames.has(memberName)) {
316
+ seenDeclarationNames.add(memberName);
317
+ declarations.push(memberDecl);
318
+ }
319
+ }
320
+ continue;
321
+ }
322
+ if (registryEntry?.kind === 'ngmodule' && registryEntry.exports) {
323
+ const moduleSpecifier = importSpecifierByName.get(depClassName);
324
+ // Recursively collect all non-module exports (handles nested NgModules
325
+ // like ReactiveFormsModule → ɵInternalFormsSharedModule → DefaultValueAccessor)
326
+ const allExports = [];
327
+ const expandModule = (moduleName, visited = new Set()) => {
328
+ if (visited.has(moduleName))
329
+ return;
330
+ visited.add(moduleName);
331
+ const mod = registry?.get(moduleName);
332
+ if (!mod?.exports)
333
+ return;
334
+ for (const name of mod.exports) {
335
+ const entry = registry?.get(name);
336
+ if (!entry)
337
+ continue;
338
+ if (entry.kind === 'ngmodule') {
339
+ expandModule(name, visited);
340
+ }
341
+ else {
342
+ allExports.push(name);
343
+ }
344
+ }
345
+ };
346
+ expandModule(depClassName);
347
+ for (const exportedName of allExports) {
348
+ const exportedEntry = registry?.get(exportedName);
349
+ if (exportedEntry) {
350
+ const kind = exportedEntry.kind === 'pipe' ? 1 : 0;
351
+ // Create a reference to the exported class. If it's not
352
+ // already imported, track it for synthetic import injection.
353
+ const exportedRef = new o.WrappedNodeExpr(exportedName);
354
+ if (exportedEntry.sourcePackage || moduleSpecifier) {
355
+ syntheticImports.set(exportedName, exportedEntry.sourcePackage || moduleSpecifier);
356
+ }
357
+ const decl = {
358
+ type: exportedRef,
359
+ selector: exportedEntry.selector,
360
+ kind,
361
+ ...(kind === 1 ? { name: exportedEntry.pipeName } : {}),
362
+ };
363
+ if (exportedEntry.inputs) {
364
+ decl.inputs = Object.values(exportedEntry.inputs).map((i) => i.bindingPropertyName);
365
+ }
366
+ if (exportedEntry.outputs) {
367
+ decl.outputs = Object.values(exportedEntry.outputs);
368
+ }
369
+ if (!seenDeclarationNames.has(exportedName)) {
370
+ seenDeclarationNames.add(exportedName);
371
+ declarations.push(decl);
372
+ }
373
+ }
374
+ }
375
+ continue;
376
+ }
377
+ const selector = registryEntry?.selector ?? localSelectors.get(depClassName);
378
+ const kind = registryEntry?.kind === 'pipe' ? 1 : 0;
379
+ const decl = {
380
+ type: dep,
381
+ selector: selector || `_unresolved-${depClassName}`,
382
+ kind,
383
+ ...(kind === 1 ? { name: registryEntry?.pipeName } : {}),
384
+ };
385
+ // Pass inputs/outputs from registry so template bindings resolve.
386
+ // R3DirectiveDependencyMetadata expects inputs/outputs as string[]
387
+ // of binding property names.
388
+ if (registryEntry?.inputs) {
389
+ decl.inputs = Object.values(registryEntry.inputs).map((i) => i.bindingPropertyName);
390
+ }
391
+ if (registryEntry?.outputs) {
392
+ decl.outputs = Object.values(registryEntry.outputs);
393
+ }
394
+ if (!seenDeclarationNames.has(depClassName)) {
395
+ seenDeclarationNames.add(depClassName);
396
+ declarations.push(decl);
397
+ }
398
+ }
399
+ // Add self-reference so recursive components (e.g. tree views)
400
+ // can use their own selector in their template.
401
+ // Skip in partial mode — the linker handles self-resolution.
402
+ if (!isPartial && !seenDeclarationNames.has(className)) {
403
+ const selfInputs = Object.entries(sigs.inputs).map(([, v]) => v.bindingPropertyName);
404
+ const selfOutputs = Object.values({
405
+ ...meta.outputs,
406
+ ...fields.outputs,
407
+ ...sigs.outputs,
408
+ });
409
+ seenDeclarationNames.add(className);
410
+ declarations.push({
411
+ type: classRef.value,
412
+ selector: meta.selector,
413
+ kind: 0,
414
+ ...(selfInputs.length > 0 ? { inputs: selfInputs } : {}),
415
+ ...(selfOutputs.length > 0 ? { outputs: selfOutputs } : {}),
416
+ });
417
+ }
418
+ let templateContent = meta.template || '';
419
+ if (!templateContent && meta.templateUrl) {
420
+ try {
421
+ const templatePath = path.resolve(path.dirname(fileName), meta.templateUrl);
422
+ templateContent = fs.readFileSync(templatePath, 'utf-8');
423
+ resourceDependencies.push(templatePath);
424
+ }
425
+ catch {
426
+ console.warn(`[fast-compile] Could not read template file "${meta.templateUrl}" for ${className}`);
427
+ }
428
+ }
429
+ if (Array.isArray(meta.styleUrls)) {
430
+ for (const url of meta.styleUrls) {
431
+ try {
432
+ const stylePath = path.resolve(path.dirname(fileName), url);
433
+ const styleContent = resolvedStyles?.get(stylePath) ??
434
+ fs.readFileSync(stylePath, 'utf-8');
435
+ meta.styles.push(styleContent);
436
+ resourceDependencies.push(stylePath);
437
+ }
438
+ catch {
439
+ console.warn(`[fast-compile] Could not read style file "${url}" for ${className}`);
440
+ }
441
+ }
442
+ }
443
+ if (resolvedInlineStyles) {
444
+ for (const [idx, css] of resolvedInlineStyles) {
445
+ if (idx < meta.styles.length) {
446
+ meta.styles[idx] = css;
447
+ }
448
+ }
449
+ }
450
+ // Store resolved resources for metadata inlining
451
+ resolvedResources.set(dec, {
452
+ template: templateContent || undefined,
453
+ styles: meta.styles?.length > 0 ? [...meta.styles] : undefined,
454
+ });
455
+ const parsedTemplate = parseTemplate(templateContent, fileName, {
456
+ preserveWhitespaces: meta.preserveWhitespaces,
457
+ enableI18nLegacyMessageIdFormat: opts.enableI18nLegacyMessageIdFormat ?? true,
458
+ i18nNormalizeLineEndingsInICUs: opts.i18nNormalizeLineEndingsInICUs ?? true,
459
+ });
460
+ const ivyInputs = {};
461
+ if (Array.isArray(meta.inputs)) {
462
+ meta.inputs.forEach((i) => (ivyInputs[i] = i));
463
+ }
464
+ else if (meta.inputs) {
465
+ Object.assign(ivyInputs, meta.inputs);
466
+ }
467
+ Object.assign(ivyInputs, fields.inputs);
468
+ for (const [key, val] of Object.entries(sigs.inputs)) {
469
+ const sigDesc = val;
470
+ ivyInputs[key] = {
471
+ classPropertyName: key,
472
+ // Honor the binding name (alias) extracted by detectSignals
473
+ // so `input(null, { alias: 'aria-label' })` registers with
474
+ // the public name `aria-label`, not the class property name.
475
+ bindingPropertyName: sigDesc.bindingPropertyName ?? key,
476
+ isSignal: true,
477
+ required: sigDesc.required || false,
478
+ transformFunction: sigDesc.transform || null,
479
+ };
480
+ }
481
+ const templateErrors = parsedTemplate.errors ?? [];
482
+ if (templateErrors.length > 0) {
483
+ const firstError = templateErrors[0];
484
+ classCompileError = new Error(`[fast-compile] Template parse error in ${fileName} (${className}): ${firstError.msg}`);
485
+ return;
486
+ }
487
+ const componentMeta = {
488
+ ...meta,
489
+ name: className,
490
+ type: classRef,
491
+ typeSourceSpan,
492
+ declarations,
493
+ template: {
494
+ nodes: parsedTemplate.nodes,
495
+ ngContentSelectors: parsedTemplate.ngContentSelectors,
496
+ preserveWhitespaces: parsedTemplate.preserveWhitespaces,
497
+ },
498
+ styles: [...(meta.styles || []), ...(parsedTemplate.styles || [])],
499
+ inputs: ivyInputs,
500
+ outputs: { ...meta.outputs, ...fields.outputs, ...sigs.outputs },
501
+ viewQueries: [...fields.viewQueries, ...sigs.viewQueries],
502
+ queries: [...fields.contentQueries, ...sigs.contentQueries],
503
+ host: hostMetadata,
504
+ changeDetection: meta.changeDetection ?? (isPartial ? null : 1),
505
+ encapsulation: meta.encapsulation ?? 0,
506
+ exportAs: meta.exportAs,
507
+ providers: meta.providers?.length
508
+ ? new o.LiteralArrayExpr(meta.providers)
509
+ : null,
510
+ viewProviders: meta.viewProviders?.length
511
+ ? new o.LiteralArrayExpr(meta.viewProviders)
512
+ : null,
513
+ animations: meta.animations?.length
514
+ ? new o.LiteralArrayExpr(meta.animations)
515
+ : null,
516
+ isStandalone: meta.standalone,
517
+ imports: meta.imports,
518
+ lifecycle: { usesOnChanges: false },
519
+ // Angular v19/v20's `createComponentDefinitionMap` reads
520
+ // `meta.interpolation.start` and `meta.interpolation.end`
521
+ // unconditionally (the `!== DEFAULT_INTERPOLATION_CONFIG` check
522
+ // is reference-equality, so a missing field enters the block
523
+ // and crashes with `Cannot read properties of undefined`). v21
524
+ // dropped the field from `createComponentDefinitionMap`
525
+ // entirely. Use the v19/v20 singleton when the Angular package
526
+ // exports it (matches the reference-equality check, skipping
527
+ // the partial-mode emission block) and fall back to a plain
528
+ // shape on v21+ where the field is ignored anyway.
529
+ interpolation: o.DEFAULT_INTERPOLATION_CONFIG ?? { start: '{{', end: '}}' },
530
+ usesInheritance,
531
+ defer: {
532
+ mode: 0,
533
+ blocks: buildDeferDependencyMap(parsedTemplate, sourceFile, registry, localSelectors).blocks,
534
+ },
535
+ declarationListEmitMode: 1,
536
+ i18nUseExternalIds: opts.i18nUseExternalIds ?? false,
537
+ relativeContextFilePath: fileName,
538
+ controlCreate: null,
539
+ };
540
+ if (ANGULAR_MAJOR < 18) {
541
+ // Angular v17 reads `meta.deferBlocks`, `meta.deferrableTypes`,
542
+ // and `meta.deferBlockDepsEmitMode` as flat top-level fields on
543
+ // the component metadata; v18 nested them under `meta.defer`.
544
+ // `compileComponentFromMetadata` on v17 reads `.size` on the two
545
+ // Maps unconditionally (compiler.mjs ~30770), so the fields must
546
+ // exist as Maps even for components that don't use @defer.
547
+ // Empty Maps are sufficient for the no-@defer case — components
548
+ // that actually use @defer on v17 are not supported (the v17
549
+ // @defer ABI is significantly different from v18+ and is not
550
+ // worth back-porting per the minimum-effort policy).
551
+ componentMeta.deferBlocks = new Map();
552
+ componentMeta.deferrableTypes = new Map();
553
+ componentMeta.deferBlockDepsEmitMode = 0;
554
+ }
555
+ if (ANGULAR_MAJOR >= 20) {
556
+ componentMeta.hasDirectiveDependencies =
557
+ declarations.length > 0 ||
558
+ (Array.isArray(meta.imports) && meta.imports.length > 0);
559
+ }
560
+ if (isPartial) {
561
+ // Partial compilation accesses .inputs, .outputs, .exportAs on
562
+ // each declaration — ensure they are arrays (not undefined).
563
+ componentMeta.declarations = declarations.map((d) => ({
564
+ ...d,
565
+ inputs: d.inputs ?? null,
566
+ outputs: d.outputs ?? null,
567
+ exportAs: d.exportAs ?? null,
568
+ isComponent: d.isComponent ?? false,
569
+ }));
570
+ const cmp = compileDeclareComponentFromMetadata(componentMeta, parsedTemplate, {
571
+ content: templateContent,
572
+ sourceUrl: fileName,
573
+ isInline: !meta.templateUrl,
574
+ inlineTemplateLiteralExpression: null,
575
+ });
576
+ ivyCode.push(`static ɵcmp = /*@__PURE__*/ ${emitAngularExpr(cmp.expression)}`);
577
+ for (const stmt of cmp.statements ?? []) {
578
+ hoistedHelpers.push(emitAngularStmt(stmt));
579
+ }
580
+ }
581
+ else {
582
+ const cmp = compileComponentFromMetadata(componentMeta, constantPool, bindingParser);
583
+ ivyCode.push(`static ɵcmp = /*@__PURE__*/ ${emitAngularExpr(cmp.expression)}`);
584
+ // Hoist nested template helper functions returned via
585
+ // R3CompiledExpression.statements. On Angular ≤19 these contain
586
+ // `*_Conditional_*_Template`, `*_For_*_Template`, etc. functions
587
+ // that the component definition references; on Angular ≥20 they
588
+ // are typically pushed into the shared constantPool instead and
589
+ // this loop is a no-op.
590
+ for (const stmt of cmp.statements ?? []) {
591
+ hoistedHelpers.push(emitAngularStmt(stmt));
592
+ }
593
+ }
594
+ break;
595
+ }
596
+ case 'Directive': {
597
+ targetType = FactoryTarget.Directive;
598
+ // Build proper input descriptors (same as Component path)
599
+ const dirInputs = {};
600
+ if (Array.isArray(meta.inputs)) {
601
+ meta.inputs.forEach((i) => (dirInputs[i] = i));
602
+ }
603
+ else if (meta.inputs) {
604
+ Object.assign(dirInputs, meta.inputs);
605
+ }
606
+ Object.assign(dirInputs, fields.inputs);
607
+ for (const [key, val] of Object.entries(sigs.inputs)) {
608
+ const sigDesc = val;
609
+ dirInputs[key] = {
610
+ classPropertyName: key,
611
+ // Honor the binding name (alias) extracted by detectSignals.
612
+ bindingPropertyName: sigDesc.bindingPropertyName ?? key,
613
+ isSignal: true,
614
+ required: sigDesc.required || false,
615
+ transformFunction: sigDesc.transform || null,
616
+ };
617
+ }
618
+ const directiveMeta = {
619
+ ...meta,
620
+ // Abstract base directives are declared as `@Directive()` with
621
+ // no metadata. Angular's R3DirectiveMetadata accepts
622
+ // `selector: string | null`, but the downstream selector parser
623
+ // calls `.replace()` on the value and crashes on `undefined`.
624
+ // Coerce missing selectors to `null` so abstract base classes
625
+ // compile correctly.
626
+ selector: meta.selector ?? null,
627
+ name: className,
628
+ type: classRef,
629
+ typeSourceSpan,
630
+ host: hostMetadata,
631
+ inputs: dirInputs,
632
+ outputs: { ...meta.outputs, ...fields.outputs, ...sigs.outputs },
633
+ viewQueries: [...fields.viewQueries, ...sigs.viewQueries],
634
+ queries: [...fields.contentQueries, ...sigs.contentQueries],
635
+ // Angular's compiler treats `providers` as an Expression and
636
+ // emits `ɵɵProvidersFeature(<expr>)` whenever it is truthy.
637
+ // Passing the bare JS array of WrappedNodeExpr from
638
+ // extractMetadata causes the emitter to lower it to `null`,
639
+ // which then crashes Angular at runtime in `resolveProvider`
640
+ // because it tries to read `.provide` on `null`. Wrap into a
641
+ // LiteralArrayExpr (matching the Component branch) so it
642
+ // emits a real array literal — and pass `null` when there are
643
+ // no providers so Angular skips the feature entirely.
644
+ providers: meta.providers?.length
645
+ ? new o.LiteralArrayExpr(meta.providers)
646
+ : null,
647
+ exportAs: meta.exportAs,
648
+ isStandalone: meta.standalone,
649
+ lifecycle: { usesOnChanges: false },
650
+ usesInheritance,
651
+ controlCreate: null,
652
+ };
653
+ if (isPartial) {
654
+ const dir = compileDeclareDirectiveFromMetadata(directiveMeta);
655
+ ivyCode.push(`static ɵdir = /*@__PURE__*/ ${emitAngularExpr(dir.expression)}`);
656
+ }
657
+ else {
658
+ const dir = compileDirectiveFromMetadata(directiveMeta, constantPool, bindingParser);
659
+ ivyCode.push(`static ɵdir = /*@__PURE__*/ ${emitAngularExpr(dir.expression)}`);
660
+ }
661
+ break;
662
+ }
663
+ case 'Pipe': {
664
+ targetType = FactoryTarget.Pipe;
665
+ const pipeMeta = {
666
+ ...meta,
667
+ name: className,
668
+ pipeName: meta.name,
669
+ type: classRef,
670
+ isStandalone: meta.standalone,
671
+ pure: meta.pure ?? true,
672
+ };
673
+ if (isPartial) {
674
+ const pipe = compileDeclarePipeFromMetadata(pipeMeta);
675
+ ivyCode.push(`static ɵpipe = /*@__PURE__*/ ${emitAngularExpr(pipe.expression)}`);
676
+ }
677
+ else {
678
+ const pipe = compilePipeFromMetadata(pipeMeta);
679
+ ivyCode.push(`static ɵpipe = /*@__PURE__*/ ${emitAngularExpr(pipe.expression)}`);
680
+ }
681
+ break;
682
+ }
683
+ case 'Injectable': {
684
+ targetType = FactoryTarget.Injectable;
685
+ const injectableMeta = {
686
+ name: className,
687
+ type: classRef,
688
+ typeArgumentCount: 0,
689
+ providedIn: {
690
+ expression: new o.LiteralExpr(meta.providedIn || 'root'),
691
+ forwardRef: 0,
692
+ },
693
+ };
694
+ // Forward provider configuration so `ɵprov` honors useFactory/
695
+ // useClass/useExisting/useValue. Without this, an
696
+ // `@Injectable({ useFactory: () => ... })` quietly falls back
697
+ // to constructor instantiation.
698
+ if (meta.useClass)
699
+ injectableMeta.useClass = meta.useClass;
700
+ if (meta.useFactory)
701
+ injectableMeta.useFactory = meta.useFactory;
702
+ if (meta.useExisting)
703
+ injectableMeta.useExisting = meta.useExisting;
704
+ if (meta.useValue)
705
+ injectableMeta.useValue = meta.useValue;
706
+ if (isPartial) {
707
+ const inj = compileDeclareInjectableFromMetadata(injectableMeta);
708
+ ivyCode.push(`static ɵprov = /*@__PURE__*/ ${emitAngularExpr(inj.expression)}`);
709
+ }
710
+ else {
711
+ const inj = o.compileInjectable(injectableMeta, true);
712
+ ivyCode.push(`static ɵprov = /*@__PURE__*/ ${emitAngularExpr(inj.expression)}`);
713
+ }
714
+ break;
715
+ }
716
+ case 'NgModule': {
717
+ targetType = FactoryTarget.NgModule;
718
+ const ngModuleImports = Array.isArray(meta.imports)
719
+ ? meta.imports
720
+ : [];
721
+ const ngModuleDeclarations = Array.isArray(meta.declarations)
722
+ ? meta.declarations
723
+ : [];
724
+ const ngModuleExports = Array.isArray(meta.exports)
725
+ ? meta.exports
726
+ : [];
727
+ const ngModuleBootstrap = Array.isArray(meta.bootstrap)
728
+ ? meta.bootstrap
729
+ : [];
730
+ const ngModuleMeta = {
731
+ kind: R3NgModuleMetadataKind.Global,
732
+ type: classRef,
733
+ bootstrap: ngModuleBootstrap.map((e) => ({
734
+ value: e,
735
+ type: e,
736
+ })),
737
+ declarations: ngModuleDeclarations.map((e) => ({ value: e, type: e })),
738
+ publicDeclarationTypes: null,
739
+ imports: ngModuleImports.map((e) => ({
740
+ value: e,
741
+ type: e,
742
+ })),
743
+ includeImportTypes: true,
744
+ exports: ngModuleExports.map((e) => ({
745
+ value: e,
746
+ type: e,
747
+ })),
748
+ selectorScopeMode: R3SelectorScopeMode.Inline,
749
+ containsForwardDecls: false,
750
+ schemas: [],
751
+ id: null,
752
+ };
753
+ const injectorMeta = {
754
+ name: className,
755
+ type: classRef,
756
+ providers: meta.providers
757
+ ? new o.LiteralArrayExpr(meta.providers)
758
+ : null,
759
+ imports: ngModuleImports.map((e) => e),
760
+ };
761
+ if (isPartial) {
762
+ const ngMod = compileDeclareNgModuleFromMetadata(ngModuleMeta);
763
+ ivyCode.push(`static ɵmod = /*@__PURE__*/ ${emitAngularExpr(ngMod.expression)}`);
764
+ const injector = compileDeclareInjectorFromMetadata(injectorMeta);
765
+ ivyCode.push(`static ɵinj = /*@__PURE__*/ ${emitAngularExpr(injector.expression)}`);
766
+ }
767
+ else {
768
+ const ngMod = compileNgModule(ngModuleMeta);
769
+ ivyCode.push(`static ɵmod = /*@__PURE__*/ ${emitAngularExpr(ngMod.expression)}`);
770
+ const injector = compileInjector(injectorMeta);
771
+ ivyCode.push(`static ɵinj = /*@__PURE__*/ ${emitAngularExpr(injector.expression)}`);
772
+ }
773
+ break;
774
+ }
775
+ }
776
+ });
777
+ if (classCompileError) {
778
+ throw classCompileError;
779
+ }
780
+ // Generate factory
781
+ const deps = oxcNode
782
+ ? extractConstructorDeps(oxcNode, sourceCode, typeOnlyImports)
783
+ : [];
784
+ if (deps === null) {
785
+ const baseVar = `ɵ${className}_BaseFactory`;
786
+ ivyCode.unshift(`static ɵfac = /*@__PURE__*/ (() => { let ${baseVar}; return function ${className}_Factory(__ngFactoryType__) { return (${baseVar} || (${baseVar} = i0.ɵɵgetInheritedFactory(${className})))(__ngFactoryType__ || ${className}); }; })()`);
787
+ }
788
+ else if (deps === 'invalid') {
789
+ ivyCode.unshift(`static ɵfac = function ${className}_Factory(__ngFactoryType__) { i0.ɵɵinvalidFactory(); }`);
790
+ }
791
+ else {
792
+ const factoryMeta = {
793
+ name: className,
794
+ type: classRef,
795
+ typeArgumentCount: 0,
796
+ deps,
797
+ target: targetType,
798
+ };
799
+ if (isPartial) {
800
+ const fac = compileDeclareFactoryFunction(factoryMeta);
801
+ ivyCode.unshift(`static ɵfac = /*@__PURE__*/ ${emitAngularExpr(fac.expression)}`);
802
+ }
803
+ else {
804
+ const fac = compileFactoryFunction(factoryMeta);
805
+ ivyCode.unshift(`static ɵfac = /*@__PURE__*/ ${emitAngularExpr(fac.expression)}`);
806
+ }
807
+ }
808
+ // Emit setClassMetadata for runtime decorator reflection (devMode only)
809
+ angularDecorators.forEach((dec) => {
810
+ const call = dec.expression;
811
+ const decName = call.expression.getText(origSourceFile);
812
+ const decArgsNode = call.arguments[0];
813
+ // Build the decorator args for setClassMetadata, inlining resources if needed.
814
+ const resources = resolvedResources.get(dec);
815
+ let metadataArgsExpr = null;
816
+ if (decArgsNode) {
817
+ if (resources && ts.isObjectLiteralExpression(decArgsNode)) {
818
+ // Inline external templateUrl/styleUrl(s) into the metadata so Angular's
819
+ // runtime doesn't try to fetch relative URLs (which fails during SSR).
820
+ const rewrittenProps = [];
821
+ let needsTransform = false;
822
+ for (const prop of decArgsNode
823
+ .properties) {
824
+ if (!ts.isPropertyAssignment(prop)) {
825
+ rewrittenProps.push(prop.getText(origSourceFile));
826
+ continue;
827
+ }
828
+ const propName = prop.name?.getText(origSourceFile);
829
+ if (propName === 'templateUrl' && resources.template) {
830
+ rewrittenProps.push(`template: ${JSON.stringify(resources.template)}`);
831
+ needsTransform = true;
832
+ }
833
+ else if ((propName === 'styleUrl' || propName === 'styleUrls') &&
834
+ resources.styles?.length) {
835
+ rewrittenProps.push(`styles: [${resources.styles.map((s) => JSON.stringify(s)).join(', ')}]`);
836
+ needsTransform = true;
837
+ }
838
+ else {
839
+ rewrittenProps.push(prop.getText(origSourceFile));
840
+ }
841
+ }
842
+ metadataArgsExpr = needsTransform
843
+ ? `{${rewrittenProps.join(', ')}}`
844
+ : decArgsNode.getText(origSourceFile);
845
+ }
846
+ else {
847
+ metadataArgsExpr = decArgsNode.getText(origSourceFile);
848
+ }
849
+ }
850
+ try {
851
+ // `LiteralMapPropertyAssignment` was a class with a constructor in
852
+ // Angular up through ~v19, removed-then-restored in later v20/v21
853
+ // patches. The only stable shape across versions is the plain
854
+ // `{ key, value, quoted }` object literal — Angular's own emitter
855
+ // and Analog's `JSEmitter` both consume entries via duck typing
856
+ // (`.key` / `.value` / `.quoted`), and `compileClassMetadata` does
857
+ // not `instanceof`-check entries. The js-emitter has a matching
858
+ // comment at the consumption site. Using `new o.LiteralMapProperty
859
+ // Assignment(...)` would throw "is not a constructor" on Angular
860
+ // versions where the class export is missing (e.g. 20.3.x), and
861
+ // the surrounding try/catch would silently disable class metadata
862
+ // emission for every class in the project.
863
+ const classMetaInput = {
864
+ type: new o.WrappedNodeExpr(ts.factory.createIdentifier(className)),
865
+ decorators: new o.LiteralArrayExpr([
866
+ new o.LiteralMapExpr([
867
+ {
868
+ key: 'type',
869
+ value: new o.WrappedNodeExpr(decName),
870
+ quoted: false,
871
+ },
872
+ ...(metadataArgsExpr
873
+ ? [
874
+ {
875
+ key: 'args',
876
+ value: new o.LiteralArrayExpr([
877
+ new o.WrappedNodeExpr(metadataArgsExpr),
878
+ ]),
879
+ quoted: false,
880
+ },
881
+ ]
882
+ : []),
883
+ ]),
884
+ ]),
885
+ ctorParameters: null,
886
+ propDecorators: null,
887
+ };
888
+ const classMetadataExpr = isPartial
889
+ ? compileDeclareClassMetadata(classMetaInput)
890
+ : compileClassMetadata(classMetaInput);
891
+ constantPool.statements.push(new o.ExpressionStatement(classMetadataExpr));
892
+ }
893
+ catch (e) {
894
+ // Skip if compileClassMetadata fails — surfaced via DEBUG=analog-fast-compile
895
+ if (debugCompile.enabled) {
896
+ debugCompile('compileClassMetadata failed for %s in %s: %s', className, fileName, e?.message);
897
+ }
898
+ }
899
+ });
900
+ // Collect member decorators (@HostListener, @HostBinding, @Input, @Output,
901
+ // @ViewChild, @ContentChild, etc.) so they are removed from the source.
902
+ // The metadata has already been extracted by detectFieldDecorators().
903
+ const memberDecorators = [];
904
+ for (const member of node.members) {
905
+ const mDecorators = ts.getDecorators(member);
906
+ if (!mDecorators)
907
+ continue;
908
+ for (const dec of mDecorators) {
909
+ if (!ts.isCallExpression(dec.expression))
910
+ continue;
911
+ const decName = dec.expression.expression.getText(origSourceFile);
912
+ if (FIELD_DECORATORS.has(decName)) {
913
+ memberDecorators.push(dec);
914
+ }
915
+ }
916
+ }
917
+ classResults.push({
918
+ ivyCode,
919
+ decorators: [...angularDecorators, ...memberDecorators],
920
+ classEnd: node.getEnd(),
921
+ });
922
+ }
923
+ // Apply edits via MagicString
924
+ const ms = new MagicString(sourceCode, { filename: fileName });
925
+ // 1. Prepend i0 import (skip if already present)
926
+ if (!sourceCode.includes('import * as i0 from')) {
927
+ ms.prepend('import * as i0 from "@angular/core";\n');
928
+ }
929
+ // 1b. Inject synthetic imports for NgModule-exported classes
930
+ for (const [name, specifier] of syntheticImports) {
931
+ if (!importedNames.has(name)) {
932
+ ms.prepend(`import { ${name} } from "${specifier}";\n`);
933
+ importedNames.add(name);
934
+ }
935
+ }
936
+ // 2a. Hoist non-exported variable/function declarations that appear after
937
+ // a class to just before the first class. This avoids TDZ errors when
938
+ // static ɵcmp references file-level constants declared after the class.
939
+ //
940
+ // We must NOT hoist a statement whose initializer references a name
941
+ // (class, exported const/let) that is defined between firstClassPos and
942
+ // the statement itself — doing so would move the reference before the
943
+ // definition and cause a TDZ error at runtime.
944
+ if (classResults.length > 0) {
945
+ // Find the position right before the first class (any class, not just Angular-decorated)
946
+ let firstClassPos = Infinity;
947
+ const nonHoistableNames = new Set();
948
+ for (const stmt of origSourceFile.statements) {
949
+ if (ts.isClassDeclaration(stmt)) {
950
+ firstClassPos = stmt.getStart(origSourceFile);
951
+ // The first class itself is non-hoistable — statements hoisted
952
+ // before it must not reference it.
953
+ if (stmt.name) {
954
+ nonHoistableNames.add(stmt.name.text);
955
+ }
956
+ break;
957
+ }
958
+ }
959
+ // Pass 1: collect names defined by statements that will NOT be hoisted
960
+ // (classes, exported vars/functions) and therefore remain after firstClassPos.
961
+ for (const stmt of origSourceFile.statements) {
962
+ const stmtStart = stmt.getStart(origSourceFile);
963
+ if (stmtStart <= firstClassPos)
964
+ continue;
965
+ if (ts.isClassDeclaration(stmt) && stmt.name) {
966
+ nonHoistableNames.add(stmt.name.text);
967
+ }
968
+ if (ts.isEnumDeclaration(stmt)) {
969
+ nonHoistableNames.add(stmt.name.text);
970
+ }
971
+ if (ts.isVariableStatement(stmt) && hasExportModifier(stmt)) {
972
+ for (const decl of stmt.declarationList.declarations) {
973
+ const names = [];
974
+ collectBindingNames(decl.name, names);
975
+ for (const n of names) {
976
+ nonHoistableNames.add(n);
977
+ }
978
+ }
979
+ }
980
+ if (ts.isFunctionDeclaration(stmt) &&
981
+ hasExportModifier(stmt) &&
982
+ stmt.name) {
983
+ nonHoistableNames.add(stmt.name.text);
984
+ }
985
+ }
986
+ // Pass 2: for each hoist candidate, check if it references any
987
+ // non-hoistable name. If it does, skip it and add its own names to the
988
+ // non-hoistable set (transitivity).
989
+ for (const stmt of origSourceFile.statements) {
990
+ const stmtStart = stmt.getStart(origSourceFile);
991
+ if (stmtStart <= firstClassPos)
992
+ continue;
993
+ const isCandidate = (ts.isVariableStatement(stmt) && !hasExportModifier(stmt)) ||
994
+ (ts.isFunctionDeclaration(stmt) && !hasExportModifier(stmt));
995
+ if (!isCandidate)
996
+ continue;
997
+ const referencedIds = collectIdentifiers(stmt);
998
+ let referencesNonHoistable = false;
999
+ for (const id of referencedIds) {
1000
+ if (nonHoistableNames.has(id)) {
1001
+ referencesNonHoistable = true;
1002
+ break;
1003
+ }
1004
+ }
1005
+ if (referencesNonHoistable) {
1006
+ // Don't hoist — mark this statement's names as non-hoistable too
1007
+ for (const name of getDefinedNames(stmt)) {
1008
+ nonHoistableNames.add(name);
1009
+ }
1010
+ }
1011
+ else {
1012
+ const text = stmt.getText(origSourceFile);
1013
+ ms.remove(stmtStart, stmt.getEnd());
1014
+ ms.appendLeft(firstClassPos, text + '\n');
1015
+ }
1016
+ }
1017
+ }
1018
+ // 2. For each compiled class: remove decorators + insert Ivy definitions
1019
+ for (const cr of classResults) {
1020
+ // Remove Angular decorators from source
1021
+ for (const dec of cr.decorators) {
1022
+ const start = dec.getStart(origSourceFile);
1023
+ const end = dec.getEnd();
1024
+ let trimEnd = end;
1025
+ while (trimEnd < sourceCode.length &&
1026
+ (sourceCode[trimEnd] === ' ' ||
1027
+ sourceCode[trimEnd] === '\n' ||
1028
+ sourceCode[trimEnd] === '\r')) {
1029
+ trimEnd++;
1030
+ }
1031
+ ms.remove(start, trimEnd);
1032
+ }
1033
+ // Insert static members before closing }
1034
+ if (cr.ivyCode.length > 0) {
1035
+ const memberCode = cr.ivyCode.map((c) => ' ' + c + ';').join('\n');
1036
+ ms.appendLeft(cr.classEnd - 1, '\n' + memberCode + '\n');
1037
+ }
1038
+ }
1039
+ // 3. Emit constant pool statements.
1040
+ // Split into: helper declarations (const/function) that must appear before
1041
+ // the class (since static property initializers reference them), and
1042
+ // side-effect statements (setClassMetadata IIFEs) that go after.
1043
+ const helpers = [];
1044
+ const sideEffects = [];
1045
+ // Per-class hoisted helpers (nested template functions) come first so they
1046
+ // are available when subsequent constant-pool helpers reference them.
1047
+ helpers.push(...hoistedHelpers);
1048
+ for (const s of constantPool.statements) {
1049
+ const code = emitAngularStmt(s);
1050
+ if (s instanceof o.ExpressionStatement &&
1051
+ s.expr instanceof o.InvokeFunctionExpr) {
1052
+ // setClassMetadata is wrapped in `(() => { ... })()` — annotate
1053
+ // the IIFE call so bundlers can drop it when ngDevMode is folded
1054
+ // to false at production build time.
1055
+ sideEffects.push(`/*@__PURE__*/ ${code}`);
1056
+ }
1057
+ else {
1058
+ helpers.push(code);
1059
+ }
1060
+ }
1061
+ if (helpers.length > 0) {
1062
+ // Insert helpers after the last import / top-level non-class statement,
1063
+ // well before any class that references them.
1064
+ // Detect which imported names are type-only so we skip imports that will
1065
+ // be fully removed by elideTypeOnlyImportsMagicString (step 4).
1066
+ // Inserting at a position inside a soon-to-be-removed range would cause
1067
+ // MagicString to discard the helpers along with the import.
1068
+ const willElide = detectTypeOnlyImportNames(ms.toString());
1069
+ let insertPos = 0;
1070
+ for (const stmt of origSourceFile.statements) {
1071
+ if (ts.isImportDeclaration(stmt)) {
1072
+ // Skip imports that will be entirely removed by type elision
1073
+ if (willElide.size > 0 && stmt.importClause) {
1074
+ const clause = stmt.importClause;
1075
+ const namedBindings = clause.namedBindings && ts.isNamedImports(clause.namedBindings)
1076
+ ? clause.namedBindings.elements
1077
+ : undefined;
1078
+ const defaultName = clause.name;
1079
+ const allElided = (!defaultName || willElide.has(defaultName.text)) &&
1080
+ (!namedBindings ||
1081
+ namedBindings.every((el) => el.isTypeOnly || willElide.has(el.name.text)));
1082
+ if (allElided)
1083
+ continue;
1084
+ }
1085
+ insertPos = stmt.getEnd();
1086
+ }
1087
+ else if (ts.isVariableStatement(stmt) &&
1088
+ !stmt.getText(origSourceFile).includes('class')) {
1089
+ insertPos = stmt.getEnd();
1090
+ }
1091
+ else if (!ts.isExportAssignment(stmt)) {
1092
+ // Stop at the first class or non-import/non-variable statement
1093
+ break;
1094
+ }
1095
+ }
1096
+ // Helpers must survive downstream `ms.overwrite()` / `ms.remove()` of
1097
+ // imports performed by the type-only specifier elision pass (step 5).
1098
+ //
1099
+ // Case `insertPos > 0`: a surviving import ends at `insertPos`. Use
1100
+ // `appendRight(insertPos, …)` so helpers bind to the `\n` after the
1101
+ // import's `;` — outside any overwrite range `[node.start, node.end)`
1102
+ // of a preceding import. `appendLeft(insertPos, …)` would bind to the
1103
+ // `;` itself, which IS inside the overwrite range and would be wiped.
1104
+ //
1105
+ // Case `insertPos === 0`: no import survives — every import is going
1106
+ // to be elided, and `elideTypeOnlyImportsMagicString` will
1107
+ // `ms.remove(0, declEnd)` the first one. `appendRight(0, …)` anchors
1108
+ // to the character at position 0 (the first import's first char),
1109
+ // which is inside `[0, declEnd)` and gets wiped. Use `appendLeft(0, …)`
1110
+ // instead — it anchors to the LEFT of position 0, outside any range
1111
+ // starting at 0, so the helpers survive. Ordering is still correct
1112
+ // because the i0 import prepended in step 1 lives in MagicString's
1113
+ // `intro` (from `ms.prepend`), which always emits before any
1114
+ // `appendLeft` content at position 0.
1115
+ if (insertPos === 0) {
1116
+ ms.appendLeft(0, helpers.join('\n') + '\n');
1117
+ }
1118
+ else {
1119
+ ms.appendRight(insertPos, '\n' + helpers.join('\n') + '\n');
1120
+ }
1121
+ }
1122
+ if (sideEffects.length > 0) {
1123
+ ms.append('\n\n' + sideEffects.join('\n'));
1124
+ }
1125
+ // 4. Lower class field initializers to constructor assignments when
1126
+ // useDefineForClassFields is false (standard Angular tsconfig).
1127
+ // Uses the original OXC AST positions which are still valid for MagicString
1128
+ // since MagicString tracks edits relative to the original source.
1129
+ // Ivy static definitions (ɵcmp, ɵfac) are not in the original AST so they
1130
+ // are unaffected, and shouldLowerField skips static fields regardless.
1131
+ if (!useDefineForClassFields) {
1132
+ lowerClassFields(ms, sourceCode, oxcProgram);
1133
+ }
1134
+ // 5. Elide imports that are only used in type positions (type annotations,
1135
+ // implements, generics, etc.). Without this pass, single-file transpilers
1136
+ // like OXC / esbuild cannot tell that `import { SomeType }` is type-only
1137
+ // and will leave the import in the output, causing runtime errors.
1138
+ elideTypeOnlyImportsMagicString(ms);
1139
+ const map = ms.generateMap({
1140
+ source: fileName,
1141
+ file: fileName + '.js',
1142
+ includeContent: true,
1143
+ hires: 'boundary',
1144
+ });
1145
+ if (debugCompile.enabled) {
1146
+ debugCompile('compile %s done (%dms, %d classes, %d resource deps)', fileName, Math.round(performance.now() - startTimeMs), classResults.length, resourceDependencies.length);
1147
+ }
1148
+ return {
1149
+ code: ms.toString(),
1150
+ map,
1151
+ resourceDependencies,
1152
+ };
1153
+ }
1154
+ function injectAngularImport(sf) {
1155
+ return ts.factory.updateSourceFile(sf, [
1156
+ ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamespaceImport(ts.factory.createIdentifier('i0'))), ts.factory.createStringLiteral('@angular/core')),
1157
+ ...sf.statements,
1158
+ ]);
1159
+ }
1160
+ //# sourceMappingURL=compile.js.map