refactor(compiler): allow sync AOT compilation (#16832).
AOT compilation can be executed synchronously now, if the `ReosurceLoader` returns a string directly (and no `Promise`).
This commit is contained in:

committed by
Chuck Jazdzewski

parent
255d7226d1
commit
5af143e8e4
@ -39,7 +39,7 @@ export class AotCompiler {
|
||||
|
||||
clearCache() { this._metadataResolver.clearCache(); }
|
||||
|
||||
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
|
||||
compileAllAsync(rootFiles: string[]): Promise<GeneratedFile[]> {
|
||||
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
|
||||
@ -56,6 +56,20 @@ export class AotCompiler {
|
||||
});
|
||||
}
|
||||
|
||||
compileAllSync(rootFiles: string[]): GeneratedFile[] {
|
||||
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
|
||||
ngModules.forEach(
|
||||
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||
ngModule.type.reference, true));
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
|
||||
file.injectables));
|
||||
return flatten(sourceModules);
|
||||
}
|
||||
|
||||
private _compileSrcFile(
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
|
||||
|
@ -17,5 +17,5 @@ export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryRes
|
||||
/**
|
||||
* Loads a resource (e.g. html / css)
|
||||
*/
|
||||
loadResource(path: string): Promise<string>;
|
||||
loadResource(path: string): Promise<string>|string;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import {ResourceLoader} from './resource_loader';
|
||||
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
|
||||
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
|
||||
import {UrlResolver} from './url_resolver';
|
||||
import {SyncAsyncResult, isDefined, syntaxError} from './util';
|
||||
import {SyncAsync, isDefined, syntaxError} from './util';
|
||||
|
||||
export interface PrenormalizedTemplateMetadata {
|
||||
ngModuleType: any;
|
||||
@ -35,7 +35,7 @@ export interface PrenormalizedTemplateMetadata {
|
||||
|
||||
@CompilerInjectable()
|
||||
export class DirectiveNormalizer {
|
||||
private _resourceLoaderCache = new Map<string, Promise<string>>();
|
||||
private _resourceLoaderCache = new Map<string, SyncAsync<string>>();
|
||||
|
||||
constructor(
|
||||
private _resourceLoader: ResourceLoader, private _urlResolver: UrlResolver,
|
||||
@ -53,19 +53,17 @@ export class DirectiveNormalizer {
|
||||
(stylesheet) => { this._resourceLoaderCache.delete(stylesheet.moduleUrl !); });
|
||||
}
|
||||
|
||||
private _fetch(url: string): Promise<string> {
|
||||
private _fetch(url: string): SyncAsync<string> {
|
||||
let result = this._resourceLoaderCache.get(url);
|
||||
if (!result) {
|
||||
result = this._resourceLoader.get(url) !;
|
||||
result = this._resourceLoader.get(url);
|
||||
this._resourceLoaderCache.set(url, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
normalizeTemplate(prenormData: PrenormalizedTemplateMetadata):
|
||||
SyncAsyncResult<CompileTemplateMetadata> {
|
||||
let normalizedTemplateSync: CompileTemplateMetadata = null !;
|
||||
let normalizedTemplateAsync: Promise<CompileTemplateMetadata> = undefined !;
|
||||
SyncAsync<CompileTemplateMetadata> {
|
||||
if (isDefined(prenormData.template)) {
|
||||
if (isDefined(prenormData.templateUrl)) {
|
||||
throw syntaxError(
|
||||
@ -75,39 +73,33 @@ export class DirectiveNormalizer {
|
||||
throw syntaxError(
|
||||
`The template specified for component ${stringify(prenormData.componentType)} is not a string`);
|
||||
}
|
||||
normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
|
||||
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync !);
|
||||
} else if (isDefined(prenormData.templateUrl)) {
|
||||
if (typeof prenormData.templateUrl !== 'string') {
|
||||
throw syntaxError(
|
||||
`The templateUrl specified for component ${stringify(prenormData.componentType)} is not a string`);
|
||||
}
|
||||
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
|
||||
} else {
|
||||
throw syntaxError(
|
||||
`No template specified for component ${stringify(prenormData.componentType)}`);
|
||||
}
|
||||
return SyncAsync.then(
|
||||
this.normalizeTemplateOnly(prenormData),
|
||||
(result: CompileTemplateMetadata) => this.normalizeExternalStylesheets(result));
|
||||
}
|
||||
|
||||
if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) {
|
||||
// sync case
|
||||
return new SyncAsyncResult(normalizedTemplateSync);
|
||||
normalizeTemplateOnly(prenomData: PrenormalizedTemplateMetadata):
|
||||
SyncAsync<CompileTemplateMetadata> {
|
||||
let template: SyncAsync<string>;
|
||||
let templateUrl: string;
|
||||
if (prenomData.template != null) {
|
||||
template = prenomData.template;
|
||||
templateUrl = prenomData.moduleUrl;
|
||||
} else {
|
||||
// async case
|
||||
return new SyncAsyncResult(
|
||||
null, normalizedTemplateAsync.then(
|
||||
(normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate)));
|
||||
templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl !);
|
||||
template = this._fetch(templateUrl);
|
||||
}
|
||||
}
|
||||
|
||||
normalizeTemplateSync(prenomData: PrenormalizedTemplateMetadata): CompileTemplateMetadata {
|
||||
return this.normalizeLoadedTemplate(prenomData, prenomData.template !, prenomData.moduleUrl);
|
||||
}
|
||||
|
||||
normalizeTemplateAsync(prenomData: PrenormalizedTemplateMetadata):
|
||||
Promise<CompileTemplateMetadata> {
|
||||
const templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl !);
|
||||
return this._fetch(templateUrl)
|
||||
.then((value) => this.normalizeLoadedTemplate(prenomData, value, templateUrl));
|
||||
return SyncAsync.then(
|
||||
template, (template) => this.normalizeLoadedTemplate(prenomData, template, templateUrl));
|
||||
}
|
||||
|
||||
normalizeLoadedTemplate(
|
||||
@ -162,37 +154,42 @@ export class DirectiveNormalizer {
|
||||
}
|
||||
|
||||
normalizeExternalStylesheets(templateMeta: CompileTemplateMetadata):
|
||||
Promise<CompileTemplateMetadata> {
|
||||
return this._loadMissingExternalStylesheets(templateMeta.styleUrls)
|
||||
.then((externalStylesheets) => new CompileTemplateMetadata({
|
||||
encapsulation: templateMeta.encapsulation,
|
||||
template: templateMeta.template,
|
||||
templateUrl: templateMeta.templateUrl,
|
||||
styles: templateMeta.styles,
|
||||
styleUrls: templateMeta.styleUrls,
|
||||
externalStylesheets: externalStylesheets,
|
||||
ngContentSelectors: templateMeta.ngContentSelectors,
|
||||
animations: templateMeta.animations,
|
||||
interpolation: templateMeta.interpolation,
|
||||
isInline: templateMeta.isInline,
|
||||
}));
|
||||
SyncAsync<CompileTemplateMetadata> {
|
||||
return SyncAsync.then(
|
||||
this._loadMissingExternalStylesheets(templateMeta.styleUrls),
|
||||
(externalStylesheets) => new CompileTemplateMetadata({
|
||||
encapsulation: templateMeta.encapsulation,
|
||||
template: templateMeta.template,
|
||||
templateUrl: templateMeta.templateUrl,
|
||||
styles: templateMeta.styles,
|
||||
styleUrls: templateMeta.styleUrls,
|
||||
externalStylesheets: externalStylesheets,
|
||||
ngContentSelectors: templateMeta.ngContentSelectors,
|
||||
animations: templateMeta.animations,
|
||||
interpolation: templateMeta.interpolation,
|
||||
isInline: templateMeta.isInline,
|
||||
}));
|
||||
}
|
||||
|
||||
private _loadMissingExternalStylesheets(
|
||||
styleUrls: string[],
|
||||
loadedStylesheets:
|
||||
Map<string, CompileStylesheetMetadata> = new Map<string, CompileStylesheetMetadata>()):
|
||||
Promise<CompileStylesheetMetadata[]> {
|
||||
return Promise
|
||||
.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl))
|
||||
.map(styleUrl => this._fetch(styleUrl).then((loadedStyle) => {
|
||||
const stylesheet = this.normalizeStylesheet(
|
||||
new CompileStylesheetMetadata({styles: [loadedStyle], moduleUrl: styleUrl}));
|
||||
loadedStylesheets.set(styleUrl, stylesheet);
|
||||
return this._loadMissingExternalStylesheets(
|
||||
stylesheet.styleUrls, loadedStylesheets);
|
||||
})))
|
||||
.then((_) => Array.from(loadedStylesheets.values()));
|
||||
SyncAsync<CompileStylesheetMetadata[]> {
|
||||
return SyncAsync.then(
|
||||
SyncAsync.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl))
|
||||
.map(
|
||||
styleUrl => SyncAsync.then(
|
||||
this._fetch(styleUrl),
|
||||
(loadedStyle) => {
|
||||
const stylesheet =
|
||||
this.normalizeStylesheet(new CompileStylesheetMetadata(
|
||||
{styles: [loadedStyle], moduleUrl: styleUrl}));
|
||||
loadedStylesheets.set(styleUrl, stylesheet);
|
||||
return this._loadMissingExternalStylesheets(
|
||||
stylesheet.styleUrls, loadedStylesheets);
|
||||
}))),
|
||||
(_) => Array.from(loadedStylesheets.values()));
|
||||
}
|
||||
|
||||
normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata {
|
||||
|
@ -19,7 +19,7 @@ import {jitStatements} from '../output/output_jit';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {OutputContext, SyncAsyncResult} from '../util';
|
||||
import {OutputContext, SyncAsync} from '../util';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
|
||||
@ -51,20 +51,20 @@ export class JitCompiler implements Compiler {
|
||||
get injector(): Injector { return this._injector; }
|
||||
|
||||
compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T> {
|
||||
return this._compileModuleAndComponents(moduleType, true).syncResult !;
|
||||
return SyncAsync.assertSync(this._compileModuleAndComponents(moduleType, true));
|
||||
}
|
||||
|
||||
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> {
|
||||
return this._compileModuleAndComponents(moduleType, false).asyncResult !;
|
||||
return Promise.resolve(this._compileModuleAndComponents(moduleType, false));
|
||||
}
|
||||
|
||||
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> {
|
||||
return this._compileModuleAndAllComponents(moduleType, true).syncResult !;
|
||||
return SyncAsync.assertSync(this._compileModuleAndAllComponents(moduleType, true));
|
||||
}
|
||||
|
||||
compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>):
|
||||
Promise<ModuleWithComponentFactories<T>> {
|
||||
return this._compileModuleAndAllComponents(moduleType, false).asyncResult !;
|
||||
return Promise.resolve(this._compileModuleAndAllComponents(moduleType, false));
|
||||
}
|
||||
|
||||
getNgContentSelectors(component: Type<any>): string[] {
|
||||
@ -97,36 +97,24 @@ export class JitCompiler implements Compiler {
|
||||
}
|
||||
|
||||
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
|
||||
SyncAsyncResult<NgModuleFactory<T>> {
|
||||
const loadingPromise = this._loadModules(moduleType, isSync);
|
||||
const createResult = () => {
|
||||
SyncAsync<NgModuleFactory<T>> {
|
||||
return SyncAsync.then(this._loadModules(moduleType, isSync), () => {
|
||||
this._compileComponents(moduleType, null);
|
||||
return this._compileModule(moduleType);
|
||||
};
|
||||
if (isSync) {
|
||||
return new SyncAsyncResult(createResult());
|
||||
} else {
|
||||
return new SyncAsyncResult(null, loadingPromise.then(createResult));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _compileModuleAndAllComponents<T>(moduleType: Type<T>, isSync: boolean):
|
||||
SyncAsyncResult<ModuleWithComponentFactories<T>> {
|
||||
const loadingPromise = this._loadModules(moduleType, isSync);
|
||||
const createResult = () => {
|
||||
SyncAsync<ModuleWithComponentFactories<T>> {
|
||||
return SyncAsync.then(this._loadModules(moduleType, isSync), () => {
|
||||
const componentFactories: ComponentFactory<any>[] = [];
|
||||
this._compileComponents(moduleType, componentFactories);
|
||||
return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories);
|
||||
};
|
||||
if (isSync) {
|
||||
return new SyncAsyncResult(createResult());
|
||||
} else {
|
||||
return new SyncAsyncResult(null, loadingPromise.then(createResult));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _loadModules(mainModule: any, isSync: boolean): Promise<any> {
|
||||
const loadingPromises: Promise<any>[] = [];
|
||||
private _loadModules(mainModule: any, isSync: boolean): SyncAsync<any> {
|
||||
const loading: Promise<any>[] = [];
|
||||
const mainNgModule = this._metadataResolver.getNgModuleMetadata(mainModule) !;
|
||||
// Note: for runtime compilation, we want to transitively compile all modules,
|
||||
// so we also need to load the declared directives / pipes for all nested modules.
|
||||
@ -137,13 +125,13 @@ export class JitCompiler implements Compiler {
|
||||
const promise =
|
||||
this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync);
|
||||
if (promise) {
|
||||
loadingPromises.push(promise);
|
||||
loading.push(promise);
|
||||
}
|
||||
});
|
||||
this._filterJitIdentifiers(moduleMeta.declaredPipes)
|
||||
.forEach((ref) => this._metadataResolver.getOrLoadPipeMetadata(ref));
|
||||
});
|
||||
return Promise.all(loadingPromises);
|
||||
return SyncAsync.all(loading);
|
||||
}
|
||||
|
||||
private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, RendererType2, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef, ɵConsole as Console, ɵERROR_COMPONENT_TYPE, ɵccf as createComponentFactory, ɵstringify as stringify} from '@angular/core';
|
||||
import {Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, RendererType2, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef, ɵConsole as Console, ɵERROR_COMPONENT_TYPE, ɵccf as createComponentFactory, ɵisPromise as isPromise, ɵstringify as stringify} from '@angular/core';
|
||||
|
||||
import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol';
|
||||
import {ngfactoryFilePath} from './aot/util';
|
||||
@ -24,7 +24,7 @@ import {PipeResolver} from './pipe_resolver';
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {SummaryResolver} from './summary_resolver';
|
||||
import {getUrlScheme} from './url_resolver';
|
||||
import {MODULE_SUFFIX, ValueTransformer, noUndefined, syntaxError, visitValue} from './util';
|
||||
import {MODULE_SUFFIX, SyncAsync, ValueTransformer, noUndefined, syntaxError, visitValue} from './util';
|
||||
|
||||
export type ErrorCollector = (error: any, type?: any) => void;
|
||||
export const ERROR_COLLECTOR_TOKEN = new InjectionToken('ErrorCollector');
|
||||
@ -170,7 +170,7 @@ export class CompileMetadataResolver {
|
||||
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
|
||||
}
|
||||
|
||||
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): Promise<any>|null {
|
||||
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> {
|
||||
if (this._directiveCache.has(directiveType)) {
|
||||
return null;
|
||||
}
|
||||
@ -205,7 +205,7 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
this._directiveCache.set(directiveType, normalizedDirMeta);
|
||||
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
|
||||
return normalizedDirMeta;
|
||||
return null;
|
||||
};
|
||||
|
||||
if (metadata.isComponent) {
|
||||
@ -222,16 +222,11 @@ export class CompileMetadataResolver {
|
||||
animations: template.animations,
|
||||
interpolation: template.interpolation
|
||||
});
|
||||
if (templateMeta.syncResult) {
|
||||
createDirectiveMetadata(templateMeta.syncResult);
|
||||
if (isPromise(templateMeta) && isSync) {
|
||||
this._reportError(componentStillLoadingError(directiveType), directiveType);
|
||||
return null;
|
||||
} else {
|
||||
if (isSync) {
|
||||
this._reportError(componentStillLoadingError(directiveType), directiveType);
|
||||
return null;
|
||||
}
|
||||
return templateMeta.asyncResult !.then(createDirectiveMetadata);
|
||||
}
|
||||
return SyncAsync.then(templateMeta, createDirectiveMetadata);
|
||||
} else {
|
||||
// directive
|
||||
createDirectiveMetadata(null);
|
||||
|
@ -11,5 +11,5 @@
|
||||
* to load templates.
|
||||
*/
|
||||
export class ResourceLoader {
|
||||
get(url: string): Promise<string>|null { return null; }
|
||||
get(url: string): Promise<string>|string { return ''; }
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ɵisPromise as isPromise} from '@angular/core';
|
||||
|
||||
import * as o from './output/output_ast';
|
||||
|
||||
export const MODULE_SUFFIX = '';
|
||||
@ -80,19 +82,28 @@ export class ValueTransformer implements ValueVisitor {
|
||||
visitOther(value: any, context: any): any { return value; }
|
||||
}
|
||||
|
||||
export class SyncAsyncResult<T> {
|
||||
constructor(public syncResult: T|null, public asyncResult: Promise<T>|null = null) {
|
||||
if (!asyncResult) {
|
||||
this.asyncResult = Promise.resolve(syncResult);
|
||||
export type SyncAsync<T> = T | Promise<T>;
|
||||
|
||||
export const SyncAsync = {
|
||||
assertSync: <T>(value: SyncAsync<T>): T => {
|
||||
if (isPromise(value)) {
|
||||
throw new Error(`Illegal state: value cannot be a promise`);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
then: <T, R>(value: SyncAsync<T>, cb: (value: T) => R | Promise<R>| SyncAsync<R>):
|
||||
SyncAsync<R> => { return isPromise(value) ? value.then(cb) : cb(value);},
|
||||
all: <T>(syncAsyncValues: SyncAsync<T>[]): SyncAsync<T[]> => {
|
||||
return syncAsyncValues.some(isPromise) ? Promise.all(syncAsyncValues) : syncAsyncValues as T[];
|
||||
}
|
||||
}
|
||||
|
||||
export function syntaxError(msg: string): Error {
|
||||
const error = Error(msg);
|
||||
(error as any)[ERROR_SYNTAX_ERROR] = true;
|
||||
return error;
|
||||
}
|
||||
export function syntaxError(msg: string):
|
||||
Error {
|
||||
const error = Error(msg);
|
||||
(error as any)[ERROR_SYNTAX_ERROR] = true;
|
||||
return error;
|
||||
}
|
||||
|
||||
const ERROR_SYNTAX_ERROR = 'ngSyntaxError';
|
||||
|
||||
|
Reference in New Issue
Block a user