@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.
- package/COMPILER.md +474 -0
- package/README.md +5 -0
- package/package.json +51 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +6 -0
- package/src/index.js.map +1 -0
- package/src/lib/ast-translator.d.ts +40 -0
- package/src/lib/ast-translator.js +243 -0
- package/src/lib/ast-translator.js.map +1 -0
- package/src/lib/compile.d.ts +24 -0
- package/src/lib/compile.js +630 -0
- package/src/lib/compile.js.map +1 -0
- package/src/lib/defer.d.ts +23 -0
- package/src/lib/defer.js +158 -0
- package/src/lib/defer.js.map +1 -0
- package/src/lib/dts-reader.d.ts +25 -0
- package/src/lib/dts-reader.js +326 -0
- package/src/lib/dts-reader.js.map +1 -0
- package/src/lib/hmr.d.ts +12 -0
- package/src/lib/hmr.js +49 -0
- package/src/lib/hmr.js.map +1 -0
- package/src/lib/index.d.ts +5 -0
- package/src/lib/index.js +6 -0
- package/src/lib/index.js.map +1 -0
- package/src/lib/jit-metadata.d.ts +11 -0
- package/src/lib/jit-metadata.js +201 -0
- package/src/lib/jit-metadata.js.map +1 -0
- package/src/lib/jit-transform.d.ts +21 -0
- package/src/lib/jit-transform.js +207 -0
- package/src/lib/jit-transform.js.map +1 -0
- package/src/lib/js-emitter.d.ts +8 -0
- package/src/lib/js-emitter.js +317 -0
- package/src/lib/js-emitter.js.map +1 -0
- package/src/lib/metadata.d.ts +36 -0
- package/src/lib/metadata.js +465 -0
- package/src/lib/metadata.js.map +1 -0
- package/src/lib/registry.d.ts +32 -0
- package/src/lib/registry.js +177 -0
- package/src/lib/registry.js.map +1 -0
- package/src/lib/resource-inliner.d.ts +17 -0
- package/src/lib/resource-inliner.js +109 -0
- package/src/lib/resource-inliner.js.map +1 -0
- package/src/lib/style-ast.d.ts +8 -0
- package/src/lib/style-ast.js +110 -0
- package/src/lib/style-ast.js.map +1 -0
- package/src/lib/styles.d.ts +13 -0
- package/src/lib/styles.js +60 -0
- package/src/lib/styles.js.map +1 -0
- package/src/lib/utils.d.ts +8 -0
- package/src/lib/utils.js +71 -0
- package/src/lib/utils.js.map +1 -0
- package/vite.config.d.ts +2 -0
- package/vite.config.js +19 -0
- 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
|