@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
@@ -0,0 +1,630 @@
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 { ConstantPool, compileComponentFromMetadata, compileDirectiveFromMetadata, compilePipeFromMetadata, compileNgModule, compileInjector, R3NgModuleMetadataKind, R3SelectorScopeMode, FactoryTarget, compileFactoryFunction, parseTemplate, makeBindingParser, parseHostBindings, ParseSourceFile, ParseLocation, ParseSourceSpan, compileClassMetadata, } from '@angular/compiler';
7
+ import { collectTypeOnlyImports, findAllClasses, ANGULAR_DECORATORS, } from './utils.js';
8
+ import { emitAngularExpr, emitAngularStmt, setEmitterSourceFile, } from './js-emitter.js';
9
+ import { extractMetadata, detectSignals, detectFieldDecorators, extractConstructorDeps, } from './metadata.js';
10
+ import { buildDeferDependencyMap } from './defer.js';
11
+ /** Detect installed Angular major version for compatibility. Supports 19+. */
12
+ const ANGULAR_MAJOR = (() => {
13
+ const major = Number.parseInt(o.VERSION?.major ?? '', 10);
14
+ return Number.isFinite(major) ? major : 21;
15
+ })();
16
+ export function compile(sourceCode, fileName, optionsOrRegistry) {
17
+ // Backward compat: accept ComponentRegistry directly
18
+ const opts = optionsOrRegistry instanceof Map
19
+ ? { registry: optionsOrRegistry }
20
+ : optionsOrRegistry || {};
21
+ const registry = opts.registry;
22
+ const resolvedStyles = opts.resolvedStyles;
23
+ const resolvedInlineStyles = opts.resolvedInlineStyles;
24
+ const origSourceFile = ts.createSourceFile(fileName, sourceCode, ts.ScriptTarget.Latest, true);
25
+ const constantPool = new ConstantPool();
26
+ const resourceDependencies = [];
27
+ const parseFile = new ParseSourceFile(sourceCode, fileName);
28
+ const parseLoc = new ParseLocation(parseFile, 0, 0, 0);
29
+ const typeSourceSpan = new ParseSourceSpan(parseLoc, parseLoc);
30
+ const typeOnlyImports = collectTypeOnlyImports(origSourceFile);
31
+ const importSpecifierByName = new Map();
32
+ const importedNames = new Set();
33
+ for (const stmt of origSourceFile.statements) {
34
+ if (!ts.isImportDeclaration(stmt) ||
35
+ !stmt.importClause?.namedBindings ||
36
+ !ts.isNamedImports(stmt.importClause.namedBindings)) {
37
+ continue;
38
+ }
39
+ const moduleSpecifier = stmt.moduleSpecifier.text;
40
+ for (const element of stmt.importClause.namedBindings.elements) {
41
+ const localName = element.name.text;
42
+ importedNames.add(localName);
43
+ importSpecifierByName.set(localName, moduleSpecifier);
44
+ }
45
+ }
46
+ // Inject 'import * as i0 from "@angular/core"'
47
+ const sourceFile = injectAngularImport(origSourceFile);
48
+ // Build a file-local selector map as fallback when no external registry is provided.
49
+ // Skip the expensive extractMetadata scan when a registry covers all classes.
50
+ const localSelectors = new Map();
51
+ if (!registry) {
52
+ sourceFile.statements.forEach((stmt) => {
53
+ if (ts.isClassDeclaration(stmt) && stmt.name) {
54
+ const meta = extractMetadata(ts.getDecorators(stmt)?.[0]);
55
+ if (meta?.selector) {
56
+ localSelectors.set(stmt.name.text, meta.selector.split(',')[0].trim());
57
+ }
58
+ }
59
+ });
60
+ }
61
+ const bindingParser = makeBindingParser();
62
+ setEmitterSourceFile(origSourceFile);
63
+ const classResults = [];
64
+ // Track synthetic imports needed for NgModule export expansion.
65
+ // Maps exported class name → import specifier.
66
+ const syntheticImports = new Map();
67
+ // ANGULAR_DECORATORS imported from utils
68
+ for (const node of findAllClasses(origSourceFile)) {
69
+ const decorators = ts.getDecorators(node);
70
+ if (!decorators || decorators.length === 0)
71
+ continue;
72
+ const className = node.name?.text;
73
+ if (!className)
74
+ continue;
75
+ const angularDecorators = decorators.filter((dec) => {
76
+ if (!ts.isCallExpression(dec.expression))
77
+ return false;
78
+ const name = dec.expression.expression.getText(origSourceFile);
79
+ return ANGULAR_DECORATORS.has(name);
80
+ });
81
+ if (angularDecorators.length === 0)
82
+ continue;
83
+ const ivyCode = [];
84
+ let targetType = FactoryTarget.Injectable;
85
+ // Store resolved resources per decorator for metadata inlining
86
+ const resolvedResources = new Map();
87
+ const classIdentifier = ts.factory.createIdentifier(className);
88
+ const classRef = {
89
+ value: new o.WrappedNodeExpr(classIdentifier),
90
+ type: new o.WrappedNodeExpr(classIdentifier),
91
+ };
92
+ let classCompileError = null;
93
+ angularDecorators.forEach((dec) => {
94
+ const decoratorName = dec.expression.expression.getText(origSourceFile);
95
+ const meta = extractMetadata(dec);
96
+ const sigs = detectSignals(node);
97
+ const fields = detectFieldDecorators(node);
98
+ const hostBindings = parseHostBindings(meta.hostRaw || {});
99
+ const hostMetadata = {
100
+ attributes: hostBindings.attributes,
101
+ listeners: { ...hostBindings.listeners, ...fields.hostListeners },
102
+ properties: { ...hostBindings.properties, ...fields.hostProperties },
103
+ specialAttributes: hostBindings.specialAttributes,
104
+ };
105
+ switch (decoratorName) {
106
+ case 'Component':
107
+ targetType = FactoryTarget.Component;
108
+ if (!meta.selector) {
109
+ meta.selector = `ng-component-${className.toLowerCase()}`;
110
+ }
111
+ const declarations = [];
112
+ for (const dep of Array.isArray(meta.imports) ? meta.imports : []) {
113
+ const depClassName = dep.node.getText();
114
+ const registryEntry = registry?.get(depClassName);
115
+ if (registryEntry?.kind === 'ngmodule' && registryEntry.exports) {
116
+ const moduleSpecifier = importSpecifierByName.get(depClassName);
117
+ // Recursively collect all non-module exports (handles nested NgModules
118
+ // like ReactiveFormsModule → ɵInternalFormsSharedModule → DefaultValueAccessor)
119
+ const allExports = [];
120
+ const expandModule = (moduleName, visited = new Set()) => {
121
+ if (visited.has(moduleName))
122
+ return;
123
+ visited.add(moduleName);
124
+ const mod = registry?.get(moduleName);
125
+ if (!mod?.exports)
126
+ return;
127
+ for (const name of mod.exports) {
128
+ const entry = registry?.get(name);
129
+ if (!entry)
130
+ continue;
131
+ if (entry.kind === 'ngmodule') {
132
+ expandModule(name, visited);
133
+ }
134
+ else {
135
+ allExports.push(name);
136
+ }
137
+ }
138
+ };
139
+ expandModule(depClassName);
140
+ for (const exportedName of allExports) {
141
+ const exportedEntry = registry?.get(exportedName);
142
+ if (exportedEntry) {
143
+ const kind = exportedEntry.kind === 'pipe' ? 1 : 0;
144
+ // Create a reference to the exported class. If it's not
145
+ // already imported, track it for synthetic import injection.
146
+ const exportedId = ts.factory.createIdentifier(exportedName);
147
+ const exportedRef = new o.WrappedNodeExpr(exportedId);
148
+ if (moduleSpecifier) {
149
+ syntheticImports.set(exportedName, moduleSpecifier);
150
+ }
151
+ const decl = {
152
+ type: exportedRef,
153
+ selector: exportedEntry.selector,
154
+ kind,
155
+ ...(kind === 1 ? { name: exportedEntry.pipeName } : {}),
156
+ };
157
+ if (exportedEntry.inputs) {
158
+ decl.inputs = Object.values(exportedEntry.inputs).map((i) => i.bindingPropertyName);
159
+ }
160
+ if (exportedEntry.outputs) {
161
+ decl.outputs = Object.values(exportedEntry.outputs);
162
+ }
163
+ declarations.push(decl);
164
+ }
165
+ }
166
+ continue;
167
+ }
168
+ const selector = registryEntry?.selector ?? localSelectors.get(depClassName);
169
+ const kind = registryEntry?.kind === 'pipe' ? 1 : 0;
170
+ const decl = {
171
+ type: dep,
172
+ selector: selector || `_unresolved-${depClassName}`,
173
+ kind,
174
+ ...(kind === 1 ? { name: registryEntry?.pipeName } : {}),
175
+ };
176
+ // Pass inputs/outputs from registry so template bindings resolve.
177
+ // R3DirectiveDependencyMetadata expects inputs/outputs as string[]
178
+ // of binding property names.
179
+ if (registryEntry?.inputs) {
180
+ decl.inputs = Object.values(registryEntry.inputs).map((i) => i.bindingPropertyName);
181
+ }
182
+ if (registryEntry?.outputs) {
183
+ decl.outputs = Object.values(registryEntry.outputs);
184
+ }
185
+ declarations.push(decl);
186
+ }
187
+ // Add self-reference so recursive components (e.g. tree views)
188
+ // can use their own selector in their template.
189
+ const selfInputs = Object.entries(sigs.inputs).map(([, v]) => v.bindingPropertyName);
190
+ const selfOutputs = Object.values({
191
+ ...meta.outputs,
192
+ ...fields.outputs,
193
+ ...sigs.outputs,
194
+ });
195
+ declarations.push({
196
+ type: classRef.value,
197
+ selector: meta.selector,
198
+ kind: 0,
199
+ ...(selfInputs.length > 0 ? { inputs: selfInputs } : {}),
200
+ ...(selfOutputs.length > 0 ? { outputs: selfOutputs } : {}),
201
+ });
202
+ let templateContent = meta.template || '';
203
+ if (!templateContent && meta.templateUrl) {
204
+ try {
205
+ const templatePath = path.resolve(path.dirname(fileName), meta.templateUrl);
206
+ templateContent = fs.readFileSync(templatePath, 'utf-8');
207
+ resourceDependencies.push(templatePath);
208
+ }
209
+ catch {
210
+ console.warn(`[angular-compiler] Could not read template file "${meta.templateUrl}" for ${className}`);
211
+ }
212
+ }
213
+ if (Array.isArray(meta.styleUrls)) {
214
+ for (const url of meta.styleUrls) {
215
+ try {
216
+ const stylePath = path.resolve(path.dirname(fileName), url);
217
+ const styleContent = resolvedStyles?.get(stylePath) ??
218
+ fs.readFileSync(stylePath, 'utf-8');
219
+ meta.styles.push(styleContent);
220
+ resourceDependencies.push(stylePath);
221
+ }
222
+ catch {
223
+ console.warn(`[angular-compiler] Could not read style file "${url}" for ${className}`);
224
+ }
225
+ }
226
+ }
227
+ if (resolvedInlineStyles) {
228
+ for (const [idx, css] of resolvedInlineStyles) {
229
+ if (idx < meta.styles.length) {
230
+ meta.styles[idx] = css;
231
+ }
232
+ }
233
+ }
234
+ // Store resolved resources for metadata inlining
235
+ resolvedResources.set(dec, {
236
+ template: templateContent || undefined,
237
+ styles: meta.styles?.length > 0 ? [...meta.styles] : undefined,
238
+ });
239
+ const parsedTemplate = parseTemplate(templateContent, fileName, {
240
+ preserveWhitespaces: meta.preserveWhitespaces,
241
+ });
242
+ const ivyInputs = {};
243
+ if (Array.isArray(meta.inputs)) {
244
+ meta.inputs.forEach((i) => (ivyInputs[i] = i));
245
+ }
246
+ else if (meta.inputs) {
247
+ Object.assign(ivyInputs, meta.inputs);
248
+ }
249
+ Object.assign(ivyInputs, fields.inputs);
250
+ for (const [key, val] of Object.entries(sigs.inputs)) {
251
+ const sigDesc = val;
252
+ ivyInputs[key] = {
253
+ classPropertyName: key,
254
+ bindingPropertyName: key,
255
+ isSignal: true,
256
+ required: sigDesc.required || false,
257
+ transformFunction: sigDesc.transform || null,
258
+ };
259
+ }
260
+ const templateErrors = parsedTemplate.errors ?? [];
261
+ if (templateErrors.length > 0) {
262
+ const firstError = templateErrors[0];
263
+ classCompileError = new Error(`[angular-compiler] Template parse error in ${fileName} (${className}): ${firstError.msg}`);
264
+ return;
265
+ }
266
+ const componentMeta = {
267
+ ...meta,
268
+ name: className,
269
+ type: classRef,
270
+ typeSourceSpan,
271
+ declarations,
272
+ template: {
273
+ nodes: parsedTemplate.nodes,
274
+ ngContentSelectors: parsedTemplate.ngContentSelectors,
275
+ preserveWhitespaces: parsedTemplate.preserveWhitespaces,
276
+ },
277
+ styles: [...(meta.styles || []), ...(parsedTemplate.styles || [])],
278
+ inputs: ivyInputs,
279
+ outputs: { ...meta.outputs, ...fields.outputs, ...sigs.outputs },
280
+ viewQueries: [...fields.viewQueries, ...sigs.viewQueries],
281
+ queries: [...fields.contentQueries, ...sigs.contentQueries],
282
+ host: hostMetadata,
283
+ changeDetection: meta.changeDetection,
284
+ encapsulation: meta.encapsulation,
285
+ exportAs: meta.exportAs,
286
+ providers: meta.providers?.length
287
+ ? new o.LiteralArrayExpr(meta.providers)
288
+ : null,
289
+ viewProviders: meta.viewProviders?.length
290
+ ? new o.LiteralArrayExpr(meta.viewProviders)
291
+ : null,
292
+ animations: meta.animations?.length
293
+ ? new o.LiteralArrayExpr(meta.animations)
294
+ : null,
295
+ isStandalone: meta.standalone,
296
+ imports: meta.imports,
297
+ lifecycle: { usesOnChanges: false },
298
+ defer: {
299
+ mode: 0,
300
+ blocks: buildDeferDependencyMap(parsedTemplate, sourceFile, registry, localSelectors).blocks,
301
+ },
302
+ declarationListEmitMode: 1,
303
+ relativeContextFilePath: fileName,
304
+ controlCreate: null,
305
+ };
306
+ if (ANGULAR_MAJOR >= 20) {
307
+ componentMeta.hasDirectiveDependencies = declarations.length > 0;
308
+ }
309
+ const cmp = compileComponentFromMetadata(componentMeta, constantPool, bindingParser);
310
+ ivyCode.push(`static ɵcmp = ${emitAngularExpr(cmp.expression)}`);
311
+ break;
312
+ case 'Directive':
313
+ targetType = FactoryTarget.Directive;
314
+ // Build proper input descriptors (same as Component path)
315
+ const dirInputs = {};
316
+ if (Array.isArray(meta.inputs)) {
317
+ meta.inputs.forEach((i) => (dirInputs[i] = i));
318
+ }
319
+ else if (meta.inputs) {
320
+ Object.assign(dirInputs, meta.inputs);
321
+ }
322
+ Object.assign(dirInputs, fields.inputs);
323
+ for (const [key, val] of Object.entries(sigs.inputs)) {
324
+ const sigDesc = val;
325
+ dirInputs[key] = {
326
+ classPropertyName: key,
327
+ bindingPropertyName: key,
328
+ isSignal: true,
329
+ required: sigDesc.required || false,
330
+ transformFunction: sigDesc.transform || null,
331
+ };
332
+ }
333
+ const dir = compileDirectiveFromMetadata({
334
+ ...meta,
335
+ name: className,
336
+ type: classRef,
337
+ typeSourceSpan,
338
+ host: hostMetadata,
339
+ inputs: dirInputs,
340
+ outputs: { ...meta.outputs, ...fields.outputs, ...sigs.outputs },
341
+ viewQueries: [...fields.viewQueries, ...sigs.viewQueries],
342
+ queries: [...fields.contentQueries, ...sigs.contentQueries],
343
+ providers: meta.providers,
344
+ exportAs: meta.exportAs,
345
+ isStandalone: meta.standalone,
346
+ lifecycle: { usesOnChanges: false },
347
+ controlCreate: null,
348
+ }, constantPool, bindingParser);
349
+ ivyCode.push(`static ɵdir = ${emitAngularExpr(dir.expression)}`);
350
+ break;
351
+ case 'Pipe':
352
+ targetType = FactoryTarget.Pipe;
353
+ const pipe = compilePipeFromMetadata({
354
+ ...meta,
355
+ name: className,
356
+ pipeName: meta.name,
357
+ type: classRef,
358
+ isStandalone: meta.standalone,
359
+ pure: meta.pure ?? true,
360
+ });
361
+ ivyCode.push(`static ɵpipe = ${emitAngularExpr(pipe.expression)}`);
362
+ break;
363
+ case 'Injectable':
364
+ targetType = FactoryTarget.Injectable;
365
+ const inj = o.compileInjectable({
366
+ name: className,
367
+ type: classRef,
368
+ typeArgumentCount: 0,
369
+ providedIn: {
370
+ expression: new o.LiteralExpr(meta.providedIn || 'root'),
371
+ forwardRef: 0,
372
+ },
373
+ }, true);
374
+ ivyCode.push(`static ɵprov = ${emitAngularExpr(inj.expression)}`);
375
+ break;
376
+ case 'NgModule':
377
+ targetType = FactoryTarget.NgModule;
378
+ const ngModuleImports = Array.isArray(meta.imports)
379
+ ? meta.imports
380
+ : [];
381
+ const ngModuleDeclarations = Array.isArray(meta.declarations)
382
+ ? meta.declarations
383
+ : [];
384
+ const ngModuleExports = Array.isArray(meta.exports)
385
+ ? meta.exports
386
+ : [];
387
+ const ngModuleBootstrap = Array.isArray(meta.bootstrap)
388
+ ? meta.bootstrap
389
+ : [];
390
+ const ngMod = compileNgModule({
391
+ kind: R3NgModuleMetadataKind.Global,
392
+ type: classRef,
393
+ bootstrap: ngModuleBootstrap.map((e) => ({
394
+ value: e,
395
+ type: e,
396
+ })),
397
+ declarations: ngModuleDeclarations.map((e) => ({ value: e, type: e })),
398
+ publicDeclarationTypes: null,
399
+ imports: ngModuleImports.map((e) => ({
400
+ value: e,
401
+ type: e,
402
+ })),
403
+ includeImportTypes: true,
404
+ exports: ngModuleExports.map((e) => ({
405
+ value: e,
406
+ type: e,
407
+ })),
408
+ selectorScopeMode: R3SelectorScopeMode.Inline,
409
+ containsForwardDecls: false,
410
+ schemas: [],
411
+ id: null,
412
+ });
413
+ ivyCode.push(`static ɵmod = ${emitAngularExpr(ngMod.expression)}`);
414
+ const injector = compileInjector({
415
+ name: className,
416
+ type: classRef,
417
+ providers: meta.providers
418
+ ? new o.LiteralArrayExpr(meta.providers)
419
+ : null,
420
+ imports: ngModuleImports.map((e) => e),
421
+ });
422
+ ivyCode.push(`static ɵinj = ${emitAngularExpr(injector.expression)}`);
423
+ break;
424
+ }
425
+ });
426
+ if (classCompileError) {
427
+ throw classCompileError;
428
+ }
429
+ // Generate factory
430
+ const deps = extractConstructorDeps(node, typeOnlyImports);
431
+ if (deps === null) {
432
+ const baseVar = `ɵ${className}_BaseFactory`;
433
+ ivyCode.unshift(`static ɵfac = /*@__PURE__*/ (() => { let ${baseVar}; return function ${className}_Factory(__ngFactoryType__) { return (${baseVar} || (${baseVar} = i0.ɵɵgetInheritedFactory(${className})))(__ngFactoryType__ || ${className}); }; })()`);
434
+ }
435
+ else if (deps === 'invalid') {
436
+ ivyCode.unshift(`static ɵfac = function ${className}_Factory(__ngFactoryType__) { i0.ɵɵinvalidFactory(); }`);
437
+ }
438
+ else {
439
+ const fac = compileFactoryFunction({
440
+ name: className,
441
+ type: classRef,
442
+ typeArgumentCount: 0,
443
+ deps,
444
+ target: targetType,
445
+ });
446
+ ivyCode.unshift(`static ɵfac = ${emitAngularExpr(fac.expression)}`);
447
+ }
448
+ // Emit setClassMetadata for runtime decorator reflection (devMode only)
449
+ angularDecorators.forEach((dec) => {
450
+ const call = dec.expression;
451
+ const decName = call.expression.getText(origSourceFile);
452
+ let decArgsNode = call.arguments[0];
453
+ // Inline external templateUrl/styleUrl(s) into the metadata so Angular's
454
+ // runtime doesn't try to fetch relative URLs (which fails during SSR).
455
+ const resources = resolvedResources.get(dec);
456
+ if (decArgsNode &&
457
+ ts.isObjectLiteralExpression(decArgsNode) &&
458
+ resources) {
459
+ let needsTransform = false;
460
+ const newProperties = [];
461
+ for (const prop of decArgsNode
462
+ .properties) {
463
+ if (!ts.isPropertyAssignment(prop)) {
464
+ newProperties.push(prop);
465
+ continue;
466
+ }
467
+ const propName = prop.name?.getText(origSourceFile);
468
+ if (propName === 'templateUrl' && resources.template) {
469
+ newProperties.push(ts.factory.createPropertyAssignment('template', ts.factory.createStringLiteral(resources.template)));
470
+ needsTransform = true;
471
+ }
472
+ else if ((propName === 'styleUrl' || propName === 'styleUrls') &&
473
+ resources.styles?.length) {
474
+ newProperties.push(ts.factory.createPropertyAssignment('styles', ts.factory.createArrayLiteralExpression(resources.styles.map((s) => ts.factory.createStringLiteral(s)))));
475
+ needsTransform = true;
476
+ }
477
+ else {
478
+ newProperties.push(prop);
479
+ }
480
+ }
481
+ if (needsTransform) {
482
+ decArgsNode = ts.factory.createObjectLiteralExpression(newProperties, true);
483
+ }
484
+ }
485
+ try {
486
+ const classMetadataExpr = compileClassMetadata({
487
+ type: new o.WrappedNodeExpr(ts.factory.createIdentifier(className)),
488
+ decorators: new o.LiteralArrayExpr([
489
+ new o.LiteralMapExpr([
490
+ new o.LiteralMapPropertyAssignment('type', new o.WrappedNodeExpr(ts.factory.createIdentifier(decName)), false),
491
+ ...(decArgsNode
492
+ ? [
493
+ new o.LiteralMapPropertyAssignment('args', new o.LiteralArrayExpr([
494
+ new o.WrappedNodeExpr(decArgsNode),
495
+ ]), false),
496
+ ]
497
+ : []),
498
+ ]),
499
+ ]),
500
+ ctorParameters: null,
501
+ propDecorators: null,
502
+ });
503
+ constantPool.statements.push(new o.ExpressionStatement(classMetadataExpr));
504
+ }
505
+ catch {
506
+ // Skip if compileClassMetadata fails
507
+ }
508
+ });
509
+ // Collect member decorators (@HostListener, @HostBinding, @Input, @Output,
510
+ // @ViewChild, @ContentChild, etc.) so they are removed from the source.
511
+ // The metadata has already been extracted by detectFieldDecorators().
512
+ const memberDecorators = [];
513
+ const MEMBER_DECORATOR_NAMES = new Set([
514
+ 'Input',
515
+ 'Output',
516
+ 'HostBinding',
517
+ 'HostListener',
518
+ 'ViewChild',
519
+ 'ViewChildren',
520
+ 'ContentChild',
521
+ 'ContentChildren',
522
+ ]);
523
+ for (const member of node.members) {
524
+ const mDecorators = ts.getDecorators(member);
525
+ if (!mDecorators)
526
+ continue;
527
+ for (const dec of mDecorators) {
528
+ if (!ts.isCallExpression(dec.expression))
529
+ continue;
530
+ const decName = dec.expression.expression.getText(origSourceFile);
531
+ if (MEMBER_DECORATOR_NAMES.has(decName)) {
532
+ memberDecorators.push(dec);
533
+ }
534
+ }
535
+ }
536
+ classResults.push({
537
+ ivyCode,
538
+ decorators: [...angularDecorators, ...memberDecorators],
539
+ classEnd: node.getEnd(),
540
+ });
541
+ }
542
+ // Apply edits via MagicString
543
+ const ms = new MagicString(sourceCode, { filename: fileName });
544
+ // 1. Prepend i0 import (skip if already present)
545
+ if (!sourceCode.includes('import * as i0 from')) {
546
+ ms.prepend('import * as i0 from "@angular/core";\n');
547
+ }
548
+ // 1b. Inject synthetic imports for NgModule-exported classes
549
+ for (const [name, specifier] of syntheticImports) {
550
+ if (!importedNames.has(name)) {
551
+ ms.prepend(`import { ${name} } from "${specifier}";\n`);
552
+ importedNames.add(name);
553
+ }
554
+ }
555
+ // 2. For each compiled class: remove decorators + insert Ivy definitions
556
+ for (const cr of classResults) {
557
+ // Remove Angular decorators from source
558
+ for (const dec of cr.decorators) {
559
+ const start = dec.getStart(origSourceFile);
560
+ const end = dec.getEnd();
561
+ let trimEnd = end;
562
+ while (trimEnd < sourceCode.length &&
563
+ (sourceCode[trimEnd] === ' ' ||
564
+ sourceCode[trimEnd] === '\n' ||
565
+ sourceCode[trimEnd] === '\r')) {
566
+ trimEnd++;
567
+ }
568
+ ms.remove(start, trimEnd);
569
+ }
570
+ // Insert static members before closing }
571
+ if (cr.ivyCode.length > 0) {
572
+ const memberCode = cr.ivyCode.map((c) => ' ' + c + ';').join('\n');
573
+ ms.appendLeft(cr.classEnd - 1, '\n' + memberCode + '\n');
574
+ }
575
+ }
576
+ // 3. Emit constant pool statements.
577
+ // Split into: helper declarations (const/function) that must appear before
578
+ // the class (since static property initializers reference them), and
579
+ // side-effect statements (setClassMetadata IIFEs) that go after.
580
+ const helpers = [];
581
+ const sideEffects = [];
582
+ for (const s of constantPool.statements) {
583
+ const code = emitAngularStmt(s);
584
+ if (s instanceof o.ExpressionStatement &&
585
+ s.expr instanceof o.InvokeFunctionExpr) {
586
+ sideEffects.push(code);
587
+ }
588
+ else {
589
+ helpers.push(code);
590
+ }
591
+ }
592
+ if (helpers.length > 0) {
593
+ // Insert helpers after the last import / top-level non-class statement,
594
+ // well before any class that references them.
595
+ let insertPos = 0;
596
+ for (const stmt of origSourceFile.statements) {
597
+ if (ts.isImportDeclaration(stmt) ||
598
+ (ts.isVariableStatement(stmt) &&
599
+ !stmt.getText(origSourceFile).includes('class'))) {
600
+ insertPos = stmt.getEnd();
601
+ }
602
+ else if (!ts.isExportAssignment(stmt)) {
603
+ // Stop at the first class or non-import/non-variable statement
604
+ break;
605
+ }
606
+ }
607
+ ms.appendLeft(insertPos, '\n' + helpers.join('\n') + '\n');
608
+ }
609
+ if (sideEffects.length > 0) {
610
+ ms.append('\n\n' + sideEffects.join('\n'));
611
+ }
612
+ const map = ms.generateMap({
613
+ source: fileName,
614
+ file: fileName + '.js',
615
+ includeContent: true,
616
+ hires: 'boundary',
617
+ });
618
+ return {
619
+ code: ms.toString(),
620
+ map,
621
+ resourceDependencies,
622
+ };
623
+ }
624
+ function injectAngularImport(sf) {
625
+ return ts.factory.updateSourceFile(sf, [
626
+ ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamespaceImport(ts.factory.createIdentifier('i0'))), ts.factory.createStringLiteral('@angular/core')),
627
+ ...sf.statements,
628
+ ]);
629
+ }
630
+ //# sourceMappingURL=compile.js.map