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); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										106
									
								
								packages/compiler/src/render3/r3_back_patch_compiler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								packages/compiler/src/render3/r3_back_patch_compiler.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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}; | ||||||
|  | |||||||
							
								
								
									
										46
									
								
								packages/compiler/src/render3/r3_module_factory_compiler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								packages/compiler/src/render3/r3_module_factory_compiler.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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}`); | ||||||
| 
 | 
 | ||||||
|  |   const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe); | ||||||
|  |   const definitionFunction = | ||||||
|  |       o.importExpr(R3.definePipe).callFn([o.literalMap(definitionMapValues)]); | ||||||
|  | 
 | ||||||
|  |   if (mode === OutputMode.PartialClass) { | ||||||
|     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.Pipe), |             /* name */ definitionField, | ||||||
|             /* type */ o.INFERRED_TYPE, |             /* type */ o.INFERRED_TYPE, | ||||||
|             /* modifiers */[o.StmtModifier.Static], |             /* modifiers */[o.StmtModifier.Static], | ||||||
|           /* initializer */ o.importExpr(R3.definePipe).callFn([o.literalMap( |             /* initializer */ definitionFunction)], | ||||||
|               definitionMapValues)]))], |  | ||||||
|         /* getters */[], |         /* getters */[], | ||||||
|         /* constructorMethod */ new o.ClassMethod(null, [], []), |         /* constructorMethod */ new o.ClassMethod(null, [], []), | ||||||
|         /* methods */[])); |         /* 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()); | ||||||
|  |   } | ||||||
| } | } | ||||||
							
								
								
									
										25
									
								
								packages/compiler/src/render3/r3_types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								packages/compiler/src/render3/r3_types.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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}`); | ||||||
| 
 | 
 | ||||||
|  |   const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive); | ||||||
|  |   const definitionFunction = | ||||||
|  |       o.importExpr(R3.defineDirective).callFn([o.literalMap(definitionMapValues)]); | ||||||
|  | 
 | ||||||
|  |   if (mode === OutputMode.PartialClass) { | ||||||
|     // 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.Directive), |             /* name */ definitionField, | ||||||
|             /* type */ o.INFERRED_TYPE, |             /* type */ o.INFERRED_TYPE, | ||||||
|             /* modifiers */[o.StmtModifier.Static], |             /* modifiers */[o.StmtModifier.Static], | ||||||
|           /* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap( |             /* initializer */ definitionFunction)], | ||||||
|               definitionMapValues)]))], |  | ||||||
|         /* getters */[], |         /* getters */[], | ||||||
|         /* constructorMethod */ new o.ClassMethod(null, [], []), |         /* constructorMethod */ new o.ClassMethod(null, [], []), | ||||||
|         /* methods */[])); |         /* 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,6 +165,10 @@ export function compileComponent( | |||||||
|     field('features', o.literalArr(features)); |     field('features', o.literalArr(features)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component); | ||||||
|  |   const definitionFunction = | ||||||
|  |       o.importExpr(R3.defineComponent).callFn([o.literalMap(definitionMapValues)]); | ||||||
|  |   if (mode === OutputMode.PartialClass) { | ||||||
|     const className = identifierName(component.type) !; |     const className = identifierName(component.type) !; | ||||||
|     className || error(`Cannot resolver the name of ${component.type}`); |     className || error(`Cannot resolver the name of ${component.type}`); | ||||||
| 
 | 
 | ||||||
| @ -158,14 +177,21 @@ export function compileComponent( | |||||||
|         /* 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); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  | } | ||||||
							
								
								
									
										218
									
								
								packages/compiler/test/render3/r3_back_patch_compiler_spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								packages/compiler/test/render3/r3_back_patch_compiler_spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user