Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(compiler): add ability to produce stub .ngfactory / .ngsummary files #16963

Merged
merged 1 commit into from May 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/compiler-cli/src/codegen.ts
Expand Up @@ -38,8 +38,9 @@ export class CodeGenerator {

codegen(): Promise<any> {
return this.compiler
.compileAllAsync(this.program.getSourceFiles().map(
.analyzeModulesAsync(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
.then(analyzedModules => this.compiler.emitAllImpls(analyzedModules))
.then(generatedModules => {
generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
Expand Down
103 changes: 71 additions & 32 deletions packages/compiler/src/aot/compiler.ts
Expand Up @@ -24,7 +24,7 @@ import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
import {serializeSummaries} from './summary_serializer';
import {createForJitStub, serializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';

export class AotCompiler {
Expand All @@ -39,38 +39,86 @@ export class AotCompiler {

clearCache() { this._metadataResolver.clearCache(); }

compileAllAsync(rootFiles: string[]): Promise<GeneratedFile[]> {
analyzeModulesSync(rootFiles: string[]): NgAnalyzedModules {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} =
const analyzeResult =
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
analyzeResult.ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, true));
return analyzeResult;
}

analyzeModulesAsync(rootFiles: string[]): Promise<NgAnalyzedModules> {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const analyzeResult =
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
return Promise
.all(ngModules.map(
.all(analyzeResult.ngModules.map(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, false)))
.then(() => {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
file.ngModules, file.injectables));
return flatten(sourceModules);
});
.then(() => analyzeResult);
}

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));
emitAllStubs(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
const {files} = analyzeResult;
const sourceModules =
files.map(file => this._compileStubFile(file.srcUrl, file.directives, file.ngModules));
return flatten(sourceModules);
}

emitAllImpls(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
const {ngModuleByPipeOrDirective, files} = analyzeResult;
const sourceModules = files.map(
file => this._compileSrcFile(
file => this._compileImplFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables));
return flatten(sourceModules);
}

private _compileSrcFile(
private _compileStubFile(
srcFileUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
const generatedFiles: GeneratedFile[] = [];

const jitSummaryStmts: o.Statement[] = [];
const ngFactoryStms: o.Statement[] = [];

const ngFactoryOutputCtx = this._createOutputContext(ngfactoryFilePath(srcFileUrl, true));
const jitSummaryOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileUrl, true));

// create exports that user code can reference
ngModules.forEach((ngModuleReference) => {
this._ngModuleCompiler.createStub(ngFactoryOutputCtx, ngModuleReference);
createForJitStub(jitSummaryOutputCtx, ngModuleReference);
});
// Note: we are creating stub ngfactory/ngsummary for all source files,
// as the real calculation requires almost the same logic as producing the real content for
// them.
// Our pipeline will filter out empty ones at the end.
generatedFiles.push(this._codegenSourceModule(srcFileUrl, ngFactoryOutputCtx));
generatedFiles.push(this._codegenSourceModule(srcFileUrl, jitSummaryOutputCtx));

// create stubs for external stylesheets (always empty, as users should not import anything from
// the generated code)
directives.forEach((dirType) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
if (!compMeta.isComponent) {
return;
}
// Note: compMeta is a component and therefore template is non null.
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
generatedFiles.push(this._codegenSourceModule(
stylesheetMeta.moduleUrl !,
this._createOutputContext(_stylesModuleUrl(
stylesheetMeta.moduleUrl !, this._styleCompiler.needsStyleShim(compMeta),
fileSuffix))));
});
});
return generatedFiles;
}

private _compileImplFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
injectables: StaticSymbol[]): GeneratedFile[] {
Expand All @@ -89,21 +137,20 @@ export class AotCompiler {
directives.forEach((dirType) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
if (!compMeta.isComponent) {
return Promise.resolve(null);
return;
}
const ngModule = ngModuleByPipeOrDirective.get(dirType);
if (!ngModule) {
throw new Error(
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
}

_assertComponent(compMeta);

// compile styles
const componentStylesheet = this._styleCompiler.compileComponent(outputCtx, compMeta);
// Note: compMeta is a component and therefore template is non null.
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
generatedFiles.push(this._codegenStyles(srcFileUrl, compMeta, stylesheetMeta, fileSuffix));
generatedFiles.push(
this._codegenStyles(stylesheetMeta.moduleUrl !, compMeta, stylesheetMeta, fileSuffix));
});

// compile components
Expand Down Expand Up @@ -149,7 +196,6 @@ export class AotCompiler {
}))
];
const forJitOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileUrl, true));
const forJitTargetFilePath = summaryForJitFileName(srcFileUrl, true);
const {json, exportAs} = serializeSummaries(
forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries, typeData);
exportAs.forEach((entry) => {
Expand Down Expand Up @@ -305,13 +351,6 @@ function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string):
return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`;
}

function _assertComponent(meta: CompileDirectiveMetadata) {
if (!meta.isComponent) {
throw new Error(
`Could not compile '${identifierName(meta.type)}' because it is not a component.`);
}
}

export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
Expand Down
21 changes: 17 additions & 4 deletions packages/compiler/src/aot/summary_serializer.ts
Expand Up @@ -87,6 +87,20 @@ export function deserializeSummaries(symbolCache: StaticSymbolCache, json: strin
return deserializer.deserialize(json);
}

export function createForJitStub(outputCtx: OutputContext, reference: StaticSymbol) {
return createSummaryForJitFunction(outputCtx, reference, o.NULL_EXPR);
}

function createSummaryForJitFunction(
outputCtx: OutputContext, reference: StaticSymbol, value: o.Expression) {
const fnName = summaryForJitName(reference.name);
outputCtx.statements.push(
o.fn([], [new o.ReturnStatement(value)], new o.ArrayType(o.DYNAMIC_TYPE)).toDeclStmt(fnName, [
o.StmtModifier.Final, o.StmtModifier.Exported
]));
}


class ToJsonSerializer extends ValueTransformer {
// Note: This only contains symbols without members.
symbols: StaticSymbol[] = [];
Expand Down Expand Up @@ -215,10 +229,9 @@ class ForJitSerializer {
}
if (!isLibrary) {
const fnName = summaryForJitName(summary.type.reference.name);
this.outputCtx.statements.push(
o.fn([], [new o.ReturnStatement(this.serializeSummaryWithDeps(summary, metadata !))],
new o.ArrayType(o.DYNAMIC_TYPE))
.toDeclStmt(fnName, [o.StmtModifier.Final, o.StmtModifier.Exported]));
createSummaryForJitFunction(
this.outputCtx, summary.type.reference,
this.serializeSummaryWithDeps(summary, metadata !));
}
});

Expand Down
38 changes: 24 additions & 14 deletions packages/compiler/src/ng_module_compiler.ts
Expand Up @@ -50,21 +50,13 @@ export class NgModuleCompiler {
[new o.FnParam(LOG_VAR.name !)], [new o.ReturnStatement(ngModuleDef)], o.INFERRED_TYPE);

const ngModuleFactoryVar = `${identifierName(ngModuleMeta.type)}NgFactory`;
const ngModuleFactoryStmt =
o.variable(ngModuleFactoryVar)
.set(o.importExpr(Identifiers.createModuleFactory).callFn([
ctx.importExpr(ngModuleMeta.type.reference),
o.literalArr(bootstrapComponents.map(id => ctx.importExpr(id.reference))),
ngModuleDefFactory
]))
.toDeclStmt(
o.importType(
Identifiers.NgModuleFactory,
[o.expressionType(ctx.importExpr(ngModuleMeta.type.reference)) !],
[o.TypeModifier.Const]),
[o.StmtModifier.Final, o.StmtModifier.Exported]);
this._createNgModuleFactory(
ctx, ngModuleMeta.type.reference, o.importExpr(Identifiers.createModuleFactory).callFn([
ctx.importExpr(ngModuleMeta.type.reference),
o.literalArr(bootstrapComponents.map(id => ctx.importExpr(id.reference))),
ngModuleDefFactory
]));

ctx.statements.push(ngModuleFactoryStmt);
if (ngModuleMeta.id) {
const registerFactoryStmt =
o.importExpr(Identifiers.RegisterModuleFactoryFn)
Expand All @@ -75,4 +67,22 @@ export class NgModuleCompiler {

return new NgModuleCompileResult(ngModuleFactoryVar);
}

createStub(ctx: OutputContext, ngModuleReference: any) {
this._createNgModuleFactory(ctx, ngModuleReference, o.NULL_EXPR);
}

private _createNgModuleFactory(ctx: OutputContext, reference: any, value: o.Expression) {
const ngModuleFactoryVar = `${identifierName({reference: reference})}NgFactory`;
const ngModuleFactoryStmt =
o.variable(ngModuleFactoryVar)
.set(value)
.toDeclStmt(
o.importType(
Identifiers.NgModuleFactory, [o.expressionType(ctx.importExpr(reference)) !],
[o.TypeModifier.Const]),
[o.StmtModifier.Final, o.StmtModifier.Exported]);

ctx.statements.push(ngModuleFactoryStmt);
}
}
69 changes: 69 additions & 0 deletions packages/compiler/test/aot/emit_stubs_spec.ts
@@ -0,0 +1,69 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {MockDirectory, compile, expectNoDiagnostics, setup, toMockFileArray} from './test_util';

describe('aot stubs', () => {
let angularFiles = setup();

it('should create empty .ngfactory and .ngsummary files for every source file', () => {
const appDir = {'app.ts': `export const x = 1;`};
const rootDir = {'app': appDir};
const {genFiles} =
compile([rootDir, angularFiles], {postCompile: expectNoDiagnostics, stubsOnly: true});
expect(genFiles.find((f) => f.genFileUrl === '/app/app.ngfactory.ts')).toBeTruthy();
expect(genFiles.find((f) => f.genFileUrl === '/app/app.ngsummary.ts')).toBeTruthy();
});

it('should create empty .ngstyle files for imported css files', () => {
const appDir = {
'app.ts': `
import {Component, NgModule} from '@angular/core';

@Component({
template: '',
styleUrls: ['./style.css']
})
export class MyComp {}

@NgModule({
declarations: [MyComp]
})
export class MyModule {}
export const x = 1;
`,
'style.css': ''
};
const rootDir = {'app': appDir};
const {genFiles} =
compile([rootDir, angularFiles], {postCompile: expectNoDiagnostics, stubsOnly: true});
expect(genFiles.find((f) => f.genFileUrl === '/app/style.css.shim.ngstyle.ts')).toBeTruthy();
});

it('should create stub exports for NgModules of the right type', () => {
const appDir = {
'app.module.ts': `
import { NgModule } from '@angular/core';

@NgModule()
export class MyModule {}
`,
'app.boot.ts': `
import {NgModuleFactory} from '@angular/core';
import {MyModuleNgFactory} from './app.module.ngfactory';
import {MyModuleNgSummary} from './app.module.ngsummary';
import {MyModule} from './app.module';

export const factory: NgModuleFactory<MyModule> = MyModuleNgFactory;
export const summary: () => any[] = MyModuleNgSummary;
`
};
const rootDir = {'app': appDir};
compile([rootDir, angularFiles], {postCompile: expectNoDiagnostics, stubsOnly: true});
});
});