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:
Tobias Bosch
2017-05-17 15:39:08 -07:00
committed by Chuck Jazdzewski
parent 255d7226d1
commit 5af143e8e4
15 changed files with 577 additions and 643 deletions

View File

@ -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[],

View File

@ -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;
}

View File

@ -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 {

View File

@ -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> {

View File

@ -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);

View File

@ -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 ''; }
}

View File

@ -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';