@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.
- package/README.md +24 -0
- package/package.json +23 -5
- package/src/lib/angular-vite-plugin.d.ts +12 -1
- package/src/lib/angular-vite-plugin.js +102 -300
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/angular-vitest-plugin.js +2 -2
- package/src/lib/angular-vitest-plugin.js.map +1 -1
- package/src/lib/compiler/angular-version.d.ts +19 -0
- package/src/lib/compiler/angular-version.js +42 -0
- package/src/lib/compiler/angular-version.js.map +1 -0
- package/src/lib/compiler/class-field-lowering.d.ts +23 -0
- package/src/lib/compiler/class-field-lowering.js +213 -0
- package/src/lib/compiler/class-field-lowering.js.map +1 -0
- package/src/lib/compiler/compile.d.ts +44 -0
- package/src/lib/compiler/compile.js +1160 -0
- package/src/lib/compiler/compile.js.map +1 -0
- package/src/lib/compiler/constants.d.ts +18 -0
- package/src/lib/compiler/constants.js +48 -0
- package/src/lib/compiler/constants.js.map +1 -0
- package/src/lib/compiler/debug.d.ts +22 -0
- package/src/lib/compiler/debug.js +35 -0
- package/src/lib/compiler/debug.js.map +1 -0
- package/src/lib/compiler/defer.d.ts +47 -0
- package/src/lib/compiler/defer.js +203 -0
- package/src/lib/compiler/defer.js.map +1 -0
- package/src/lib/compiler/dts-reader.d.ts +35 -0
- package/src/lib/compiler/dts-reader.js +526 -0
- package/src/lib/compiler/dts-reader.js.map +1 -0
- package/src/lib/compiler/hmr.d.ts +16 -0
- package/src/lib/compiler/hmr.js +80 -0
- package/src/lib/compiler/hmr.js.map +1 -0
- package/src/lib/compiler/index.d.ts +7 -0
- package/src/lib/compiler/index.js +8 -0
- package/src/lib/compiler/index.js.map +1 -0
- package/src/lib/compiler/jit-metadata.d.ts +14 -0
- package/src/lib/compiler/jit-metadata.js +224 -0
- package/src/lib/compiler/jit-metadata.js.map +1 -0
- package/src/lib/compiler/jit-transform.d.ts +24 -0
- package/src/lib/compiler/jit-transform.js +269 -0
- package/src/lib/compiler/jit-transform.js.map +1 -0
- package/src/lib/compiler/js-emitter.d.ts +10 -0
- package/src/lib/compiler/js-emitter.js +502 -0
- package/src/lib/compiler/js-emitter.js.map +1 -0
- package/src/lib/compiler/metadata.d.ts +57 -0
- package/src/lib/compiler/metadata.js +894 -0
- package/src/lib/compiler/metadata.js.map +1 -0
- package/src/lib/compiler/registry.d.ts +49 -0
- package/src/lib/compiler/registry.js +273 -0
- package/src/lib/compiler/registry.js.map +1 -0
- package/src/lib/compiler/resource-inliner.d.ts +21 -0
- package/src/lib/compiler/resource-inliner.js +200 -0
- package/src/lib/compiler/resource-inliner.js.map +1 -0
- package/src/lib/compiler/style-ast.d.ts +8 -0
- package/src/lib/compiler/style-ast.js +110 -0
- package/src/lib/compiler/style-ast.js.map +1 -0
- package/src/lib/compiler/styles.d.ts +13 -0
- package/src/lib/compiler/styles.js +60 -0
- package/src/lib/compiler/styles.js.map +1 -0
- package/src/lib/compiler/test-helpers.d.ts +7 -0
- package/src/lib/compiler/test-helpers.js +28 -0
- package/src/lib/compiler/test-helpers.js.map +1 -0
- package/src/lib/compiler/type-elision.d.ts +26 -0
- package/src/lib/compiler/type-elision.js +313 -0
- package/src/lib/compiler/type-elision.js.map +1 -0
- package/src/lib/compiler/utils.d.ts +10 -0
- package/src/lib/compiler/utils.js +95 -0
- package/src/lib/compiler/utils.js.map +1 -0
- package/src/lib/fast-compile-plugin.d.ts +28 -0
- package/src/lib/fast-compile-plugin.js +420 -0
- package/src/lib/fast-compile-plugin.js.map +1 -0
- package/src/lib/utils/plugin-config.d.ts +45 -0
- package/src/lib/utils/plugin-config.js +89 -0
- package/src/lib/utils/plugin-config.js.map +1 -0
- package/src/lib/utils/safe-module-paths.d.ts +16 -0
- package/src/lib/utils/safe-module-paths.js +26 -0
- package/src/lib/utils/safe-module-paths.js.map +1 -0
- package/src/lib/utils/virtual-ids.d.ts +4 -0
- package/src/lib/utils/virtual-ids.js +30 -0
- package/src/lib/utils/virtual-ids.js.map +1 -0
- package/src/lib/utils/virtual-resources.d.ts +19 -0
- package/src/lib/utils/virtual-resources.js +47 -0
- 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
|