feat(ivy): produce Renderer2 back-patching and factories. (#22506)
Produces back-patch as described in the #22235 and referenced in #22480. This just contains the compiler implementations and the corresponding unit tests. Connecting the dots as described in #22480 will be in a follow on change. PR Close #22506
This commit is contained in:
parent
5412e10bcd
commit
b0b9ca3386
|
@ -21,6 +21,7 @@ import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||||
|
import {OutputMode} from '../render3/r3_types';
|
||||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
|
@ -170,7 +171,7 @@ export class AotCompiler {
|
||||||
_createEmptyStub(outputCtx);
|
_createEmptyStub(outputCtx);
|
||||||
}
|
}
|
||||||
// Note: for the stubs, we don't need a property srcFileUrl,
|
// Note: for the stubs, we don't need a property srcFileUrl,
|
||||||
// as lateron in emitAllImpls we will create the proper GeneratedFiles with the
|
// as later on in emitAllImpls we will create the proper GeneratedFiles with the
|
||||||
// correct srcFileUrl.
|
// correct srcFileUrl.
|
||||||
// This is good as e.g. for .ngstyle.ts files we can't derive
|
// This is good as e.g. for .ngstyle.ts files we can't derive
|
||||||
// the url of components based on the genFileUrl.
|
// the url of components based on the genFileUrl.
|
||||||
|
@ -223,7 +224,7 @@ export class AotCompiler {
|
||||||
let componentId = 0;
|
let componentId = 0;
|
||||||
file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => {
|
file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => {
|
||||||
// Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck,
|
// Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck,
|
||||||
// so we don't change the .ngfactory file too much when adding the typecheck block.
|
// so we don't change the .ngfactory file too much when adding the type-check block.
|
||||||
|
|
||||||
// create exports that user code can reference
|
// create exports that user code can reference
|
||||||
this._ngModuleCompiler.createStub(outputCtx, ngModuleMeta.type.reference);
|
this._ngModuleCompiler.createStub(outputCtx, ngModuleMeta.type.reference);
|
||||||
|
@ -256,7 +257,7 @@ export class AotCompiler {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (emitFlags & StubEmitFlags.TypeCheck) {
|
if (emitFlags & StubEmitFlags.TypeCheck) {
|
||||||
// add the typecheck block for all components of the NgModule
|
// add the type-check block for all components of the NgModule
|
||||||
ngModuleMeta.declaredDirectives.forEach((dirId) => {
|
ngModuleMeta.declaredDirectives.forEach((dirId) => {
|
||||||
const compMeta = this._metadataResolver.getDirectiveMetadata(dirId.reference);
|
const compMeta = this._metadataResolver.getDirectiveMetadata(dirId.reference);
|
||||||
if (!compMeta.isComponent) {
|
if (!compMeta.isComponent) {
|
||||||
|
@ -366,16 +367,18 @@ export class AotCompiler {
|
||||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||||
compileIvyComponent(
|
compileIvyComponent(
|
||||||
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector,
|
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector,
|
||||||
hostBindingParser);
|
hostBindingParser, OutputMode.PartialClass);
|
||||||
} else {
|
} else {
|
||||||
compileIvyDirective(context, directiveMetadata, this._reflector, hostBindingParser);
|
compileIvyDirective(
|
||||||
|
context, directiveMetadata, this._reflector, hostBindingParser,
|
||||||
|
OutputMode.PartialClass);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pipes.forEach(pipeType => {
|
pipes.forEach(pipeType => {
|
||||||
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
||||||
if (pipeMetadata) {
|
if (pipeMetadata) {
|
||||||
compileIvyPipe(context, pipeMetadata, this._reflector);
|
compileIvyPipe(context, pipeMetadata, this._reflector, OutputMode.PartialClass);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* @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 {StaticReflector} from '../aot/static_reflector';
|
||||||
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileTypeMetadata} from '../compile_metadata';
|
||||||
|
import {DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Lexer, ParseError, Parser} from '../compiler';
|
||||||
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
|
import * as o from '../output/output_ast';
|
||||||
|
import {BindingParser} from '../template_parser/binding_parser';
|
||||||
|
import {TemplateAst} from '../template_parser/template_ast';
|
||||||
|
import {OutputContext} from '../util';
|
||||||
|
|
||||||
|
import {compilePipe} from './r3_pipe_compiler';
|
||||||
|
import {BUILD_OPTIMIZER_REMOVE, OutputMode} from './r3_types';
|
||||||
|
import {compileComponent, compileDirective} from './r3_view_compiler';
|
||||||
|
|
||||||
|
export const enum ModuleKind {
|
||||||
|
Renderer2,
|
||||||
|
Renderer3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce the back-patching function for the given module to the output context.
|
||||||
|
*/
|
||||||
|
export function compileModuleBackPatch(
|
||||||
|
outputCtx: OutputContext, name: string, module: CompileNgModuleMetadata, kind: ModuleKind,
|
||||||
|
backPatchReferenceOf: (module: CompileTypeMetadata) => o.Expression,
|
||||||
|
parseTemplate: (
|
||||||
|
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
|
||||||
|
directiveIdentifiers: CompileIdentifierMetadata[]) => {
|
||||||
|
template: TemplateAst[],
|
||||||
|
pipes: CompilePipeSummary[]
|
||||||
|
},
|
||||||
|
reflector: StaticReflector, resolver: CompileMetadataResolver) {
|
||||||
|
const imports: o.Statement[] = [];
|
||||||
|
let statements: o.Statement[] = [];
|
||||||
|
|
||||||
|
// Call dependent back patching
|
||||||
|
for (const importedModule of module.importedModules) {
|
||||||
|
const importBackPatchFunction = backPatchReferenceOf(importedModule.type);
|
||||||
|
|
||||||
|
// e.g. // @BUILD_OPTIMIZER_REMOVE
|
||||||
|
imports.push(new o.CommentStmt(BUILD_OPTIMIZER_REMOVE));
|
||||||
|
|
||||||
|
// e.g. ngBackPatch_some_other_module_Module();
|
||||||
|
imports.push(importBackPatchFunction.callFn([]).toStmt());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The local output context allows collecting the back-patch statements that
|
||||||
|
// are generated by the various compilers which allows putting placing them
|
||||||
|
// into the body of a function instead of at global scope.
|
||||||
|
const localCtx: OutputContext = {
|
||||||
|
statements,
|
||||||
|
constantPool: outputCtx.constantPool,
|
||||||
|
genFilePath: outputCtx.genFilePath,
|
||||||
|
importExpr: outputCtx.importExpr
|
||||||
|
};
|
||||||
|
|
||||||
|
// e.g. export function ngBackPatch_some_module_Lib1Module()
|
||||||
|
if (kind === ModuleKind.Renderer2) {
|
||||||
|
// For all Renderer2 modules generate back-patching code for all the components, directives,
|
||||||
|
// pipes, and injectables as well as the injector def for the module itself.
|
||||||
|
|
||||||
|
const expressionParser = new Parser(new Lexer());
|
||||||
|
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
|
const errors: ParseError[] = [];
|
||||||
|
const hostBindingParser = new BindingParser(
|
||||||
|
expressionParser, DEFAULT_INTERPOLATION_CONFIG, elementSchemaRegistry, [], errors);
|
||||||
|
|
||||||
|
// Back-patch all declared directive and components
|
||||||
|
for (const declaredDirective of module.declaredDirectives) {
|
||||||
|
const declaredDirectiveMetadata = resolver.getDirectiveMetadata(declaredDirective.reference);
|
||||||
|
if (declaredDirectiveMetadata.isComponent) {
|
||||||
|
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||||
|
parseTemplate(declaredDirectiveMetadata, module, module.transitiveModule.directives);
|
||||||
|
compileComponent(
|
||||||
|
localCtx, declaredDirectiveMetadata, parsedPipes, parsedTemplate, reflector,
|
||||||
|
hostBindingParser, OutputMode.BackPatch);
|
||||||
|
} else {
|
||||||
|
compileDirective(
|
||||||
|
localCtx, declaredDirectiveMetadata, reflector, hostBindingParser,
|
||||||
|
OutputMode.BackPatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back-patch all pipes declared in the module.
|
||||||
|
for (const pipeType of module.declaredPipes) {
|
||||||
|
const pipeMetadata = resolver.getPipeMetadata(pipeType.reference);
|
||||||
|
if (pipeMetadata) {
|
||||||
|
compilePipe(localCtx, pipeMetadata, reflector, OutputMode.BackPatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputCtx.statements.push(new o.DeclareFunctionStmt(
|
||||||
|
name, [], [...imports, ...statements], o.INFERRED_TYPE, [o.StmtModifier.Exported]));
|
||||||
|
}
|
|
@ -10,18 +10,12 @@ import * as o from '../output/output_ast';
|
||||||
|
|
||||||
const CORE = '@angular/core';
|
const CORE = '@angular/core';
|
||||||
|
|
||||||
// Copied from core and must be in sync with the value in the runtime.
|
|
||||||
export const enum LifeCycleGuard {ON_INIT = 1, ON_DESTROY = 2, ON_CHANGES = 4}
|
|
||||||
|
|
||||||
// TODO: Include assignments that use the enum literals
|
|
||||||
// e.g. { let a: core.LifeCycleGuard.ON_INIT = LifeCycleGuard.ON_INIT; ...}
|
|
||||||
// Ensure these get removed in bundling.
|
|
||||||
|
|
||||||
export class Identifiers {
|
export class Identifiers {
|
||||||
/* Methods */
|
/* Methods */
|
||||||
static NEW_METHOD = 'n';
|
static NEW_METHOD = 'n';
|
||||||
static HOST_BINDING_METHOD = 'h';
|
static HOST_BINDING_METHOD = 'h';
|
||||||
static TRANSFORM_METHOD = 'transform';
|
static TRANSFORM_METHOD = 'transform';
|
||||||
|
static PATCH_DEPS = 'patchedDeps';
|
||||||
|
|
||||||
/* Instructions */
|
/* Instructions */
|
||||||
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* @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 {StaticReflector} from '../aot/static_reflector';
|
||||||
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||||
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
|
import * as o from '../output/output_ast';
|
||||||
|
import {OutputContext} from '../util';
|
||||||
|
|
||||||
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a Renderer2 compatibility module factory to the output context.
|
||||||
|
*/
|
||||||
|
export function compileModuleFactory(
|
||||||
|
outputCtx: OutputContext, module: CompileNgModuleMetadata,
|
||||||
|
backPatchReferenceOf: (module: CompileTypeMetadata) => o.Expression,
|
||||||
|
resolver: CompileMetadataResolver) {
|
||||||
|
const ngModuleFactoryVar = `${identifierName(module.type)}NgFactory`;
|
||||||
|
|
||||||
|
const parentInjector = 'parentInjector';
|
||||||
|
const createFunction = o.fn(
|
||||||
|
[new o.FnParam(parentInjector, o.DYNAMIC_TYPE)],
|
||||||
|
[new o.IfStmt(
|
||||||
|
o.THIS_EXPR.prop(R3.PATCH_DEPS).notIdentical(o.literal(true, o.INFERRED_TYPE)),
|
||||||
|
[
|
||||||
|
o.THIS_EXPR.prop(R3.PATCH_DEPS).set(o.literal(true, o.INFERRED_TYPE)).toStmt(),
|
||||||
|
backPatchReferenceOf(module.type).callFn([]).toStmt()
|
||||||
|
])],
|
||||||
|
o.INFERRED_TYPE, null, `${ngModuleFactoryVar}_Create`);
|
||||||
|
|
||||||
|
const moduleFactoryLiteral = o.literalMap([
|
||||||
|
{key: 'moduleType', value: outputCtx.importExpr(module.type.reference), quoted: false},
|
||||||
|
{key: 'create', value: createFunction, quoted: false}
|
||||||
|
]);
|
||||||
|
|
||||||
|
outputCtx.statements.push(
|
||||||
|
o.variable(ngModuleFactoryVar).set(moduleFactoryLiteral).toDeclStmt(o.DYNAMIC_TYPE, [
|
||||||
|
o.StmtModifier.Exported, o.StmtModifier.Final
|
||||||
|
]));
|
||||||
|
}
|
|
@ -13,10 +13,15 @@ import * as o from '../output/output_ast';
|
||||||
import {OutputContext, error} from '../util';
|
import {OutputContext, error} from '../util';
|
||||||
|
|
||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
import {BUILD_OPTIMIZER_COLOCATE, OutputMode} from './r3_types';
|
||||||
import {createFactory} from './r3_view_compiler';
|
import {createFactory} from './r3_view_compiler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a pipe definition to the output context.
|
||||||
|
*/
|
||||||
export function compilePipe(
|
export function compilePipe(
|
||||||
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
|
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector,
|
||||||
|
mode: OutputMode) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
|
||||||
// e.g. 'type: MyPipe`
|
// e.g. 'type: MyPipe`
|
||||||
|
@ -35,16 +40,29 @@ export function compilePipe(
|
||||||
const className = identifierName(pipe.type) !;
|
const className = identifierName(pipe.type) !;
|
||||||
className || error(`Cannot resolve the name of ${pipe.type}`);
|
className || error(`Cannot resolve the name of ${pipe.type}`);
|
||||||
|
|
||||||
outputCtx.statements.push(new o.ClassStmt(
|
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe);
|
||||||
/* name */ className,
|
const definitionFunction =
|
||||||
/* parent */ null,
|
o.importExpr(R3.definePipe).callFn([o.literalMap(definitionMapValues)]);
|
||||||
/* fields */[new o.ClassField(
|
|
||||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe),
|
if (mode === OutputMode.PartialClass) {
|
||||||
/* type */ o.INFERRED_TYPE,
|
outputCtx.statements.push(new o.ClassStmt(
|
||||||
/* modifiers */[o.StmtModifier.Static],
|
/* name */ className,
|
||||||
/* initializer */ o.importExpr(R3.definePipe).callFn([o.literalMap(
|
/* parent */ null,
|
||||||
definitionMapValues)]))],
|
/* fields */[new o.ClassField(
|
||||||
/* getters */[],
|
/* name */ definitionField,
|
||||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
/* type */ o.INFERRED_TYPE,
|
||||||
/* methods */[]));
|
/* modifiers */[o.StmtModifier.Static],
|
||||||
|
/* initializer */ definitionFunction)],
|
||||||
|
/* getters */[],
|
||||||
|
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||||
|
/* methods */[]));
|
||||||
|
} else {
|
||||||
|
// Create back-patch definition.
|
||||||
|
const classReference = outputCtx.importExpr(pipe.type.reference);
|
||||||
|
|
||||||
|
// Create the back-patch statement
|
||||||
|
outputCtx.statements.push(
|
||||||
|
new o.CommentStmt(BUILD_OPTIMIZER_COLOCATE),
|
||||||
|
classReference.prop(definitionField).set(definitionFunction).toStmt());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The statement mode for the render, either as a class back-patch or as a partial class
|
||||||
|
*/
|
||||||
|
export const enum OutputMode {
|
||||||
|
PartialClass,
|
||||||
|
BackPatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comment to insert above back-patch
|
||||||
|
*/
|
||||||
|
export const BUILD_OPTIMIZER_COLOCATE = '@__BUILD_OPTIMIZER_COLOCATE__';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comment to mark removable expressions
|
||||||
|
*/
|
||||||
|
export const BUILD_OPTIMIZER_REMOVE = '@__BUILD_OPTIMIZER_REMOVE__';
|
|
@ -21,6 +21,7 @@ import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventA
|
||||||
import {OutputContext, error} from '../util';
|
import {OutputContext, error} from '../util';
|
||||||
|
|
||||||
import {Identifiers as R3} from './r3_identifiers';
|
import {Identifiers as R3} from './r3_identifiers';
|
||||||
|
import {BUILD_OPTIMIZER_COLOCATE, OutputMode} from './r3_types';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ const IMPLICIT_REFERENCE = '$implicit';
|
||||||
|
|
||||||
export function compileDirective(
|
export function compileDirective(
|
||||||
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector,
|
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector,
|
||||||
bindingParser: BindingParser) {
|
bindingParser: BindingParser, mode: OutputMode) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
|
||||||
const field = (key: string, value: o.Expression | null) => {
|
const field = (key: string, value: o.Expression | null) => {
|
||||||
|
@ -68,24 +69,38 @@ export function compileDirective(
|
||||||
const className = identifierName(directive.type) !;
|
const className = identifierName(directive.type) !;
|
||||||
className || error(`Cannot resolver the name of ${directive.type}`);
|
className || error(`Cannot resolver the name of ${directive.type}`);
|
||||||
|
|
||||||
// Create the partial class to be merged with the actual class.
|
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive);
|
||||||
outputCtx.statements.push(new o.ClassStmt(
|
const definitionFunction =
|
||||||
/* name */ className,
|
o.importExpr(R3.defineDirective).callFn([o.literalMap(definitionMapValues)]);
|
||||||
/* parent */ null,
|
|
||||||
/* fields */[new o.ClassField(
|
if (mode === OutputMode.PartialClass) {
|
||||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive),
|
// Create the partial class to be merged with the actual class.
|
||||||
/* type */ o.INFERRED_TYPE,
|
outputCtx.statements.push(new o.ClassStmt(
|
||||||
/* modifiers */[o.StmtModifier.Static],
|
/* name */ className,
|
||||||
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
|
/* parent */ null,
|
||||||
definitionMapValues)]))],
|
/* fields */[new o.ClassField(
|
||||||
/* getters */[],
|
/* name */ definitionField,
|
||||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
/* type */ o.INFERRED_TYPE,
|
||||||
/* methods */[]));
|
/* modifiers */[o.StmtModifier.Static],
|
||||||
|
/* initializer */ definitionFunction)],
|
||||||
|
/* getters */[],
|
||||||
|
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||||
|
/* methods */[]));
|
||||||
|
} else {
|
||||||
|
// Create back-patch definition.
|
||||||
|
const classReference = outputCtx.importExpr(directive.type.reference);
|
||||||
|
|
||||||
|
// Create the back-patch statement
|
||||||
|
outputCtx.statements.push(new o.CommentStmt(BUILD_OPTIMIZER_COLOCATE));
|
||||||
|
outputCtx.statements.push(
|
||||||
|
classReference.prop(definitionField).set(definitionFunction).toStmt());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compileComponent(
|
export function compileComponent(
|
||||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
||||||
template: TemplateAst[], reflector: CompileReflector, bindingParser: BindingParser) {
|
template: TemplateAst[], reflector: CompileReflector, bindingParser: BindingParser,
|
||||||
|
mode: OutputMode) {
|
||||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||||
|
|
||||||
const field = (key: string, value: o.Expression | null) => {
|
const field = (key: string, value: o.Expression | null) => {
|
||||||
|
@ -150,22 +165,33 @@ export function compileComponent(
|
||||||
field('features', o.literalArr(features));
|
field('features', o.literalArr(features));
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = identifierName(component.type) !;
|
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component);
|
||||||
className || error(`Cannot resolver the name of ${component.type}`);
|
const definitionFunction =
|
||||||
|
o.importExpr(R3.defineComponent).callFn([o.literalMap(definitionMapValues)]);
|
||||||
|
if (mode === OutputMode.PartialClass) {
|
||||||
|
const className = identifierName(component.type) !;
|
||||||
|
className || error(`Cannot resolver the name of ${component.type}`);
|
||||||
|
|
||||||
// Create the partial class to be merged with the actual class.
|
// Create the partial class to be merged with the actual class.
|
||||||
outputCtx.statements.push(new o.ClassStmt(
|
outputCtx.statements.push(new o.ClassStmt(
|
||||||
/* name */ className,
|
/* name */ className,
|
||||||
/* parent */ null,
|
/* parent */ null,
|
||||||
/* fields */[new o.ClassField(
|
/* fields */[new o.ClassField(
|
||||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Component),
|
/* name */ definitionField,
|
||||||
/* type */ o.INFERRED_TYPE,
|
/* type */ o.INFERRED_TYPE,
|
||||||
/* modifiers */[o.StmtModifier.Static],
|
/* modifiers */[o.StmtModifier.Static],
|
||||||
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
|
/* initializer */ definitionFunction)],
|
||||||
definitionMapValues)]))],
|
/* getters */[],
|
||||||
/* getters */[],
|
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
/* methods */[]));
|
||||||
/* methods */[]));
|
} else {
|
||||||
|
const classReference = outputCtx.importExpr(component.type.reference);
|
||||||
|
|
||||||
|
// Create the back-patch statement
|
||||||
|
outputCtx.statements.push(
|
||||||
|
new o.CommentStmt(BUILD_OPTIMIZER_COLOCATE),
|
||||||
|
classReference.prop(definitionField).set(definitionFunction).toStmt());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove these when the things are fully supported
|
// TODO: Remove these when the things are fully supported
|
||||||
|
|
|
@ -55,6 +55,7 @@ export const settings: ts.CompilerOptions = {
|
||||||
export interface EmitterOptions {
|
export interface EmitterOptions {
|
||||||
emitMetadata: boolean;
|
emitMetadata: boolean;
|
||||||
mockData?: MockDirectory;
|
mockData?: MockDirectory;
|
||||||
|
context?: Map<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcPathsOnDisc() {
|
function calcPathsOnDisc() {
|
||||||
|
@ -74,12 +75,16 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
private scriptNames: string[];
|
private scriptNames: string[];
|
||||||
private root = '/';
|
private root = '/';
|
||||||
private collector = new MetadataCollector();
|
private collector = new MetadataCollector();
|
||||||
|
private cachedAddedDirectories: Set<string>|undefined;
|
||||||
|
|
||||||
constructor(scriptNames: string[], private options: EmitterOptions) {
|
constructor(scriptNames: string[], private options: EmitterOptions) {
|
||||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||||
// the source tree.
|
// the source tree.
|
||||||
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
|
||||||
this.root = rootPath;
|
this.root = rootPath;
|
||||||
|
if (options.context) {
|
||||||
|
this.addedFiles = mergeMaps(options.context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public writtenAngularFiles(target = new Map<string, string>()): Map<string, string> {
|
public writtenAngularFiles(target = new Map<string, string>()): Map<string, string> {
|
||||||
|
@ -93,12 +98,14 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
public addScript(fileName: string, content: string) {
|
public addScript(fileName: string, content: string) {
|
||||||
const scriptName = this.effectiveName(fileName);
|
const scriptName = this.effectiveName(fileName);
|
||||||
this.addedFiles.set(scriptName, content);
|
this.addedFiles.set(scriptName, content);
|
||||||
|
this.cachedAddedDirectories = undefined;
|
||||||
this.scriptNames.push(scriptName);
|
this.scriptNames.push(scriptName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override(fileName: string, content: string) {
|
public override(fileName: string, content: string) {
|
||||||
const scriptName = this.effectiveName(fileName);
|
const scriptName = this.effectiveName(fileName);
|
||||||
this.addedFiles.set(scriptName, content);
|
this.addedFiles.set(scriptName, content);
|
||||||
|
this.cachedAddedDirectories = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addWrittenFile(fileName: string, content: string) {
|
public addWrittenFile(fileName: string, content: string) {
|
||||||
|
@ -140,6 +147,7 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
|
|
||||||
directoryExists(directoryName: string): boolean {
|
directoryExists(directoryName: string): boolean {
|
||||||
return directoryExists(directoryName, this.options.mockData) ||
|
return directoryExists(directoryName, this.options.mockData) ||
|
||||||
|
this.getAddedDirectories().has(directoryName) ||
|
||||||
(fs.existsSync(directoryName) && fs.statSync(directoryName).isDirectory());
|
(fs.existsSync(directoryName) && fs.statSync(directoryName).isDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +196,23 @@ export class EmittingCompilerHost implements ts.CompilerHost {
|
||||||
}
|
}
|
||||||
useCaseSensitiveFileNames(): boolean { return false; }
|
useCaseSensitiveFileNames(): boolean { return false; }
|
||||||
getNewLine(): string { return '\n'; }
|
getNewLine(): string { return '\n'; }
|
||||||
|
|
||||||
|
private getAddedDirectories(): Set<string> {
|
||||||
|
let result = this.cachedAddedDirectories;
|
||||||
|
if (!result) {
|
||||||
|
const newCache = new Set<string>();
|
||||||
|
const addFile = (fileName: string) => {
|
||||||
|
const directory = fileName.substr(0, fileName.lastIndexOf('/'));
|
||||||
|
if (!newCache.has(directory)) {
|
||||||
|
newCache.add(directory);
|
||||||
|
addFile(directory);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Array.from(this.addedFiles.keys()).forEach(addFile);
|
||||||
|
this.cachedAddedDirectories = result = newCache;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockCompilerHost implements ts.CompilerHost {
|
export class MockCompilerHost implements ts.CompilerHost {
|
||||||
|
@ -703,3 +728,43 @@ function stripNgResourceSuffix(fileName: string): string {
|
||||||
function addNgResourceSuffix(fileName: string): string {
|
function addNgResourceSuffix(fileName: string): string {
|
||||||
return `${fileName}.$ngresource$`;
|
return `${fileName}.$ngresource$`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractFileNames(directory: MockDirectory): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
const scan = (directory: MockDirectory, prefix: string) => {
|
||||||
|
for (let name of Object.getOwnPropertyNames(directory)) {
|
||||||
|
const entry = directory[name];
|
||||||
|
const fileName = `${prefix}/${name}`;
|
||||||
|
if (typeof entry === 'string') {
|
||||||
|
result.push(fileName);
|
||||||
|
} else if (entry) {
|
||||||
|
scan(entry, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
scan(directory, '');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emitLibrary(
|
||||||
|
context: Map<string, string>, mockData: MockDirectory,
|
||||||
|
scriptFiles?: string[]): Map<string, string> {
|
||||||
|
const emittingHost = new EmittingCompilerHost(
|
||||||
|
scriptFiles || extractFileNames(mockData), {emitMetadata: true, mockData, context});
|
||||||
|
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
||||||
|
expectNoDiagnostics(emittingProgram);
|
||||||
|
emittingProgram.emit();
|
||||||
|
return emittingHost.written;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeMaps<K, V>(...maps: Map<K, V>[]): Map<K, V> {
|
||||||
|
const result = new Map<K, V>();
|
||||||
|
|
||||||
|
for (const map of maps) {
|
||||||
|
for (const [key, value] of Array.from(map.entries())) {
|
||||||
|
result.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -6,14 +6,18 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, ParseError, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileMetadataResolver, CompileNgModuleMetadata, CompilePipeSummary, CompileTypeMetadata, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, ParseError, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver, templateSourceUrl} from '@angular/compiler';
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
import {ViewEncapsulation} from '@angular/core';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {NgAnalyzedModules} from '../../src/aot/compiler';
|
||||||
import {ConstantPool} from '../../src/constant_pool';
|
import {ConstantPool} from '../../src/constant_pool';
|
||||||
import {ParserError} from '../../src/expression_parser/ast';
|
import {ParserError} from '../../src/expression_parser/ast';
|
||||||
import * as o from '../../src/output/output_ast';
|
import * as o from '../../src/output/output_ast';
|
||||||
|
import {ModuleKind, compileModuleBackPatch} from '../../src/render3/r3_back_patch_compiler';
|
||||||
|
import {compileModuleFactory} from '../../src/render3/r3_module_factory_compiler';
|
||||||
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
|
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
|
||||||
|
import {OutputMode} from '../../src/render3/r3_types';
|
||||||
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
||||||
import {BindingParser} from '../../src/template_parser/binding_parser';
|
import {BindingParser} from '../../src/template_parser/binding_parser';
|
||||||
import {OutputContext} from '../../src/util';
|
import {OutputContext} from '../../src/util';
|
||||||
|
@ -122,9 +126,13 @@ function r(...pieces: (string | RegExp)[]): RegExp {
|
||||||
return new RegExp(results.join(''));
|
return new RegExp(results.join(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile(
|
function doCompile(
|
||||||
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error;}) {
|
errorCollector: (error: any, fileName?: string) => void = error => { throw error; },
|
||||||
|
compileAction: (
|
||||||
|
outputCtx: OutputContext, analyzedModules: NgAnalyzedModules,
|
||||||
|
resolver: CompileMetadataResolver, htmlParser: HtmlParser, templateParser: TemplateParser,
|
||||||
|
hostBindingParser: BindingParser, reflector: StaticReflector) => void) {
|
||||||
const testFiles = toMockFileArray(data);
|
const testFiles = toMockFileArray(data);
|
||||||
const scripts = testFiles.map(entry => entry.fileName);
|
const scripts = testFiles.map(entry => entry.fileName);
|
||||||
const angularFilesArray = toMockFileArray(angularFiles);
|
const angularFilesArray = toMockFileArray(angularFiles);
|
||||||
|
@ -207,37 +215,9 @@ export function compile(
|
||||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the directives.
|
compileAction(
|
||||||
for (const pipeOrDirective of pipesOrDirectives) {
|
fakeOutputContext, analyzedModules, resolver, htmlParser, templateParser, hostBindingParser,
|
||||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
|
staticReflector);
|
||||||
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (resolver.isDirective(pipeOrDirective)) {
|
|
||||||
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
|
|
||||||
if (metadata.isComponent) {
|
|
||||||
const fakeUrl = 'ng://fake-template-url.html';
|
|
||||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
|
||||||
|
|
||||||
const directives = module.transitiveModule.directives.map(
|
|
||||||
dir => resolver.getDirectiveSummary(dir.reference));
|
|
||||||
const pipes =
|
|
||||||
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
|
||||||
const parsedTemplate = templateParser.parse(
|
|
||||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
|
||||||
compileComponent(
|
|
||||||
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector,
|
|
||||||
hostBindingParser);
|
|
||||||
} else {
|
|
||||||
compileDirective(fakeOutputContext, metadata, staticReflector, hostBindingParser);
|
|
||||||
}
|
|
||||||
} else if (resolver.isPipe(pipeOrDirective)) {
|
|
||||||
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
|
||||||
if (metadata) {
|
|
||||||
compilePipe(fakeOutputContext, metadata, staticReflector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeOutputContext.statements.unshift(...fakeOutputContext.constantPool.statements);
|
fakeOutputContext.statements.unshift(...fakeOutputContext.constantPool.statements);
|
||||||
|
|
||||||
|
@ -257,3 +237,109 @@ export function compile(
|
||||||
|
|
||||||
return {source: result.sourceText, outputContext: fakeOutputContext};
|
return {source: result.sourceText, outputContext: fakeOutputContext};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function compile(
|
||||||
|
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||||
|
errorCollector: (error: any, fileName?: string) => void = error => { throw error;}) {
|
||||||
|
return doCompile(
|
||||||
|
data, angularFiles, options, errorCollector,
|
||||||
|
(outputCtx: OutputContext, analyzedModules: NgAnalyzedModules,
|
||||||
|
resolver: CompileMetadataResolver, htmlParser: HtmlParser, templateParser: TemplateParser,
|
||||||
|
hostBindingParser: BindingParser, reflector: StaticReflector) => {
|
||||||
|
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||||
|
for (const pipeOrDirective of pipesOrDirectives) {
|
||||||
|
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
|
||||||
|
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (resolver.isDirective(pipeOrDirective)) {
|
||||||
|
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
|
||||||
|
if (metadata.isComponent) {
|
||||||
|
const fakeUrl = 'ng://fake-template-url.html';
|
||||||
|
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
||||||
|
|
||||||
|
const directives = module.transitiveModule.directives.map(
|
||||||
|
dir => resolver.getDirectiveSummary(dir.reference));
|
||||||
|
const pipes = module.transitiveModule.pipes.map(
|
||||||
|
pipe => resolver.getPipeSummary(pipe.reference));
|
||||||
|
const parsedTemplate = templateParser.parse(
|
||||||
|
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||||
|
compileComponent(
|
||||||
|
outputCtx, metadata, pipes, parsedTemplate.template, reflector, hostBindingParser,
|
||||||
|
OutputMode.PartialClass);
|
||||||
|
} else {
|
||||||
|
compileDirective(
|
||||||
|
outputCtx, metadata, reflector, hostBindingParser, OutputMode.PartialClass);
|
||||||
|
}
|
||||||
|
} else if (resolver.isPipe(pipeOrDirective)) {
|
||||||
|
const metadata = resolver.getPipeMetadata(pipeOrDirective);
|
||||||
|
if (metadata) {
|
||||||
|
compilePipe(outputCtx, metadata, reflector, OutputMode.PartialClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const DTS = /\.d\.ts$/;
|
||||||
|
const EXT = /(\.\w+)+$/;
|
||||||
|
const NONE_WORD = /\W/g;
|
||||||
|
const NODE_MODULES = /^.*\/node_modules\//;
|
||||||
|
|
||||||
|
function getBackPatchFunctionName(type: CompileTypeMetadata) {
|
||||||
|
const filePath = (type.reference.filePath as string)
|
||||||
|
.replace(EXT, '')
|
||||||
|
.replace(NODE_MODULES, '')
|
||||||
|
.replace(NONE_WORD, '_');
|
||||||
|
return `ngBackPatch_${filePath.split('/').filter(s => !!s).join('_')}_${type.reference.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBackPatchReference(type: CompileTypeMetadata): o.Expression {
|
||||||
|
return o.variable(getBackPatchFunctionName(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function backPatch(
|
||||||
|
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||||
|
errorCollector: (error: any, fileName?: string) => void = error => { throw error;}) {
|
||||||
|
return doCompile(
|
||||||
|
data, angularFiles, options, errorCollector,
|
||||||
|
(outputCtx: OutputContext, analyzedModules: NgAnalyzedModules,
|
||||||
|
resolver: CompileMetadataResolver, htmlParser: HtmlParser, templateParser: TemplateParser,
|
||||||
|
hostBindingParser: BindingParser, reflector: StaticReflector) => {
|
||||||
|
|
||||||
|
const parseTemplate =
|
||||||
|
(compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
|
||||||
|
directiveIdentifiers: CompileIdentifierMetadata[]) => {
|
||||||
|
const directives =
|
||||||
|
directiveIdentifiers.map(dir => resolver.getDirectiveSummary(dir.reference));
|
||||||
|
const pipes = ngModule.transitiveModule.pipes.map(
|
||||||
|
pipe => resolver.getPipeSummary(pipe.reference));
|
||||||
|
return templateParser.parse(
|
||||||
|
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
|
||||||
|
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const module of analyzedModules.ngModules) {
|
||||||
|
compileModuleBackPatch(
|
||||||
|
outputCtx, getBackPatchFunctionName(module.type), module,
|
||||||
|
DTS.test(module.type.reference.filePath) ? ModuleKind.Renderer2 :
|
||||||
|
ModuleKind.Renderer3,
|
||||||
|
getBackPatchReference, parseTemplate, reflector, resolver);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFactories(
|
||||||
|
data: MockDirectory, context: MockData, options: AotCompilerOptions = {},
|
||||||
|
errorCollector: (error: any, fileName?: string) => void = error => { throw error;}) {
|
||||||
|
return doCompile(
|
||||||
|
data, context, options, errorCollector,
|
||||||
|
(outputCtx: OutputContext, analyzedModules: NgAnalyzedModules,
|
||||||
|
resolver: CompileMetadataResolver, htmlParser: HtmlParser, templateParser: TemplateParser,
|
||||||
|
hostBindingParser: BindingParser, reflector: StaticReflector) => {
|
||||||
|
for (const module of analyzedModules.ngModules) {
|
||||||
|
compileModuleFactory(outputCtx, module, getBackPatchReference, resolver);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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, emitLibrary, mergeMaps, setup} from '../aot/test_util';
|
||||||
|
|
||||||
|
import {backPatch, expectEmit} from './mock_compile';
|
||||||
|
|
||||||
|
describe('r3_back_patch_compiler', () => {
|
||||||
|
const angularFiles = setup({
|
||||||
|
compileAngular: true,
|
||||||
|
compileAnimations: false,
|
||||||
|
compileCommon: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should back-patch a component in a library', () => {
|
||||||
|
const libraries = {
|
||||||
|
lib1: {
|
||||||
|
src: {
|
||||||
|
'component.ts': `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'lib1-cmp',
|
||||||
|
template: '<h1> Hello, {{name}}!</h1>'
|
||||||
|
})
|
||||||
|
export class Lib1Component {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'directive.ts': `
|
||||||
|
import {Directive, HostBinding} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[lib1-dir]'})
|
||||||
|
export class Lib1Directive {
|
||||||
|
@HostBinding('id') dirId = 'some id';
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'service.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Lib1Service {
|
||||||
|
getSomeInfo() { return 'some info'; }
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'pipe.ts': `
|
||||||
|
import {Pipe} from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({name: 'lib1Pipe', pure: true})
|
||||||
|
export class Lib1Pipe {
|
||||||
|
transform(v: any) { return v; }
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'module.ts': `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
import {Lib1Component} from './component';
|
||||||
|
import {Lib1Directive} from './directive';
|
||||||
|
import {Lib1Service} from './service';
|
||||||
|
import {Lib1Pipe} from './pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [Lib1Component, Lib1Directive, Lib1Pipe],
|
||||||
|
declarations: [Lib1Component, Lib1Directive, Lib1Pipe],
|
||||||
|
providers: [Lib1Service]
|
||||||
|
})
|
||||||
|
export class Lib1Module {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lib2: {
|
||||||
|
src: {
|
||||||
|
'component.ts': `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'lib2-cmp',
|
||||||
|
template: '<h1> Hello, {{name}}!</h1>'
|
||||||
|
})
|
||||||
|
export class Lib2Component {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'directive.ts': `
|
||||||
|
import {Directive, HostBinding} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[lib2-dir]'})
|
||||||
|
export class Lib2Directive {
|
||||||
|
@HostBinding('id') dirId = 'some id';
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'service.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Lib2Service {
|
||||||
|
getSomeInfo() { return 'some info'; }
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'pipe.ts': `
|
||||||
|
import {Pipe} from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({name: 'lib2Pipe', pure: true})
|
||||||
|
export class Lib2Pipe {
|
||||||
|
transform(v: any) { return v; }
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'module.ts': `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
import {Lib1Module} from '../../lib1/src/module';
|
||||||
|
import {Lib2Component} from './component';
|
||||||
|
import {Lib2Directive} from './directive';
|
||||||
|
import {Lib2Service} from './service';
|
||||||
|
import {Lib2Pipe} from './pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [Lib1Module],
|
||||||
|
exports: [Lib2Component, Lib2Directive, Lib2Pipe],
|
||||||
|
declarations: [Lib2Component, Lib2Directive, Lib2Pipe],
|
||||||
|
providers: [Lib2Service]
|
||||||
|
})
|
||||||
|
export class Lib2Module {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const app = {
|
||||||
|
app: {
|
||||||
|
src: {
|
||||||
|
'app.component.ts': `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {Lib1Service} from '../../lib1/src/service';
|
||||||
|
import {Lib2Service} from '../../lib2/src/service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cmp',
|
||||||
|
template: \`
|
||||||
|
<lib1-cmp lib2-dir>{{'v' | lib1Pipe | lib2Pipe}}</lib1-cmp>
|
||||||
|
<lib2-cmp lib1-dir>{{'v' | lib2Pipe | lib2Pipe}}</lib2-cmp>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
constructor(public lib1s: Lib1Service, public lib2s: Lib2Service) {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'app.module.ts': `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {Lib1Module} from '../../lib1/src/module';
|
||||||
|
import {Lib2Module} from '../../lib2/src/module';
|
||||||
|
|
||||||
|
import {AppComponent} from './app.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [Lib1Module, Lib2Module],
|
||||||
|
declarations: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const lib1_module_back_patch = `
|
||||||
|
export function ngBackPatch__lib1_src_module_Lib1Module() {
|
||||||
|
// @__BUILD_OPTIMIZER_COLOCATE__
|
||||||
|
$lib1_c$.Lib1Component.ngComponentDef = $r3$.ɵdefineComponent(…);
|
||||||
|
|
||||||
|
// @__BUILD_OPTIMIZER_COLOCATE__
|
||||||
|
$lib1_d$.Lib1Directive.ngDirectiveDef = $r3$.ɵdefineDirective(…);
|
||||||
|
|
||||||
|
// @__BUILD_OPTIMIZER_COLOCATE__
|
||||||
|
$lib1_p$.Lib1Pipe.ngPipeDef = $r3$.ɵdefinePipe(…);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const lib2_module_back_patch = `
|
||||||
|
export function ngBackPatch__lib2_src_module_Lib2Module() {
|
||||||
|
// @__BUILD_OPTIMIZER_REMOVE__
|
||||||
|
ngBackPatch__lib1_src_module_Lib1Module();
|
||||||
|
|
||||||
|
// @__BUILD_OPTIMIZER_COLOCATE__
|
||||||
|
$lib2_c$.Lib2Component.ngComponentDef = $r3$.ɵdefineComponent(…);
|
||||||
|
|
||||||
|
// @__BUILD_OPTIMIZER_COLOCATE__
|
||||||
|
$lib2_d$.Lib2Directive.ngDirectiveDef = $r3$.ɵdefineDirective(…);
|
||||||
|
|
||||||
|
// @__BUILD_OPTIMIZER_COLOCATE__
|
||||||
|
$lib1_p$.Lib2Pipe.ngPipeDef = $r3$.ɵdefinePipe(…);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const app_module_back_patch = `
|
||||||
|
export function ngBackPatch__app_src_app_AppModule() {
|
||||||
|
// @__BUILD_OPTIMIZER_REMOVE__
|
||||||
|
ngBackPatch__lib1_src_module_Lib1Module();
|
||||||
|
// @__BUILD_OPTIMIZER_REMOVE__
|
||||||
|
ngBackPatch__lib2_src_module_Lib2Module();
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const context = mergeMaps(emitLibrary(angularFiles, libraries), angularFiles);
|
||||||
|
|
||||||
|
const result = backPatch(app, context);
|
||||||
|
|
||||||
|
expectEmit(result.source, lib1_module_back_patch, 'Invalid lib1 back-patch');
|
||||||
|
expectEmit(result.source, lib2_module_back_patch, 'Invalid lib2 back-patch');
|
||||||
|
expectEmit(result.source, app_module_back_patch, 'Invalid app module back-patch');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,202 @@
|
||||||
|
/**
|
||||||
|
* @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, emitLibrary, mergeMaps, setup} from '../aot/test_util';
|
||||||
|
import {createFactories, expectEmit} from './mock_compile';
|
||||||
|
|
||||||
|
describe('r3_factory_compiler', () => {
|
||||||
|
const angularFiles = setup({
|
||||||
|
compileAngular: true,
|
||||||
|
compileAnimations: false,
|
||||||
|
compileCommon: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate factories for all modules', () => {
|
||||||
|
const libraries = {
|
||||||
|
lib1: {
|
||||||
|
src: {
|
||||||
|
'component.ts': `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'lib1-cmp',
|
||||||
|
template: '<h1> Hello, {{name}}!</h1>'
|
||||||
|
})
|
||||||
|
export class Lib1Component {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'directive.ts': `
|
||||||
|
import {Directive, HostBinding} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[lib1-dir]'})
|
||||||
|
export class Lib1Directive {
|
||||||
|
@HostBinding('id') dirId = 'some id';
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'service.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Lib1Service {
|
||||||
|
getSomeInfo() { return 'some info'; }
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'module.ts': `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
import {Lib1Component} from './component';
|
||||||
|
import {Lib1Directive} from './directive';
|
||||||
|
import {Lib1Service} from './service';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
exports: [Lib1Component, Lib1Directive],
|
||||||
|
declarations: [Lib1Component, Lib1Directive],
|
||||||
|
providers: [Lib1Service]
|
||||||
|
})
|
||||||
|
export class Lib1Module {}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lib2: {
|
||||||
|
src: {
|
||||||
|
'component.ts': `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'lib2-cmp',
|
||||||
|
template: '<h1> Hello, {{name}}!</h1>'
|
||||||
|
})
|
||||||
|
export class Lib2Component {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'directive.ts': `
|
||||||
|
import {Directive, HostBinding} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[lib2-dir]'})
|
||||||
|
export class Lib2Directive {
|
||||||
|
@HostBinding('id') dirId = 'some id';
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'service.ts': `
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class Lib2Service {
|
||||||
|
getSomeInfo() { return 'some info'; }
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'module.ts': `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
import {Lib1Module} from '../../lib1/src/module';
|
||||||
|
import {Lib2Component} from './component';
|
||||||
|
import {Lib2Directive} from './directive';
|
||||||
|
import {Lib2Service} from './service';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [Lib1Module],
|
||||||
|
exports: [Lib2Component, Lib2Directive],
|
||||||
|
declarations: [Lib2Component, Lib2Directive],
|
||||||
|
providers: [Lib2Service]
|
||||||
|
})
|
||||||
|
export class Lib2Module {}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = {
|
||||||
|
app: {
|
||||||
|
src: {
|
||||||
|
'app.component.ts': `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {Lib1Service} from '../../lib1/src/service';
|
||||||
|
import {Lib2Service} from '../../lib2/src/service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cmp',
|
||||||
|
template: \`
|
||||||
|
<lib1-cmp lib2-dir></lib1-cmp>
|
||||||
|
<lib2-cmp lib1-dir></lib2-cmp>
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
constructor(public lib1s: Lib1Service, public lib2s: Lib2Service) {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'app.module.ts': `
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {Lib1Module} from '../../lib1/src/module';
|
||||||
|
import {Lib2Module} from '../../lib2/src/module';
|
||||||
|
|
||||||
|
import {AppComponent} from './app.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [Lib1Module, Lib2Module],
|
||||||
|
declarations: [AppComponent],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const lib1_module_factory = `
|
||||||
|
export const Lib1ModuleNgFactory: $any$ = {
|
||||||
|
moduleType: $i1$.Lib1Module,
|
||||||
|
create: function Lib1ModuleNgFactory_Create(parentInjector: $any$) {
|
||||||
|
if ((this.patchedDeps !== true)) {
|
||||||
|
this.patchedDeps = true;
|
||||||
|
ngBackPatch__lib1_src_module_Lib1Module();
|
||||||
|
}
|
||||||
|
…
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const lib2_module_factory = `
|
||||||
|
export const Lib2ModuleNgFactory: $any$ = {
|
||||||
|
moduleType: $i2$.Lib2Module,
|
||||||
|
create: function Lib2ModuleNgFactory_Create(parentInjector: $any$) {
|
||||||
|
if ((this.patchedDeps !== true)) {
|
||||||
|
this.patchedDeps = true;
|
||||||
|
ngBackPatch__lib2_src_module_Lib2Module();
|
||||||
|
}
|
||||||
|
…
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
// TODO(chuckj): What should we do with the bootstrap components?
|
||||||
|
const app_module_factory = `
|
||||||
|
export const AppModuleNgFactory: $any$ = {
|
||||||
|
moduleType: AppModule,
|
||||||
|
create: function AppModuleNgFactory_Create(parentInjector: $any$) {
|
||||||
|
if ((this.patchedDeps !== true)) {
|
||||||
|
this.patchedDeps = true;
|
||||||
|
ngBackPatch__app_src_app_AppModule();
|
||||||
|
}
|
||||||
|
…
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const context = mergeMaps(emitLibrary(angularFiles, libraries), angularFiles);
|
||||||
|
|
||||||
|
const result = createFactories(app, context);
|
||||||
|
|
||||||
|
expectEmit(result.source, lib1_module_factory, 'Invalid module factory for lib1');
|
||||||
|
expectEmit(result.source, lib2_module_factory, 'Invalid module factory for lib2');
|
||||||
|
expectEmit(result.source, app_module_factory, 'Invalid module factory for app');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue