feat(ivy): support templateUrl for ngtsc (#24704)
This commit adds support for templateUrl in component templates within ngtsc. The compilation pipeline is split into sync and async versions, where asynchronous compilation invokes a special preanalyze() phase of analysis. The preanalyze() phase can optionally return a Promise which will delay compilation until it resolves. A ResourceLoader interface is used to resolve templateUrls to template strings and can return results either synchronously or asynchronously. During sync compilation it is an error if the ResourceLoader returns a Promise. Two ResourceLoader implementations are provided. One uses 'fs' to read resources directly from disk and is chosen if the CompilerHost doesn't provide a readResource method. The other wraps the readResource method from CompilerHost if it's provided. PR Close #24704
This commit is contained in:

committed by
Miško Hevery

parent
0922228024
commit
0c3738a780
@ -26,6 +26,15 @@ export interface DecoratorHandler<A> {
|
||||
*/
|
||||
detect(decorator: Decorator[]): Decorator|undefined;
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronously perform pre-analysis on the decorator/class combination.
|
||||
*
|
||||
* `preAnalyze` is optional and is not guaranteed to be called through all compilation flows. It
|
||||
* will only be called if asynchronicity is supported in the CompilerHost.
|
||||
*/
|
||||
preanalyze?(node: ts.Declaration, decorator: Decorator): Promise<void>|undefined;
|
||||
|
||||
/**
|
||||
* Perform analysis on the decorator/class combination, producing instructions for compilation
|
||||
* if successful, or an array of diagnostic messages if the analysis fails or the decorator
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
@ -14,9 +13,6 @@ import {reflectNameOfDeclaration} from '../../metadata/src/reflector';
|
||||
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from './api';
|
||||
import {DtsFileTransformer} from './declaration';
|
||||
import {ImportManager, translateType} from './translator';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Record of an adapter which decided to emit a static field, and the analysis it performed to
|
||||
@ -45,6 +41,8 @@ export class IvyCompilation {
|
||||
* Tracks the `DtsFileTransformer`s for each TS file that needs .d.ts transformations.
|
||||
*/
|
||||
private dtsMap = new Map<string, DtsFileTransformer>();
|
||||
private _diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
|
||||
/**
|
||||
* @param handlers array of `DecoratorHandler`s which will be executed against each class in the
|
||||
@ -59,11 +57,18 @@ export class IvyCompilation {
|
||||
private handlers: DecoratorHandler<any>[], private checker: ts.TypeChecker,
|
||||
private reflector: ReflectionHost, private coreImportsFrom: ts.SourceFile|null) {}
|
||||
|
||||
|
||||
analyzeSync(sf: ts.SourceFile): void { return this.analyze(sf, false); }
|
||||
|
||||
analyzeAsync(sf: ts.SourceFile): Promise<void>|undefined { return this.analyze(sf, true); }
|
||||
|
||||
/**
|
||||
* Analyze a source file and produce diagnostics for it (if any).
|
||||
*/
|
||||
analyze(sf: ts.SourceFile): ts.Diagnostic[] {
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
private analyze(sf: ts.SourceFile, preanalyze: false): undefined;
|
||||
private analyze(sf: ts.SourceFile, preanalyze: true): Promise<void>|undefined;
|
||||
private analyze(sf: ts.SourceFile, preanalyze: boolean): Promise<void>|undefined {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
const analyzeClass = (node: ts.Declaration): void => {
|
||||
// The first step is to reflect the decorators.
|
||||
@ -79,23 +84,38 @@ export class IvyCompilation {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for multiple decorators on the same node. Technically speaking this
|
||||
// could be supported, but right now it's an error.
|
||||
if (this.analysis.has(node)) {
|
||||
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
|
||||
}
|
||||
const completeAnalysis = () => {
|
||||
// Check for multiple decorators on the same node. Technically speaking this
|
||||
// could be supported, but right now it's an error.
|
||||
if (this.analysis.has(node)) {
|
||||
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
|
||||
}
|
||||
|
||||
// Run analysis on the decorator. This will produce either diagnostics, an
|
||||
// analysis result, or both.
|
||||
const analysis = adapter.analyze(node, decorator);
|
||||
if (analysis.diagnostics !== undefined) {
|
||||
diagnostics.push(...analysis.diagnostics);
|
||||
}
|
||||
if (analysis.analysis !== undefined) {
|
||||
this.analysis.set(node, {
|
||||
adapter,
|
||||
analysis: analysis.analysis, decorator,
|
||||
});
|
||||
// Run analysis on the decorator. This will produce either diagnostics, an
|
||||
// analysis result, or both.
|
||||
const analysis = adapter.analyze(node, decorator);
|
||||
|
||||
if (analysis.analysis !== undefined) {
|
||||
this.analysis.set(node, {
|
||||
adapter,
|
||||
analysis: analysis.analysis, decorator,
|
||||
});
|
||||
}
|
||||
|
||||
if (analysis.diagnostics !== undefined) {
|
||||
this._diagnostics.push(...analysis.diagnostics);
|
||||
}
|
||||
};
|
||||
|
||||
if (preanalyze && adapter.preanalyze !== undefined) {
|
||||
const preanalysis = adapter.preanalyze(node, decorator);
|
||||
if (preanalysis !== undefined) {
|
||||
promises.push(preanalysis.then(() => completeAnalysis()));
|
||||
} else {
|
||||
completeAnalysis();
|
||||
}
|
||||
} else {
|
||||
completeAnalysis();
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -109,7 +129,12 @@ export class IvyCompilation {
|
||||
};
|
||||
|
||||
visit(sf);
|
||||
return diagnostics;
|
||||
|
||||
if (preanalyze && promises.length > 0) {
|
||||
return Promise.all(promises).then(() => undefined);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +191,8 @@ export class IvyCompilation {
|
||||
return this.dtsMap.get(tsFileName) !.transform(dtsOriginalSource, tsFileName);
|
||||
}
|
||||
|
||||
get diagnostics(): ReadonlyArray<ts.Diagnostic> { return this._diagnostics; }
|
||||
|
||||
private getDtsTransformer(tsFileName: string): DtsFileTransformer {
|
||||
if (!this.dtsMap.has(tsFileName)) {
|
||||
this.dtsMap.set(tsFileName, new DtsFileTransformer(this.coreImportsFrom));
|
||||
|
Reference in New Issue
Block a user