2016-06-23 12:47:54 -04:00
/ * *
* @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
* /
2016-12-27 12:36:47 -05:00
import { StaticSymbol , StaticSymbolCache } from './aot/static_symbol' ;
import { ngfactoryFilePath } from './aot/util' ;
2016-06-20 12:52:41 -04:00
import { assertArrayOfStrings , assertInterpolationSymbols } from './assertions' ;
2016-01-06 17:13:44 -05:00
import * as cpl from './compile_metadata' ;
2017-05-18 16:46:51 -04:00
import { CompileReflector } from './compile_reflector' ;
2017-02-17 11:56:36 -05:00
import { CompilerConfig } from './config' ;
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
import { ChangeDetectionStrategy , Component , Directive , Injectable , ModuleWithProviders , Provider , Query , SchemaMetadata , Type , ViewEncapsulation , createAttribute , createComponent , createHost , createInject , createInjectable , createInjectionToken , createOptional , createSelf , createSkipSelf } from './core' ;
2016-11-10 17:07:30 -05:00
import { DirectiveNormalizer } from './directive_normalizer' ;
2016-01-06 17:13:44 -05:00
import { DirectiveResolver } from './directive_resolver' ;
2017-05-18 16:46:51 -04:00
import { Identifiers } from './identifiers' ;
import { getAllLifecycleHooks } from './lifecycle_reflector' ;
2017-09-12 12:40:28 -04:00
import { HtmlParser } from './ml_parser/html_parser' ;
2016-07-18 06:50:31 -04:00
import { NgModuleResolver } from './ng_module_resolver' ;
2016-01-06 17:13:44 -05:00
import { PipeResolver } from './pipe_resolver' ;
2016-07-28 13:39:10 -04:00
import { ElementSchemaRegistry } from './schema/element_schema_registry' ;
2017-09-12 12:40:28 -04:00
import { CssSelector } from './selector' ;
2016-11-29 18:36:33 -05:00
import { SummaryResolver } from './summary_resolver' ;
2017-08-16 12:00:03 -04:00
import { Console , SyncAsync , ValueTransformer , isPromise , noUndefined , resolveForwardRef , stringify , syntaxError , visitValue } from './util' ;
2016-04-28 20:50:03 -04:00
2016-12-06 20:11:09 -05:00
export type ErrorCollector = ( error : any , type ? : any ) = > void ;
2017-08-16 12:00:03 -04:00
export const ERROR_COMPONENT_TYPE = 'ngComponentType' ;
2016-11-10 19:27:53 -05:00
2016-11-10 17:07:30 -05:00
// Design notes:
// - don't lazily create metadata:
// For some metadata, we need to do async work sometimes,
// so the user has to kick off this loading.
// But we want to report errors even when the async work is
// not required to check that the user would have been able
// to wait correctly.
2016-02-18 13:53:21 -05:00
export class CompileMetadataResolver {
2016-12-27 12:36:47 -05:00
private _nonNormalizedDirectiveCache =
2017-08-16 12:00:03 -04:00
new Map < Type , { annotation : Directive , metadata : cpl.CompileDirectiveMetadata } > ( ) ;
private _directiveCache = new Map < Type , cpl.CompileDirectiveMetadata > ( ) ;
private _summaryCache = new Map < Type , cpl.CompileTypeSummary | null > ( ) ;
private _pipeCache = new Map < Type , cpl.CompilePipeMetadata > ( ) ;
private _ngModuleCache = new Map < Type , cpl.CompileNgModuleMetadata > ( ) ;
private _ngModuleOfTypes = new Map < Type , Type > ( ) ;
2015-09-14 18:59:09 -04:00
2016-06-08 19:38:52 -04:00
constructor (
2017-09-12 12:40:28 -04:00
private _config : CompilerConfig , private _htmlParser : HtmlParser ,
private _ngModuleResolver : NgModuleResolver , private _directiveResolver : DirectiveResolver ,
private _pipeResolver : PipeResolver , private _summaryResolver : SummaryResolver < any > ,
2016-11-29 18:36:33 -05:00
private _schemaRegistry : ElementSchemaRegistry ,
2017-03-13 17:39:34 -04:00
private _directiveNormalizer : DirectiveNormalizer , private _console : Console ,
2017-08-16 12:00:03 -04:00
private _staticSymbolCache : StaticSymbolCache , private _reflector : CompileReflector ,
private _errorCollector? : ErrorCollector ) { }
2015-09-14 18:59:09 -04:00
2017-05-18 16:46:51 -04:00
getReflector ( ) : CompileReflector { return this . _reflector ; }
2017-08-16 12:00:03 -04:00
clearCacheFor ( type : Type ) {
2016-11-10 17:07:30 -05:00
const dirMeta = this . _directiveCache . get ( type ) ;
2016-06-28 12:54:42 -04:00
this . _directiveCache . delete ( type ) ;
2016-12-27 12:36:47 -05:00
this . _nonNormalizedDirectiveCache . delete ( type ) ;
2016-12-02 13:08:46 -05:00
this . _summaryCache . delete ( type ) ;
2016-06-28 12:54:42 -04:00
this . _pipeCache . delete ( type ) ;
2016-07-18 06:50:31 -04:00
this . _ngModuleOfTypes . delete ( type ) ;
2016-09-12 22:14:17 -04:00
// Clear all of the NgModule as they contain transitive information!
2016-07-18 06:50:31 -04:00
this . _ngModuleCache . clear ( ) ;
2016-11-10 17:07:30 -05:00
if ( dirMeta ) {
this . _directiveNormalizer . clearCacheFor ( dirMeta ) ;
}
2016-06-24 11:46:43 -04:00
}
2016-12-27 17:02:28 -05:00
clearCache ( ) : void {
2016-06-24 11:46:43 -04:00
this . _directiveCache . clear ( ) ;
2016-12-27 12:36:47 -05:00
this . _nonNormalizedDirectiveCache . clear ( ) ;
2016-12-02 13:08:46 -05:00
this . _summaryCache . clear ( ) ;
2016-06-24 11:46:43 -04:00
this . _pipeCache . clear ( ) ;
2016-07-18 06:50:31 -04:00
this . _ngModuleCache . clear ( ) ;
this . _ngModuleOfTypes . clear ( ) ;
2016-11-10 17:07:30 -05:00
this . _directiveNormalizer . clearCache ( ) ;
2016-06-24 11:46:43 -04:00
}
2016-12-27 12:36:47 -05:00
private _createProxyClass ( baseType : any , name : string ) : cpl . ProxyClass {
let delegate : any = null ;
const proxyClass : cpl.ProxyClass = < any > function ( ) {
if ( ! delegate ) {
throw new Error (
` Illegal state: Class ${ name } for type ${ stringify ( baseType ) } is not compiled yet! ` ) ;
}
return delegate . apply ( this , arguments ) ;
} ;
proxyClass . setDelegate = ( d ) = > {
delegate = d ;
( < any > proxyClass ) . prototype = d . prototype ;
} ;
// Make stringify work correctly
( < any > proxyClass ) . overriddenName = name ;
return proxyClass ;
}
private getGeneratedClass ( dirType : any , name : string ) : StaticSymbol | cpl . ProxyClass {
if ( dirType instanceof StaticSymbol ) {
return this . _staticSymbolCache . get ( ngfactoryFilePath ( dirType . filePath ) , name ) ;
} else {
return this . _createProxyClass ( dirType , name ) ;
}
}
private getComponentViewClass ( dirType : any ) : StaticSymbol | cpl . ProxyClass {
return this . getGeneratedClass ( dirType , cpl . viewClassName ( dirType , 0 ) ) ;
}
getHostComponentViewClass ( dirType : any ) : StaticSymbol | cpl . ProxyClass {
return this . getGeneratedClass ( dirType , cpl . hostViewClassName ( dirType ) ) ;
}
2017-08-16 12:00:03 -04:00
getHostComponentType ( dirType : any ) : StaticSymbol | Type {
2016-12-27 12:36:47 -05:00
const name = ` ${ cpl . identifierName ( { reference : dirType } )}_Host ` ;
if ( dirType instanceof StaticSymbol ) {
return this . _staticSymbolCache . get ( dirType . filePath , name ) ;
} else {
const HostClass = < any > function HostClass() { } ;
HostClass . overriddenName = name ;
return HostClass ;
}
}
2017-08-16 12:00:03 -04:00
private getRendererType ( dirType : any ) : StaticSymbol | object {
2017-02-16 16:55:55 -05:00
if ( dirType instanceof StaticSymbol ) {
return this . _staticSymbolCache . get (
2017-02-17 12:01:37 -05:00
ngfactoryFilePath ( dirType . filePath ) , cpl . rendererTypeName ( dirType ) ) ;
2017-02-16 16:55:55 -05:00
} else {
// returning an object as proxy,
// that we fill later during runtime compilation.
return < any > { } ;
}
}
2017-03-16 20:32:14 -04:00
private getComponentFactory (
2017-03-24 12:59:58 -04:00
selector : string , dirType : any , inputs : { [ key : string ] : string } | null ,
2017-08-16 12:00:03 -04:00
outputs : { [ key : string ] : string } ) : StaticSymbol | object {
2016-12-27 12:36:47 -05:00
if ( dirType instanceof StaticSymbol ) {
return this . _staticSymbolCache . get (
ngfactoryFilePath ( dirType . filePath ) , cpl . componentFactoryName ( dirType ) ) ;
} else {
const hostView = this . getHostComponentViewClass ( dirType ) ;
2017-03-16 20:32:14 -04:00
// Note: ngContentSelectors will be filled later once the template is
2017-03-14 17:54:36 -04:00
// loaded.
2017-08-16 12:00:03 -04:00
const createComponentFactory =
this . _reflector . resolveExternalReference ( Identifiers . createComponentFactory ) ;
2017-03-16 20:32:14 -04:00
return createComponentFactory ( selector , dirType , < any > hostView , inputs , outputs , [ ] ) ;
2017-03-14 17:54:36 -04:00
}
}
2017-08-16 12:00:03 -04:00
private initComponentFactory ( factory : StaticSymbol | object , ngContentSelectors : string [ ] ) {
2017-03-14 17:54:36 -04:00
if ( ! ( factory instanceof StaticSymbol ) ) {
2017-08-16 12:00:03 -04:00
( factory as any ) . ngContentSelectors . push ( . . . ngContentSelectors ) ;
2016-12-27 12:36:47 -05:00
}
}
2017-03-24 12:59:58 -04:00
private _loadSummary ( type : any , kind : cpl.CompileSummaryKind ) : cpl . CompileTypeSummary | null {
2016-12-15 12:12:40 -05:00
let typeSummary = this . _summaryCache . get ( type ) ;
if ( ! typeSummary ) {
const summary = this . _summaryResolver . resolveSummary ( type ) ;
typeSummary = summary ? summary.type : null ;
2017-03-24 12:59:58 -04:00
this . _summaryCache . set ( type , typeSummary || null ) ;
2016-12-02 13:08:46 -05:00
}
2016-12-15 12:12:40 -05:00
return typeSummary && typeSummary . summaryKind === kind ? typeSummary : null ;
2016-12-02 13:08:46 -05:00
}
2017-09-12 12:40:28 -04:00
getHostComponentMetadata (
compMeta : cpl.CompileDirectiveMetadata ,
hostViewType? : StaticSymbol | cpl . ProxyClass ) : cpl . CompileDirectiveMetadata {
const hostType = this . getHostComponentType ( compMeta . type . reference ) ;
if ( ! hostViewType ) {
hostViewType = this . getHostComponentViewClass ( hostType ) ;
}
// Note: ! is ok here as this method should only be called with normalized directive
// metadata, which always fills in the selector.
const template = CssSelector . parse ( compMeta . selector ! ) [ 0 ] . getMatchingElementTemplate ( ) ;
const templateUrl = '' ;
const htmlAst = this . _htmlParser . parse ( template , templateUrl ) ;
return cpl . CompileDirectiveMetadata . create ( {
isHost : true ,
type : { reference : hostType , diDeps : [ ] , lifecycleHooks : [ ] } ,
template : new cpl . CompileTemplateMetadata ( {
encapsulation : ViewEncapsulation.None ,
template ,
templateUrl ,
htmlAst ,
styles : [ ] ,
styleUrls : [ ] ,
ngContentSelectors : [ ] ,
animations : [ ] ,
isInline : true ,
externalStylesheets : [ ] ,
interpolation : null ,
preserveWhitespaces : false ,
} ) ,
exportAs : null ,
changeDetection : ChangeDetectionStrategy.Default ,
inputs : [ ] ,
outputs : [ ] ,
host : { } ,
isComponent : true ,
selector : '*' ,
providers : [ ] ,
viewProviders : [ ] ,
queries : [ ] ,
2017-11-29 19:29:05 -05:00
guards : { } ,
2017-09-12 12:40:28 -04:00
viewQueries : [ ] ,
componentViewType : hostViewType ,
rendererType :
{ id : '__Host__' , encapsulation : ViewEncapsulation.None , styles : [ ] , data : { } } as object ,
entryComponents : [ ] ,
componentFactory : null
} ) ;
}
2017-05-17 18:39:08 -04:00
loadDirectiveMetadata ( ngModuleType : any , directiveType : any , isSync : boolean ) : SyncAsync < null > {
2016-11-10 17:07:30 -05:00
if ( this . _directiveCache . has ( directiveType ) ) {
2017-03-24 12:59:58 -04:00
return null ;
2016-11-10 17:07:30 -05:00
}
2016-06-28 12:54:42 -04:00
directiveType = resolveForwardRef ( directiveType ) ;
2017-03-24 12:59:58 -04:00
const { annotation , metadata } = this . getNonNormalizedDirectiveMetadata ( directiveType ) ! ;
2016-11-16 13:22:11 -05:00
2017-03-24 12:59:58 -04:00
const createDirectiveMetadata = ( templateMetadata : cpl.CompileTemplateMetadata | null ) = > {
2016-11-16 13:22:11 -05:00
const normalizedDirMeta = new cpl . CompileDirectiveMetadata ( {
2017-03-24 12:59:58 -04:00
isHost : false ,
2016-11-23 12:42:19 -05:00
type : metadata . type ,
isComponent : metadata.isComponent ,
selector : metadata.selector ,
exportAs : metadata.exportAs ,
changeDetection : metadata.changeDetection ,
inputs : metadata.inputs ,
outputs : metadata.outputs ,
hostListeners : metadata.hostListeners ,
hostProperties : metadata.hostProperties ,
hostAttributes : metadata.hostAttributes ,
providers : metadata.providers ,
viewProviders : metadata.viewProviders ,
queries : metadata.queries ,
2017-11-29 19:29:05 -05:00
guards : metadata.guards ,
2016-11-23 12:42:19 -05:00
viewQueries : metadata.viewQueries ,
entryComponents : metadata.entryComponents ,
2016-12-27 12:36:47 -05:00
componentViewType : metadata.componentViewType ,
2017-02-17 12:01:37 -05:00
rendererType : metadata.rendererType ,
2016-12-27 12:36:47 -05:00
componentFactory : metadata.componentFactory ,
2016-11-16 13:22:11 -05:00
template : templateMetadata
} ) ;
2017-03-14 17:54:36 -04:00
if ( templateMetadata ) {
2017-03-24 12:59:58 -04:00
this . initComponentFactory ( metadata . componentFactory ! , templateMetadata . ngContentSelectors ) ;
2017-03-14 17:54:36 -04:00
}
2016-11-16 13:22:11 -05:00
this . _directiveCache . set ( directiveType , normalizedDirMeta ) ;
2016-12-02 13:08:46 -05:00
this . _summaryCache . set ( directiveType , normalizedDirMeta . toSummary ( ) ) ;
2017-05-17 18:39:08 -04:00
return null ;
2016-11-16 13:22:11 -05:00
} ;
2016-11-10 17:07:30 -05:00
2016-11-23 12:42:19 -05:00
if ( metadata . isComponent ) {
2017-03-24 12:59:58 -04:00
const template = metadata . template ! ;
2016-11-16 13:22:11 -05:00
const templateMeta = this . _directiveNormalizer . normalizeTemplate ( {
2017-03-14 12:16:15 -04:00
ngModuleType ,
2016-11-16 13:22:11 -05:00
componentType : directiveType ,
2017-05-18 16:46:51 -04:00
module Url : this . _reflector . componentModuleUrl ( directiveType , annotation ) ,
2017-03-24 12:59:58 -04:00
encapsulation : template.encapsulation ,
template : template.template ,
templateUrl : template.templateUrl ,
styles : template.styles ,
styleUrls : template.styleUrls ,
animations : template.animations ,
2017-07-28 09:58:28 -04:00
interpolation : template.interpolation ,
preserveWhitespaces : template.preserveWhitespaces
2016-11-16 13:22:11 -05:00
} ) ;
2017-05-17 18:39:08 -04:00
if ( isPromise ( templateMeta ) && isSync ) {
this . _reportError ( componentStillLoadingError ( directiveType ) , directiveType ) ;
2016-11-16 13:22:11 -05:00
return null ;
2016-01-06 17:13:44 -05:00
}
2017-05-17 18:39:08 -04:00
return SyncAsync . then ( templateMeta , createDirectiveMetadata ) ;
2016-11-16 13:22:11 -05:00
} else {
// directive
createDirectiveMetadata ( null ) ;
return null ;
}
}
2016-01-06 17:13:44 -05:00
2016-11-23 12:42:19 -05:00
getNonNormalizedDirectiveMetadata ( directiveType : any ) :
2017-03-24 12:59:58 -04:00
{ annotation : Directive , metadata : cpl.CompileDirectiveMetadata } | null {
2016-11-16 13:22:11 -05:00
directiveType = resolveForwardRef ( directiveType ) ;
2016-12-27 12:36:47 -05:00
if ( ! directiveType ) {
return null ;
}
let cacheEntry = this . _nonNormalizedDirectiveCache . get ( directiveType ) ;
if ( cacheEntry ) {
return cacheEntry ;
}
const dirMeta = this . _directiveResolver . resolve ( directiveType , false ) ;
2016-11-16 13:22:11 -05:00
if ( ! dirMeta ) {
return null ;
}
2017-03-24 12:59:58 -04:00
let nonNormalizedTemplateMetadata : cpl.CompileTemplateMetadata = undefined ! ;
2016-11-10 17:07:30 -05:00
2017-08-16 12:00:03 -04:00
if ( createComponent . isTypeOf ( dirMeta ) ) {
2016-11-10 17:07:30 -05:00
// component
2017-08-16 12:00:03 -04:00
const compMeta = dirMeta as Component ;
assertArrayOfStrings ( 'styles' , compMeta . styles ) ;
assertArrayOfStrings ( 'styleUrls' , compMeta . styleUrls ) ;
assertInterpolationSymbols ( 'interpolation' , compMeta . interpolation ) ;
2016-11-10 17:07:30 -05:00
2017-08-16 12:00:03 -04:00
const animations = compMeta . animations ;
2016-11-10 17:07:30 -05:00
2016-11-16 13:22:11 -05:00
nonNormalizedTemplateMetadata = new cpl . CompileTemplateMetadata ( {
2017-08-16 12:00:03 -04:00
encapsulation : noUndefined ( compMeta . encapsulation ) ,
template : noUndefined ( compMeta . template ) ,
templateUrl : noUndefined ( compMeta . templateUrl ) ,
2017-09-11 18:18:19 -04:00
htmlAst : null ,
2017-08-16 12:00:03 -04:00
styles : compMeta.styles || [ ] ,
styleUrls : compMeta.styleUrls || [ ] ,
2017-03-24 12:59:58 -04:00
animations : animations || [ ] ,
2017-08-16 12:00:03 -04:00
interpolation : noUndefined ( compMeta . interpolation ) ,
isInline : ! ! compMeta . template ,
2017-03-24 12:59:58 -04:00
externalStylesheets : [ ] ,
2017-07-28 09:58:28 -04:00
ngContentSelectors : [ ] ,
preserveWhitespaces : noUndefined ( dirMeta . preserveWhitespaces ) ,
2016-11-10 17:07:30 -05:00
} ) ;
2016-11-16 13:22:11 -05:00
}
2017-03-24 12:59:58 -04:00
let changeDetectionStrategy : ChangeDetectionStrategy = null ! ;
2016-11-30 13:52:51 -05:00
let viewProviders : cpl.CompileProviderMetadata [ ] = [ ] ;
2016-12-27 12:36:47 -05:00
let entryComponentMetadata : cpl.CompileEntryComponentMetadata [ ] = [ ] ;
2016-11-16 13:22:11 -05:00
let selector = dirMeta . selector ;
2017-08-16 12:00:03 -04:00
if ( createComponent . isTypeOf ( dirMeta ) ) {
2016-11-16 13:22:11 -05:00
// Component
2017-08-16 12:00:03 -04:00
const compMeta = dirMeta as Component ;
changeDetectionStrategy = compMeta . changeDetection ! ;
if ( compMeta . viewProviders ) {
2016-11-16 13:22:11 -05:00
viewProviders = this . _getProvidersMetadata (
2017-08-16 12:00:03 -04:00
compMeta . viewProviders , entryComponentMetadata ,
2016-12-15 12:12:40 -05:00
` viewProviders for " ${ stringifyType ( directiveType ) } " ` , [ ] , directiveType ) ;
2016-11-16 13:22:11 -05:00
}
2017-08-16 12:00:03 -04:00
if ( compMeta . entryComponents ) {
entryComponentMetadata = flattenAndDedupeArray ( compMeta . entryComponents )
2017-03-24 12:59:58 -04:00
. map ( ( type ) = > this . _getEntryComponentMetadata ( type ) ! )
2016-11-23 12:42:19 -05:00
. concat ( entryComponentMetadata ) ;
2016-11-16 13:22:11 -05:00
}
if ( ! selector ) {
selector = this . _schemaRegistry . getDefaultComponentElementName ( ) ;
2016-11-10 17:07:30 -05:00
}
} else {
2016-11-16 13:22:11 -05:00
// Directive
if ( ! selector ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 16:54:38 -05:00
` Directive ${ stringifyType ( directiveType ) } has no selector, please add it! ` ) ,
2016-12-06 20:11:09 -05:00
directiveType ) ;
selector = 'error' ;
2016-11-16 13:22:11 -05:00
}
2016-11-10 17:07:30 -05:00
}
2016-11-16 13:22:11 -05:00
2016-11-30 13:52:51 -05:00
let providers : cpl.CompileProviderMetadata [ ] = [ ] ;
2016-12-27 17:02:28 -05:00
if ( dirMeta . providers != null ) {
2016-11-16 13:22:11 -05:00
providers = this . _getProvidersMetadata (
2016-12-15 12:12:40 -05:00
dirMeta . providers , entryComponentMetadata ,
` providers for " ${ stringifyType ( directiveType ) } " ` , [ ] , directiveType ) ;
2016-11-16 13:22:11 -05:00
}
let queries : cpl.CompileQueryMetadata [ ] = [ ] ;
let viewQueries : cpl.CompileQueryMetadata [ ] = [ ] ;
2016-12-27 17:02:28 -05:00
if ( dirMeta . queries != null ) {
2016-11-16 13:22:11 -05:00
queries = this . _getQueriesMetadata ( dirMeta . queries , false , directiveType ) ;
viewQueries = this . _getQueriesMetadata ( dirMeta . queries , true , directiveType ) ;
}
2016-11-23 12:42:19 -05:00
const metadata = cpl . CompileDirectiveMetadata . create ( {
2017-03-24 12:59:58 -04:00
isHost : false ,
2016-11-16 13:22:11 -05:00
selector : selector ,
2017-03-24 12:59:58 -04:00
exportAs : noUndefined ( dirMeta . exportAs ) ,
2016-11-16 13:22:11 -05:00
isComponent : ! ! nonNormalizedTemplateMetadata ,
2016-11-23 12:42:19 -05:00
type : this . _getTypeMetadata ( directiveType ) ,
2016-11-16 13:22:11 -05:00
template : nonNormalizedTemplateMetadata ,
changeDetection : changeDetectionStrategy ,
2017-03-24 12:59:58 -04:00
inputs : dirMeta.inputs || [ ] ,
outputs : dirMeta.outputs || [ ] ,
host : dirMeta.host || { } ,
providers : providers || [ ] ,
viewProviders : viewProviders || [ ] ,
queries : queries || [ ] ,
2017-11-29 19:29:05 -05:00
guards : dirMeta.guards || { } ,
2017-03-24 12:59:58 -04:00
viewQueries : viewQueries || [ ] ,
2016-12-27 12:36:47 -05:00
entryComponents : entryComponentMetadata ,
componentViewType : nonNormalizedTemplateMetadata ? this . getComponentViewClass ( directiveType ) :
2017-03-24 12:59:58 -04:00
null ,
rendererType : nonNormalizedTemplateMetadata ? this . getRendererType ( directiveType ) : null ,
componentFactory : null
2016-11-16 13:22:11 -05:00
} ) ;
2017-03-16 20:32:14 -04:00
if ( nonNormalizedTemplateMetadata ) {
metadata . componentFactory =
this . getComponentFactory ( selector , directiveType , metadata . inputs , metadata . outputs ) ;
}
2016-12-27 12:36:47 -05:00
cacheEntry = { metadata , annotation : dirMeta } ;
this . _nonNormalizedDirectiveCache . set ( directiveType , cacheEntry ) ;
return cacheEntry ;
2016-11-10 17:07:30 -05:00
}
/ * *
* Gets the metadata for the given directive .
2017-02-05 17:22:06 -05:00
* This assumes ` loadNgModuleDirectiveAndPipeMetadata ` has been called first .
2016-11-10 17:07:30 -05:00
* /
getDirectiveMetadata ( directiveType : any ) : cpl . CompileDirectiveMetadata {
2017-03-24 12:59:58 -04:00
const dirMeta = this . _directiveCache . get ( directiveType ) ! ;
2016-11-10 17:07:30 -05:00
if ( ! dirMeta ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2017-02-05 17:22:06 -05:00
` Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${ stringifyType ( directiveType ) } . ` ) ,
2016-12-06 20:11:09 -05:00
directiveType ) ;
2015-12-02 13:35:51 -05:00
}
2016-11-10 17:07:30 -05:00
return dirMeta ;
2015-12-02 13:35:51 -05:00
}
2016-11-10 19:27:53 -05:00
getDirectiveSummary ( dirType : any ) : cpl . CompileDirectiveSummary {
2016-12-02 13:08:46 -05:00
const dirSummary =
< cpl.CompileDirectiveSummary > this . _loadSummary ( dirType , cpl . CompileSummaryKind . Directive ) ;
2016-11-10 19:27:53 -05:00
if ( ! dirSummary ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Illegal state: Could not load the summary for directive ${ stringifyType ( dirType ) } . ` ) ,
2016-12-06 20:11:09 -05:00
dirType ) ;
2016-11-10 19:27:53 -05:00
}
return dirSummary ;
}
2017-04-26 12:24:42 -04:00
isDirective ( type : any ) {
return ! ! this . _loadSummary ( type , cpl . CompileSummaryKind . Directive ) ||
this . _directiveResolver . isDirective ( type ) ;
}
isPipe ( type : any ) {
return ! ! this . _loadSummary ( type , cpl . CompileSummaryKind . Pipe ) ||
this . _pipeResolver . isPipe ( type ) ;
}
2016-11-10 17:07:30 -05:00
2017-04-26 12:24:42 -04:00
isNgModule ( type : any ) {
return ! ! this . _loadSummary ( type , cpl . CompileSummaryKind . NgModule ) ||
this . _ngModuleResolver . isNgModule ( type ) ;
}
2016-11-10 17:07:30 -05:00
2017-12-15 19:25:04 -05:00
getNgModuleSummary ( module Type : any , alreadyCollecting : Set < any > | null = null ) :
cpl . CompileNgModuleSummary | null {
2017-03-24 12:59:58 -04:00
let module Summary : cpl . CompileNgModuleSummary | null =
2016-12-02 13:08:46 -05:00
< cpl.CompileNgModuleSummary > this . _loadSummary ( module Type , cpl . CompileSummaryKind . NgModule ) ;
2016-11-29 18:36:33 -05:00
if ( ! module Summary ) {
2017-12-15 19:25:04 -05:00
const module Meta = this . getNgModuleMetadata ( module Type , false , alreadyCollecting ) ;
2016-12-02 13:08:46 -05:00
module Summary = module Meta ? module Meta.toSummary ( ) : null ;
2016-11-29 18:36:33 -05:00
if ( module Summary ) {
2016-12-02 13:08:46 -05:00
this . _summaryCache . set ( module Type , module Summary ) ;
2016-11-29 18:36:33 -05:00
}
}
return module Summary ;
2016-11-10 19:27:53 -05:00
}
2016-11-10 17:07:30 -05:00
/ * *
2016-11-29 18:36:33 -05:00
* Loads the declared directives and pipes of an NgModule .
2016-11-10 17:07:30 -05:00
* /
2016-11-29 11:08:22 -05:00
loadNgModuleDirectiveAndPipeMetadata ( module Type : any , isSync : boolean , throwIfNotFound = true ) :
2016-11-29 18:36:33 -05:00
Promise < any > {
2016-11-29 11:08:22 -05:00
const ngModule = this . getNgModuleMetadata ( module Type , throwIfNotFound ) ;
2016-11-29 18:36:33 -05:00
const loading : Promise < any > [ ] = [ ] ;
2016-11-29 11:08:22 -05:00
if ( ngModule ) {
2016-11-29 18:36:33 -05:00
ngModule . declaredDirectives . forEach ( ( id ) = > {
2017-04-26 12:24:42 -04:00
const promise = this . loadDirectiveMetadata ( module Type , id . reference , isSync ) ;
2016-11-29 18:36:33 -05:00
if ( promise ) {
loading . push ( promise ) ;
}
} ) ;
ngModule . declaredPipes . forEach ( ( id ) = > this . _loadPipeMetadata ( id . reference ) ) ;
2016-11-29 11:08:22 -05:00
}
2016-11-29 18:36:33 -05:00
return Promise . all ( loading ) ;
2016-11-10 17:07:30 -05:00
}
2017-12-15 19:25:04 -05:00
getNgModuleMetadata (
module Type : any , throwIfNotFound = true ,
alreadyCollecting : Set < any > | null = null ) : cpl . CompileNgModuleMetadata | null {
2016-06-28 12:54:42 -04:00
module Type = resolveForwardRef ( module Type ) ;
2016-09-14 18:58:18 -04:00
let compileMeta = this . _ngModuleCache . get ( module Type ) ;
2016-11-10 17:07:30 -05:00
if ( compileMeta ) {
return compileMeta ;
}
const meta = this . _ngModuleResolver . resolve ( module Type , throwIfNotFound ) ;
if ( ! meta ) {
return null ;
}
const declaredDirectives : cpl.CompileIdentifierMetadata [ ] = [ ] ;
2016-11-10 19:27:53 -05:00
const exportedNonModuleIdentifiers : cpl.CompileIdentifierMetadata [ ] = [ ] ;
2016-11-10 17:07:30 -05:00
const declaredPipes : cpl.CompileIdentifierMetadata [ ] = [ ] ;
2016-11-10 19:27:53 -05:00
const importedModules : cpl.CompileNgModuleSummary [ ] = [ ] ;
const exportedModules : cpl.CompileNgModuleSummary [ ] = [ ] ;
2016-11-29 11:08:22 -05:00
const providers : cpl.CompileProviderMetadata [ ] = [ ] ;
2016-12-27 12:36:47 -05:00
const entryComponents : cpl.CompileEntryComponentMetadata [ ] = [ ] ;
2016-11-10 17:07:30 -05:00
const bootstrapComponents : cpl.CompileIdentifierMetadata [ ] = [ ] ;
const schemas : SchemaMetadata [ ] = [ ] ;
if ( meta . imports ) {
flattenAndDedupeArray ( meta . imports ) . forEach ( ( importedType ) = > {
2017-08-16 12:00:03 -04:00
let importedModuleType : Type = undefined ! ;
2016-11-10 17:07:30 -05:00
if ( isValidType ( importedType ) ) {
importedModuleType = importedType ;
} else if ( importedType && importedType . ngModule ) {
const module WithProviders : ModuleWithProviders = importedType ;
importedModuleType = module WithProviders.ngModule ;
if ( module WithProviders.providers ) {
providers . push ( . . . this . _getProvidersMetadata (
module WithProviders.providers , entryComponents ,
2016-12-15 12:12:40 -05:00
` provider for the NgModule ' ${ stringifyType ( importedModuleType ) } ' ` , [ ] ,
importedType ) ) ;
2016-07-18 06:50:31 -04:00
}
2016-11-10 17:07:30 -05:00
}
2016-09-14 18:58:18 -04:00
2016-11-10 17:07:30 -05:00
if ( importedModuleType ) {
2017-03-06 20:00:25 -05:00
if ( this . _checkSelfImport ( module Type , importedModuleType ) ) return ;
2017-12-15 19:25:04 -05:00
if ( ! alreadyCollecting ) alreadyCollecting = new Set ( ) ;
if ( alreadyCollecting . has ( importedModuleType ) ) {
this . _reportError (
syntaxError (
` ${ this . _getTypeDescriptor ( importedModuleType ) } ' ${ stringifyType ( importedType ) } ' is imported recursively by the module ' ${ stringifyType ( module Type ) } '. ` ) ,
module Type ) ;
return ;
}
alreadyCollecting . add ( importedModuleType ) ;
const importedModuleSummary =
this . getNgModuleSummary ( importedModuleType , alreadyCollecting ) ;
alreadyCollecting . delete ( importedModuleType ) ;
2016-11-10 19:27:53 -05:00
if ( ! importedModuleSummary ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2017-03-14 20:12:18 -04:00
` Unexpected ${ this . _getTypeDescriptor ( importedType ) } ' ${ stringifyType ( importedType ) } ' imported by the module ' ${ stringifyType ( module Type ) } '. Please add a @NgModule annotation. ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
return ;
2016-07-18 06:50:31 -04:00
}
2016-11-10 19:27:53 -05:00
importedModules . push ( importedModuleSummary ) ;
2016-11-10 17:07:30 -05:00
} else {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Unexpected value ' ${ stringifyType ( importedType ) } ' imported by the module ' ${ stringifyType ( module Type ) } ' ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
return ;
2016-11-10 17:07:30 -05:00
}
} ) ;
}
2016-07-18 06:50:31 -04:00
2016-11-10 17:07:30 -05:00
if ( meta . exports ) {
flattenAndDedupeArray ( meta . exports ) . forEach ( ( exportedType ) = > {
if ( ! isValidType ( exportedType ) ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Unexpected value ' ${ stringifyType ( exportedType ) } ' exported by the module ' ${ stringifyType ( module Type ) } ' ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
return ;
2016-11-10 17:07:30 -05:00
}
2017-12-15 19:25:04 -05:00
if ( ! alreadyCollecting ) alreadyCollecting = new Set ( ) ;
if ( alreadyCollecting . has ( exportedType ) ) {
this . _reportError (
syntaxError (
` ${ this . _getTypeDescriptor ( exportedType ) } ' ${ stringify ( exportedType ) } ' is exported recursively by the module ' ${ stringifyType ( module Type ) } ' ` ) ,
module Type ) ;
return ;
}
alreadyCollecting . add ( exportedType ) ;
const exportedModuleSummary = this . getNgModuleSummary ( exportedType , alreadyCollecting ) ;
alreadyCollecting . delete ( exportedType ) ;
2016-11-10 19:27:53 -05:00
if ( exportedModuleSummary ) {
exportedModules . push ( exportedModuleSummary ) ;
2016-11-10 17:07:30 -05:00
} else {
2016-11-23 12:42:19 -05:00
exportedNonModuleIdentifiers . push ( this . _getIdentifierMetadata ( exportedType ) ) ;
2016-11-10 17:07:30 -05:00
}
} ) ;
}
2016-07-18 06:50:31 -04:00
2016-11-10 17:07:30 -05:00
// Note: This will be modified later, so we rely on
// getting a new instance every time!
const transitiveModule = this . _getTransitiveNgModuleMetadata ( importedModules , exportedModules ) ;
if ( meta . declarations ) {
flattenAndDedupeArray ( meta . declarations ) . forEach ( ( declaredType ) = > {
if ( ! isValidType ( declaredType ) ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Unexpected value ' ${ stringifyType ( declaredType ) } ' declared by the module ' ${ stringifyType ( module Type ) } ' ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
return ;
2016-11-10 17:07:30 -05:00
}
2016-11-23 12:42:19 -05:00
const declaredIdentifier = this . _getIdentifierMetadata ( declaredType ) ;
2017-04-26 12:24:42 -04:00
if ( this . isDirective ( declaredType ) ) {
2016-12-02 13:08:46 -05:00
transitiveModule . addDirective ( declaredIdentifier ) ;
2016-11-10 17:07:30 -05:00
declaredDirectives . push ( declaredIdentifier ) ;
this . _addTypeToModule ( declaredType , module Type ) ;
2017-04-26 12:24:42 -04:00
} else if ( this . isPipe ( declaredType ) ) {
2016-12-02 13:08:46 -05:00
transitiveModule . addPipe ( declaredIdentifier ) ;
2016-11-10 17:07:30 -05:00
transitiveModule . pipes . push ( declaredIdentifier ) ;
declaredPipes . push ( declaredIdentifier ) ;
this . _addTypeToModule ( declaredType , module Type ) ;
} else {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2017-03-14 20:12:18 -04:00
` Unexpected ${ this . _getTypeDescriptor ( declaredType ) } ' ${ stringifyType ( declaredType ) } ' declared by the module ' ${ stringifyType ( module Type ) } '. Please add a @Pipe/@Directive/@Component annotation. ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
return ;
2016-11-10 17:07:30 -05:00
}
} ) ;
}
feat(browser): use AppModules for bootstrap in the browser
This introduces the `BrowserModule` to be used for long form
bootstrap and offline compile bootstrap:
```
@AppModule({
modules: [BrowserModule],
precompile: [MainComponent],
providers: […], // additional providers
directives: […], // additional platform directives
pipes: […] // additional platform pipes
})
class MyModule {
constructor(appRef: ApplicationRef) {
appRef.bootstrap(MainComponent);
}
}
// offline compile
import {bootstrapModuleFactory} from ‘@angular/platform-browser’;
bootstrapModuleFactory(MyModuleNgFactory);
// runtime compile long form
import {bootstrapModule} from ‘@angular/platform-browser-dynamic’;
bootstrapModule(MyModule);
```
The short form, `bootstrap(...)`, can now creates a module on the fly,
given `directives`, `pipes, `providers`, `precompile` and `modules`
properties.
Related changes:
- make `SanitizationService`, `SecurityContext` public in `@angular/core` so that the offline compiler can resolve the token
- move `AnimationDriver` to `platform-browser` and make it
public so that the offline compiler can resolve the token
BREAKING CHANGES:
- short form bootstrap does no longer allow
to inject compiler internals (i.e. everything
from `@angular/compiler). Inject `Compiler` instead.
To provide custom providers for the compiler,
create a custom compiler via `browserCompiler({providers: [...]})`
and pass that into the `bootstrap` method.
2016-06-30 16:07:17 -04:00
2016-11-10 19:27:53 -05:00
const exportedDirectives : cpl.CompileIdentifierMetadata [ ] = [ ] ;
const exportedPipes : cpl.CompileIdentifierMetadata [ ] = [ ] ;
exportedNonModuleIdentifiers . forEach ( ( exportedId ) = > {
if ( transitiveModule . directivesSet . has ( exportedId . reference ) ) {
exportedDirectives . push ( exportedId ) ;
2016-12-02 13:08:46 -05:00
transitiveModule . addExportedDirective ( exportedId ) ;
2016-11-10 19:27:53 -05:00
} else if ( transitiveModule . pipesSet . has ( exportedId . reference ) ) {
exportedPipes . push ( exportedId ) ;
2016-12-02 13:08:46 -05:00
transitiveModule . addExportedPipe ( exportedId ) ;
2016-11-10 19:27:53 -05:00
} else {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Can't export ${ this . _getTypeDescriptor ( exportedId . reference ) } ${ stringifyType ( exportedId . reference ) } from ${ stringifyType ( module Type ) } as it was neither declared nor imported! ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
2017-04-04 11:30:38 -04:00
return ;
2016-11-10 19:27:53 -05:00
}
} ) ;
2016-11-10 17:07:30 -05:00
// The providers of the module have to go last
// so that they overwrite any other provider we already added.
if ( meta . providers ) {
providers . push ( . . . this . _getProvidersMetadata (
2016-12-15 12:12:40 -05:00
meta . providers , entryComponents ,
` provider for the NgModule ' ${ stringifyType ( module Type ) } ' ` , [ ] , module Type ) ) ;
2016-11-10 17:07:30 -05:00
}
2016-09-14 18:58:18 -04:00
2016-11-10 17:07:30 -05:00
if ( meta . entryComponents ) {
2016-12-15 12:12:40 -05:00
entryComponents . push ( . . . flattenAndDedupeArray ( meta . entryComponents )
2017-03-24 12:59:58 -04:00
. map ( type = > this . _getEntryComponentMetadata ( type ) ! ) ) ;
2016-11-10 17:07:30 -05:00
}
2016-09-14 18:58:18 -04:00
2016-11-10 17:07:30 -05:00
if ( meta . bootstrap ) {
2016-12-06 20:11:09 -05:00
flattenAndDedupeArray ( meta . bootstrap ) . forEach ( type = > {
2016-11-10 17:07:30 -05:00
if ( ! isValidType ( type ) ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Unexpected value ' ${ stringifyType ( type ) } ' used in the bootstrap property of module ' ${ stringifyType ( module Type ) } ' ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
return ;
2016-11-10 17:07:30 -05:00
}
2016-12-15 12:12:40 -05:00
bootstrapComponents . push ( this . _getIdentifierMetadata ( type ) ) ;
2016-11-10 17:07:30 -05:00
} ) ;
}
2016-09-14 18:58:18 -04:00
2016-12-27 12:36:47 -05:00
entryComponents . push (
2017-03-24 12:59:58 -04:00
. . . bootstrapComponents . map ( type = > this . _getEntryComponentMetadata ( type . reference ) ! ) ) ;
2016-09-14 18:58:18 -04:00
2016-11-10 17:07:30 -05:00
if ( meta . schemas ) {
schemas . push ( . . . flattenAndDedupeArray ( meta . schemas ) ) ;
}
2016-07-27 04:52:31 -04:00
2016-11-10 17:07:30 -05:00
compileMeta = new cpl . CompileNgModuleMetadata ( {
2016-11-23 12:42:19 -05:00
type : this . _getTypeMetadata ( module Type ) ,
2016-11-10 17:07:30 -05:00
providers ,
entryComponents ,
bootstrapComponents ,
schemas ,
declaredDirectives ,
exportedDirectives ,
declaredPipes ,
exportedPipes ,
importedModules ,
exportedModules ,
transitiveModule ,
2017-03-24 12:59:58 -04:00
id : meta.id || null ,
2016-11-10 17:07:30 -05:00
} ) ;
2016-09-14 18:58:18 -04:00
2016-12-02 13:08:46 -05:00
entryComponents . forEach ( ( id ) = > transitiveModule . addEntryComponent ( id ) ) ;
2017-03-24 12:59:58 -04:00
providers . forEach ( ( provider ) = > transitiveModule . addProvider ( provider , compileMeta ! . type ) ) ;
2016-12-02 13:08:46 -05:00
transitiveModule . addModule ( compileMeta . type ) ;
2016-11-10 17:07:30 -05:00
this . _ngModuleCache . set ( module Type , compileMeta ) ;
2016-06-28 12:54:42 -04:00
return compileMeta ;
}
2017-08-16 12:00:03 -04:00
private _checkSelfImport ( module Type : Type , importedModuleType : Type ) : boolean {
2017-03-06 20:00:25 -05:00
if ( module Type === importedModuleType ) {
this . _reportError (
syntaxError ( ` ' ${ stringifyType ( module Type ) } ' module can't import itself ` ) , module Type ) ;
return true ;
}
return false ;
}
2017-08-16 12:00:03 -04:00
private _getTypeDescriptor ( type : Type ) : string {
2017-11-06 13:01:27 -05:00
if ( isValidType ( type ) ) {
if ( this . isDirective ( type ) ) {
return 'directive' ;
}
2016-09-14 18:58:18 -04:00
2017-11-06 13:01:27 -05:00
if ( this . isPipe ( type ) ) {
return 'pipe' ;
}
2016-09-14 18:58:18 -04:00
2017-11-06 13:01:27 -05:00
if ( this . isNgModule ( type ) ) {
return 'module' ;
}
2016-09-14 18:58:18 -04:00
}
if ( ( type as any ) . provide ) {
2016-08-17 18:57:02 -04:00
return 'provider' ;
}
2016-09-14 18:58:18 -04:00
return 'value' ;
2016-08-17 18:57:02 -04:00
}
2016-11-10 17:07:30 -05:00
2017-08-16 12:00:03 -04:00
private _addTypeToModule ( type : Type , module Type : Type ) {
2016-07-18 06:50:31 -04:00
const oldModule = this . _ngModuleOfTypes . get ( type ) ;
if ( oldModule && oldModule !== module Type ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Type ${ stringifyType ( type ) } is part of the declarations of 2 modules: ${ stringifyType ( oldModule ) } and ${ stringifyType ( module Type ) } ! ` +
` Please consider moving ${ stringifyType ( type ) } to a higher module that imports ${ stringifyType ( oldModule ) } and ${ stringifyType ( module Type ) } . ` +
` You can also create a new NgModule that exports and includes ${ stringifyType ( type ) } then import that NgModule in ${ stringifyType ( oldModule ) } and ${ stringifyType ( module Type ) } . ` ) ,
2016-12-06 20:11:09 -05:00
module Type ) ;
2017-04-04 11:30:38 -04:00
return ;
2016-02-18 13:53:21 -05:00
}
2016-07-18 06:50:31 -04:00
this . _ngModuleOfTypes . set ( type , module Type ) ;
}
private _getTransitiveNgModuleMetadata (
2016-11-10 19:27:53 -05:00
importedModules : cpl.CompileNgModuleSummary [ ] ,
exportedModules : cpl.CompileNgModuleSummary [ ] ) : cpl . TransitiveCompileNgModuleMetadata {
2016-07-25 03:36:30 -04:00
// collect `providers` / `entryComponents` from all imported and all exported modules
2016-12-02 13:08:46 -05:00
const result = new cpl . TransitiveCompileNgModuleMetadata ( ) ;
2016-11-29 11:08:22 -05:00
const module sByToken = new Map < any , Set < any > > ( ) ;
importedModules . concat ( exportedModules ) . forEach ( ( modSummary ) = > {
2016-12-02 13:08:46 -05:00
modSummary . module s.forEach ( ( mod ) = > result . addModule ( mod ) ) ;
modSummary . entryComponents . forEach ( ( comp ) = > result . addEntryComponent ( comp ) ) ;
2016-11-29 18:36:33 -05:00
const addedTokens = new Set < any > ( ) ;
2016-11-29 11:08:22 -05:00
modSummary . providers . forEach ( ( entry ) = > {
const tokenRef = cpl . tokenReference ( entry . provider . token ) ;
let prevModules = module sByToken.get ( tokenRef ) ;
if ( ! prevModules ) {
prevModules = new Set < any > ( ) ;
module sByToken.set ( tokenRef , prevModules ) ;
}
const module Ref = entry . module .reference ;
2016-11-29 18:36:33 -05:00
// Note: the providers of one module may still contain multiple providers
// per token (e.g. for multi providers), and we need to preserve these.
if ( addedTokens . has ( tokenRef ) || ! prevModules . has ( module Ref ) ) {
2016-11-29 11:08:22 -05:00
prevModules . add ( module Ref ) ;
2016-11-29 18:36:33 -05:00
addedTokens . add ( tokenRef ) ;
2016-12-02 13:08:46 -05:00
result . addProvider ( entry . provider , entry . module ) ;
2016-11-29 11:08:22 -05:00
}
} ) ;
} ) ;
2016-12-02 13:08:46 -05:00
exportedModules . forEach ( ( modSummary ) = > {
modSummary . exportedDirectives . forEach ( ( id ) = > result . addExportedDirective ( id ) ) ;
modSummary . exportedPipes . forEach ( ( id ) = > result . addExportedPipe ( id ) ) ;
} ) ;
importedModules . forEach ( ( modSummary ) = > {
modSummary . exportedDirectives . forEach ( ( id ) = > result . addDirective ( id ) ) ;
modSummary . exportedPipes . forEach ( ( id ) = > result . addPipe ( id ) ) ;
2016-11-29 11:08:22 -05:00
} ) ;
2016-12-02 13:08:46 -05:00
return result ;
2016-07-18 06:50:31 -04:00
}
2017-08-16 12:00:03 -04:00
private _getIdentifierMetadata ( type : Type ) : cpl . CompileIdentifierMetadata {
2016-11-10 17:07:30 -05:00
type = resolveForwardRef ( type ) ;
2016-11-30 13:52:51 -05:00
return { reference : type } ;
2016-02-18 13:53:21 -05:00
}
2016-12-15 12:12:40 -05:00
isInjectable ( type : any ) : boolean {
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
const annotations = this . _reflector . tryAnnotations ( type ) ;
2017-08-16 12:00:03 -04:00
return annotations . some ( ann = > createInjectable . isTypeOf ( ann ) ) ;
2016-12-15 12:12:40 -05:00
}
getInjectableSummary ( type : any ) : cpl . CompileTypeSummary {
2017-03-13 17:39:34 -04:00
return {
summaryKind : cpl.CompileSummaryKind.Injectable ,
type : this . _getTypeMetadata ( type , null , false )
} ;
2016-12-15 12:12:40 -05:00
}
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
getInjectableMetadata (
type : any , dependencies : any [ ] | null = null ,
throwOnUnknownDeps : boolean = true ) : cpl . CompileInjectableMetadata | null {
2016-12-15 12:12:40 -05:00
const typeSummary = this . _loadSummary ( type , cpl . CompileSummaryKind . Injectable ) ;
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
const typeMetadata = typeSummary ?
typeSummary . type :
this . _getTypeMetadata ( type , dependencies , throwOnUnknownDeps ) ;
const annotations : Injectable [ ] =
this . _reflector . annotations ( type ) . filter ( ann = > createInjectable . isTypeOf ( ann ) ) ;
if ( annotations . length === 0 ) {
return null ;
2016-12-15 12:12:40 -05:00
}
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
const meta = annotations [ annotations . length - 1 ] ;
return {
symbol : type ,
type : typeMetadata ,
module : meta.scope || undefined ,
useValue : meta.useValue ,
useClass : meta.useClass ,
useExisting : meta.useExisting ,
useFactory : meta.useFactory ,
deps : meta.deps ,
} ;
2016-12-15 12:12:40 -05:00
}
2017-08-16 12:00:03 -04:00
private _getTypeMetadata ( type : Type , dependencies : any [ ] | null = null , throwOnUnknownDeps = true ) :
cpl . CompileTypeMetadata {
2016-11-23 12:42:19 -05:00
const identifier = this . _getIdentifierMetadata ( type ) ;
2016-11-30 13:52:51 -05:00
return {
2016-11-10 17:07:30 -05:00
reference : identifier.reference ,
2017-03-13 17:39:34 -04:00
diDeps : this._getDependenciesMetadata ( identifier . reference , dependencies , throwOnUnknownDeps ) ,
2017-05-18 16:46:51 -04:00
lifecycleHooks : getAllLifecycleHooks ( this . _reflector , identifier . reference ) ,
2016-11-30 13:52:51 -05:00
} ;
2016-01-06 17:13:44 -05:00
}
2017-03-24 12:59:58 -04:00
private _getFactoryMetadata ( factory : Function , dependencies : any [ ] | null = null ) :
2016-06-18 12:42:34 -04:00
cpl . CompileFactoryMetadata {
2016-06-28 12:54:42 -04:00
factory = resolveForwardRef ( factory ) ;
2016-11-30 13:52:51 -05:00
return { reference : factory , diDeps : this._getDependenciesMetadata ( factory , dependencies ) } ;
2016-01-06 17:13:44 -05:00
}
2016-11-10 17:07:30 -05:00
/ * *
* Gets the metadata for the given pipe .
2017-02-05 17:22:06 -05:00
* This assumes ` loadNgModuleDirectiveAndPipeMetadata ` has been called first .
2016-11-10 17:07:30 -05:00
* /
2017-03-24 12:59:58 -04:00
getPipeMetadata ( pipeType : any ) : cpl . CompilePipeMetadata | null {
2016-11-10 17:07:30 -05:00
const pipeMeta = this . _pipeCache . get ( pipeType ) ;
if ( ! pipeMeta ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2017-02-05 17:22:06 -05:00
` Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${ stringifyType ( pipeType ) } . ` ) ,
2016-12-06 20:11:09 -05:00
pipeType ) ;
2016-11-10 17:07:30 -05:00
}
2017-03-24 12:59:58 -04:00
return pipeMeta || null ;
2016-11-10 17:07:30 -05:00
}
2016-09-14 18:58:18 -04:00
2016-11-10 19:27:53 -05:00
getPipeSummary ( pipeType : any ) : cpl . CompilePipeSummary {
2016-12-02 13:08:46 -05:00
const pipeSummary =
< cpl.CompilePipeSummary > this . _loadSummary ( pipeType , cpl . CompileSummaryKind . Pipe ) ;
2016-11-10 19:27:53 -05:00
if ( ! pipeSummary ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Illegal state: Could not load the summary for pipe ${ stringifyType ( pipeType ) } . ` ) ,
2016-12-06 20:11:09 -05:00
pipeType ) ;
2016-11-10 19:27:53 -05:00
}
return pipeSummary ;
}
2016-11-16 13:22:11 -05:00
getOrLoadPipeMetadata ( pipeType : any ) : cpl . CompilePipeMetadata {
let pipeMeta = this . _pipeCache . get ( pipeType ) ;
2016-11-10 17:07:30 -05:00
if ( ! pipeMeta ) {
2016-11-16 13:22:11 -05:00
pipeMeta = this . _loadPipeMetadata ( pipeType ) ;
2015-09-14 18:59:09 -04:00
}
2016-11-16 13:22:11 -05:00
return pipeMeta ;
}
private _loadPipeMetadata ( pipeType : any ) : cpl . CompilePipeMetadata {
pipeType = resolveForwardRef ( pipeType ) ;
2017-03-24 12:59:58 -04:00
const pipeAnnotation = this . _pipeResolver . resolve ( pipeType ) ! ;
2016-11-10 17:07:30 -05:00
2016-11-16 13:22:11 -05:00
const pipeMeta = new cpl . CompilePipeMetadata ( {
2016-11-23 12:42:19 -05:00
type : this . _getTypeMetadata ( pipeType ) ,
2016-11-16 13:22:11 -05:00
name : pipeAnnotation.name ,
2017-03-24 12:59:58 -04:00
pure : ! ! pipeAnnotation . pure
2016-11-10 17:07:30 -05:00
} ) ;
2016-11-16 13:22:11 -05:00
this . _pipeCache . set ( pipeType , pipeMeta ) ;
2016-12-02 13:08:46 -05:00
this . _summaryCache . set ( pipeType , pipeMeta . toSummary ( ) ) ;
2016-11-16 13:22:11 -05:00
return pipeMeta ;
2015-09-14 18:59:09 -04:00
}
2017-03-13 17:39:34 -04:00
private _getDependenciesMetadata (
2017-08-16 12:00:03 -04:00
typeOrFunc : Type | Function , dependencies : any [ ] | null ,
2017-03-13 17:39:34 -04:00
throwOnUnknownDeps = true ) : cpl . CompileDiDependencyMetadata [ ] {
2016-06-09 19:07:06 -04:00
let hasUnknownDeps = false ;
2016-11-12 08:08:58 -05:00
const params = dependencies || this . _reflector . parameters ( typeOrFunc ) || [ ] ;
2016-09-14 18:58:18 -04:00
2016-11-12 08:08:58 -05:00
const dependenciesMetadata : cpl.CompileDiDependencyMetadata [ ] = params . map ( ( param ) = > {
2016-02-18 13:53:21 -05:00
let isAttribute = false ;
let isHost = false ;
let isSelf = false ;
let isSkipSelf = false ;
let isOptional = false ;
2016-10-24 16:28:23 -04:00
let token : any = null ;
2016-09-14 18:58:18 -04:00
if ( Array . isArray ( param ) ) {
param . forEach ( ( paramEntry ) = > {
2017-08-16 12:00:03 -04:00
if ( createHost . isTypeOf ( paramEntry ) ) {
2016-06-08 19:38:52 -04:00
isHost = true ;
2017-08-16 12:00:03 -04:00
} else if ( createSelf . isTypeOf ( paramEntry ) ) {
2016-06-08 19:38:52 -04:00
isSelf = true ;
2017-08-16 12:00:03 -04:00
} else if ( createSkipSelf . isTypeOf ( paramEntry ) ) {
2016-06-08 19:38:52 -04:00
isSkipSelf = true ;
2017-08-16 12:00:03 -04:00
} else if ( createOptional . isTypeOf ( paramEntry ) ) {
2016-06-08 19:38:52 -04:00
isOptional = true ;
2017-08-16 12:00:03 -04:00
} else if ( createAttribute . isTypeOf ( paramEntry ) ) {
2016-06-08 19:38:52 -04:00
isAttribute = true ;
token = paramEntry . attributeName ;
2017-08-16 12:00:03 -04:00
} else if ( createInject . isTypeOf ( paramEntry ) ) {
2016-06-08 19:38:52 -04:00
token = paramEntry . token ;
2017-08-16 12:00:03 -04:00
} else if (
createInjectionToken . isTypeOf ( paramEntry ) || paramEntry instanceof StaticSymbol ) {
2017-02-20 19:20:45 -05:00
token = paramEntry ;
2016-12-27 17:02:28 -05:00
} else if ( isValidType ( paramEntry ) && token == null ) {
2016-06-08 19:38:52 -04:00
token = paramEntry ;
}
} ) ;
2016-01-06 17:13:44 -05:00
} else {
2016-02-18 13:53:21 -05:00
token = param ;
2016-01-06 17:13:44 -05:00
}
2016-12-27 17:02:28 -05:00
if ( token == null ) {
2016-06-09 19:07:06 -04:00
hasUnknownDeps = true ;
2017-03-24 12:59:58 -04:00
return null ! ;
2016-01-06 17:13:44 -05:00
}
2016-09-14 18:58:18 -04:00
2016-11-30 13:52:51 -05:00
return {
2016-09-14 18:58:18 -04:00
isAttribute ,
isHost ,
isSelf ,
isSkipSelf ,
isOptional ,
2016-11-10 17:07:30 -05:00
token : this._getTokenMetadata ( token )
2016-11-30 13:52:51 -05:00
} ;
2016-02-18 13:53:21 -05:00
2016-01-06 17:13:44 -05:00
} ) ;
2016-06-09 19:07:06 -04:00
if ( hasUnknownDeps ) {
2016-11-12 08:08:58 -05:00
const depsTokens =
2016-12-15 12:12:40 -05:00
dependenciesMetadata . map ( ( dep ) = > dep ? stringifyType ( dep . token ) : '?' ) . join ( ', ' ) ;
2017-03-13 17:39:34 -04:00
const message =
` Can't resolve all parameters for ${ stringifyType ( typeOrFunc ) } : ( ${ depsTokens } ). ` ;
2017-09-26 16:40:47 -04:00
if ( throwOnUnknownDeps || this . _config . strictInjectionParameters ) {
2017-03-13 17:39:34 -04:00
this . _reportError ( syntaxError ( message ) , typeOrFunc ) ;
} else {
2017-09-26 16:40:47 -04:00
this . _console . warn ( ` Warning: ${ message } This will become an error in Angular v6.x ` ) ;
2017-03-13 17:39:34 -04:00
}
2016-06-09 19:07:06 -04:00
}
return dependenciesMetadata ;
2016-01-06 17:13:44 -05:00
}
2016-11-10 17:07:30 -05:00
private _getTokenMetadata ( token : any ) : cpl . CompileTokenMetadata {
2016-01-06 17:13:44 -05:00
token = resolveForwardRef ( token ) ;
2016-09-14 18:58:18 -04:00
let compileToken : cpl.CompileTokenMetadata ;
2016-10-19 16:42:39 -04:00
if ( typeof token === 'string' ) {
2016-11-30 13:52:51 -05:00
compileToken = { value : token } ;
2016-01-06 17:13:44 -05:00
} else {
2016-11-30 13:52:51 -05:00
compileToken = { identifier : { reference : token } } ;
2016-01-06 17:13:44 -05:00
}
return compileToken ;
}
2016-11-10 17:07:30 -05:00
private _getProvidersMetadata (
2016-12-27 12:36:47 -05:00
providers : Provider [ ] , targetEntryComponents : cpl.CompileEntryComponentMetadata [ ] ,
2016-12-06 20:11:09 -05:00
debugInfo? : string , compileProviders : cpl.CompileProviderMetadata [ ] = [ ] ,
type ? : any ) : cpl . CompileProviderMetadata [ ] {
2016-08-23 19:18:41 -04:00
providers . forEach ( ( provider : any , providerIdx : number ) = > {
2016-09-14 18:58:18 -04:00
if ( Array . isArray ( provider ) ) {
2016-11-30 13:52:51 -05:00
this . _getProvidersMetadata ( provider , targetEntryComponents , debugInfo , compileProviders ) ;
} else {
provider = resolveForwardRef ( provider ) ;
2017-03-24 12:59:58 -04:00
let providerMeta : cpl.ProviderMeta = undefined ! ;
2016-12-27 20:05:14 -05:00
if ( provider && typeof provider === 'object' && provider . hasOwnProperty ( 'provide' ) ) {
2016-12-27 17:02:28 -05:00
this . _validateProvider ( provider ) ;
2016-11-30 13:52:51 -05:00
providerMeta = new cpl . ProviderMeta ( provider . provide , provider ) ;
} else if ( isValidType ( provider ) ) {
providerMeta = new cpl . ProviderMeta ( provider , { useClass : provider } ) ;
2016-12-27 20:05:14 -05:00
} else if ( provider === void 0 ) {
2017-01-27 16:19:00 -05:00
this . _reportError ( syntaxError (
2016-12-27 20:05:14 -05:00
` Encountered undefined provider! Usually this means you have a circular dependencies (might be caused by using 'barrel' index.ts files. ` ) ) ;
2017-04-04 11:30:38 -04:00
return ;
2016-07-07 13:05:55 -04:00
} else {
2016-11-30 13:52:51 -05:00
const providersInfo =
( < string [ ] > providers . reduce (
( soFar : string [ ] , seenProvider : any , seenProviderIdx : number ) = > {
if ( seenProviderIdx < providerIdx ) {
2016-12-15 12:12:40 -05:00
soFar . push ( ` ${ stringifyType ( seenProvider ) } ` ) ;
2016-11-30 13:52:51 -05:00
} else if ( seenProviderIdx == providerIdx ) {
2016-12-15 12:12:40 -05:00
soFar . push ( ` ? ${ stringifyType ( seenProvider ) } ? ` ) ;
2016-11-30 13:52:51 -05:00
} else if ( seenProviderIdx == providerIdx + 1 ) {
soFar . push ( '...' ) ;
}
return soFar ;
} ,
[ ] ) )
. join ( ', ' ) ;
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-06 20:11:09 -05:00
` Invalid ${ debugInfo ? debugInfo : 'provider' } - only instances of Provider and Type are allowed, got: [ ${ providersInfo } ] ` ) ,
type ) ;
2017-04-04 11:30:38 -04:00
return ;
2016-11-30 13:52:51 -05:00
}
2017-05-18 16:46:51 -04:00
if ( providerMeta . token ===
this . _reflector . resolveExternalReference ( Identifiers . ANALYZE_FOR_ENTRY_COMPONENTS ) ) {
2016-12-06 20:11:09 -05:00
targetEntryComponents . push ( . . . this . _getEntryComponentsFromProvider ( providerMeta , type ) ) ;
2016-11-30 13:52:51 -05:00
} else {
compileProviders . push ( this . getProviderMetadata ( providerMeta ) ) ;
2016-07-07 13:05:55 -04:00
}
}
} ) ;
return compileProviders ;
}
2016-12-27 17:02:28 -05:00
private _validateProvider ( provider : any ) : void {
if ( provider . hasOwnProperty ( 'useClass' ) && provider . useClass == null ) {
2017-01-27 16:19:00 -05:00
this . _reportError ( syntaxError (
2016-12-27 17:02:28 -05:00
` Invalid provider for ${ stringifyType ( provider . provide ) } . useClass cannot be ${ provider . useClass } .
Usually it happens when :
1 . There ' s a circular dependency ( might be caused by using index . ts ( barrel ) files ) .
2 . Class was used before it was declared . Use forwardRef in this case . ` ));
}
}
2016-12-06 20:11:09 -05:00
private _getEntryComponentsFromProvider ( provider : cpl.ProviderMeta , type ? : any ) :
2016-12-27 12:36:47 -05:00
cpl . CompileEntryComponentMetadata [ ] {
const components : cpl.CompileEntryComponentMetadata [ ] = [ ] ;
2016-09-14 18:58:18 -04:00
const collectedIdentifiers : cpl.CompileIdentifierMetadata [ ] = [ ] ;
2016-07-07 13:05:55 -04:00
if ( provider . useFactory || provider . useExisting || provider . useClass ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError ( ` The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue! ` ) , type ) ;
2016-12-06 20:11:09 -05:00
return [ ] ;
2016-07-07 13:05:55 -04:00
}
2016-09-14 18:58:18 -04:00
2016-07-07 13:05:55 -04:00
if ( ! provider . multi ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError ( ` The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'! ` ) ,
2016-12-15 16:07:12 -05:00
type ) ;
2016-12-06 20:11:09 -05:00
return [ ] ;
2016-07-07 13:05:55 -04:00
}
2016-09-14 18:58:18 -04:00
2016-11-30 13:52:51 -05:00
extractIdentifiers ( provider . useValue , collectedIdentifiers ) ;
2016-07-07 13:05:55 -04:00
collectedIdentifiers . forEach ( ( identifier ) = > {
2017-02-06 20:55:58 -05:00
const entry = this . _getEntryComponentMetadata ( identifier . reference , false ) ;
2016-12-27 12:36:47 -05:00
if ( entry ) {
components . push ( entry ) ;
2016-07-07 13:05:55 -04:00
}
2016-01-06 17:13:44 -05:00
} ) ;
2016-07-07 13:05:55 -04:00
return components ;
2016-01-06 17:13:44 -05:00
}
2017-02-06 20:55:58 -05:00
private _getEntryComponentMetadata ( dirType : any , throwIfNotFound = true ) :
2017-03-24 12:59:58 -04:00
cpl . CompileEntryComponentMetadata | null {
2016-12-27 12:36:47 -05:00
const dirMeta = this . getNonNormalizedDirectiveMetadata ( dirType ) ;
2017-02-06 20:55:58 -05:00
if ( dirMeta && dirMeta . metadata . isComponent ) {
2017-03-24 12:59:58 -04:00
return { componentType : dirType , componentFactory : dirMeta.metadata.componentFactory ! } ;
2016-12-27 12:36:47 -05:00
}
2017-03-30 17:51:29 -04:00
const dirSummary =
< cpl.CompileDirectiveSummary > this . _loadSummary ( dirType , cpl . CompileSummaryKind . Directive ) ;
if ( dirSummary && dirSummary . isComponent ) {
2017-03-24 12:59:58 -04:00
return { componentType : dirType , componentFactory : dirSummary.componentFactory ! } ;
2017-03-30 17:51:29 -04:00
}
2017-02-06 20:55:58 -05:00
if ( throwIfNotFound ) {
throw syntaxError ( ` ${ dirType . name } cannot be used as an entry component. ` ) ;
}
2017-03-24 12:59:58 -04:00
return null ;
2016-12-27 12:36:47 -05:00
}
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
private _getInjectableTypeMetadata ( type : Type , dependencies : any [ ] | null = null ) :
cpl . CompileTypeMetadata {
const typeSummary = this . _loadSummary ( type , cpl . CompileSummaryKind . Injectable ) ;
if ( typeSummary ) {
return typeSummary . type ;
}
return this . _getTypeMetadata ( type , dependencies ) ;
}
2016-08-15 22:37:42 -04:00
getProviderMetadata ( provider : cpl.ProviderMeta ) : cpl . CompileProviderMetadata {
2017-03-24 12:59:58 -04:00
let compileDeps : cpl.CompileDiDependencyMetadata [ ] = undefined ! ;
let compileTypeMetadata : cpl.CompileTypeMetadata = null ! ;
let compileFactoryMetadata : cpl.CompileFactoryMetadata = null ! ;
2016-11-30 13:52:51 -05:00
let token : cpl.CompileTokenMetadata = this . _getTokenMetadata ( provider . token ) ;
2016-06-18 12:42:34 -04:00
2016-09-14 18:58:18 -04:00
if ( provider . useClass ) {
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
compileTypeMetadata =
this . _getInjectableTypeMetadata ( provider . useClass , provider . dependencies ) ;
2016-06-18 12:42:34 -04:00
compileDeps = compileTypeMetadata . diDeps ;
2016-11-30 13:52:51 -05:00
if ( provider . token === provider . useClass ) {
// use the compileTypeMetadata as it contains information about lifecycleHooks...
token = { identifier : compileTypeMetadata } ;
}
2016-09-14 18:58:18 -04:00
} else if ( provider . useFactory ) {
2016-11-23 12:42:19 -05:00
compileFactoryMetadata = this . _getFactoryMetadata ( provider . useFactory , provider . dependencies ) ;
2016-06-18 12:42:34 -04:00
compileDeps = compileFactoryMetadata . diDeps ;
2016-01-06 17:13:44 -05:00
}
2016-06-18 12:42:34 -04:00
2016-11-30 13:52:51 -05:00
return {
token : token ,
2016-06-18 12:42:34 -04:00
useClass : compileTypeMetadata ,
2016-11-30 13:52:51 -05:00
useValue : provider.useValue ,
2016-06-18 12:42:34 -04:00
useFactory : compileFactoryMetadata ,
2017-03-24 12:59:58 -04:00
useExisting : provider.useExisting ? this . _getTokenMetadata ( provider . useExisting ) : undefined ,
2016-01-06 17:13:44 -05:00
deps : compileDeps ,
multi : provider.multi
2016-11-30 13:52:51 -05:00
} ;
2016-01-06 17:13:44 -05:00
}
2016-11-10 17:07:30 -05:00
private _getQueriesMetadata (
2016-09-12 22:14:17 -04:00
queries : { [ key : string ] : Query } , isViewQuery : boolean ,
2017-08-16 12:00:03 -04:00
directiveType : Type ) : cpl . CompileQueryMetadata [ ] {
2016-09-14 18:58:18 -04:00
const res : cpl.CompileQueryMetadata [ ] = [ ] ;
Object . keys ( queries ) . forEach ( ( propertyName : string ) = > {
const query = queries [ propertyName ] ;
2016-06-22 20:25:42 -04:00
if ( query . isViewQuery === isViewQuery ) {
2016-11-10 17:07:30 -05:00
res . push ( this . _getQueryMetadata ( query , propertyName , directiveType ) ) ;
2016-06-22 20:25:42 -04:00
}
} ) ;
2016-01-06 17:13:44 -05:00
2016-09-14 18:58:18 -04:00
return res ;
2016-09-12 12:44:20 -04:00
}
2016-09-14 18:58:18 -04:00
private _queryVarBindings ( selector : any ) : string [ ] { return selector . split ( /\s*,\s*/ ) ; }
2016-09-12 12:44:20 -04:00
2017-08-16 12:00:03 -04:00
private _getQueryMetadata ( q : Query , propertyName : string , typeOrFunc : Type | Function ) :
2016-06-08 19:38:52 -04:00
cpl . CompileQueryMetadata {
2016-11-12 08:08:58 -05:00
let selectors : cpl.CompileTokenMetadata [ ] ;
2016-09-14 18:58:18 -04:00
if ( typeof q . selector === 'string' ) {
2016-11-10 17:07:30 -05:00
selectors =
this . _queryVarBindings ( q . selector ) . map ( varName = > this . _getTokenMetadata ( varName ) ) ;
2016-01-06 17:13:44 -05:00
} else {
2016-09-14 18:58:18 -04:00
if ( ! q . selector ) {
2016-12-06 20:11:09 -05:00
this . _reportError (
2017-01-27 16:19:00 -05:00
syntaxError (
2016-12-15 12:12:40 -05:00
` Can't construct a query for the property " ${ propertyName } " of " ${ stringifyType ( typeOrFunc ) } " since the query selector wasn't defined. ` ) ,
2016-12-06 20:11:09 -05:00
typeOrFunc ) ;
2017-04-04 11:30:38 -04:00
selectors = [ ] ;
} else {
selectors = [ this . _getTokenMetadata ( q . selector ) ] ;
2016-06-04 22:46:03 -04:00
}
2016-01-06 17:13:44 -05:00
}
2016-09-14 18:58:18 -04:00
2016-11-30 13:52:51 -05:00
return {
2016-09-14 18:58:18 -04:00
selectors ,
2016-01-06 17:13:44 -05:00
first : q.first ,
2016-09-14 18:58:18 -04:00
descendants : q.descendants , propertyName ,
2017-03-24 12:59:58 -04:00
read : q.read ? this . _getTokenMetadata ( q . read ) : null !
2016-11-30 13:52:51 -05:00
} ;
2016-01-06 17:13:44 -05:00
}
2016-12-06 20:11:09 -05:00
private _reportError ( error : any , type ? : any , otherType? : any ) {
if ( this . _errorCollector ) {
this . _errorCollector ( error , type ) ;
if ( otherType ) {
this . _errorCollector ( error , otherType ) ;
}
} else {
throw error ;
}
}
2015-09-14 18:59:09 -04:00
}
2016-07-18 06:50:31 -04:00
function flattenArray ( tree : any [ ] , out : Array < any > = [ ] ) : Array < any > {
if ( tree ) {
2016-09-14 18:58:18 -04:00
for ( let i = 0 ; i < tree . length ; i ++ ) {
const item = resolveForwardRef ( tree [ i ] ) ;
if ( Array . isArray ( item ) ) {
2016-07-18 06:50:31 -04:00
flattenArray ( item , out ) ;
} else {
out . push ( item ) ;
}
2015-09-14 18:59:09 -04:00
}
}
2016-06-22 17:06:23 -04:00
return out ;
2015-09-14 18:59:09 -04:00
}
2016-10-28 17:08:54 -04:00
function dedupeArray ( array : any [ ] ) : Array < any > {
if ( array ) {
return Array . from ( new Set ( array ) ) ;
}
return [ ] ;
}
function flattenAndDedupeArray ( tree : any [ ] ) : Array < any > {
return dedupeArray ( flattenArray ( tree ) ) ;
}
2016-02-18 13:53:21 -05:00
function isValidType ( value : any ) : boolean {
2016-12-05 16:26:12 -05:00
return ( value instanceof StaticSymbol ) || ( value instanceof Type ) ;
2016-02-18 13:53:21 -05:00
}
2016-11-30 13:52:51 -05:00
function extractIdentifiers ( value : any , targetIdentifiers : cpl.CompileIdentifierMetadata [ ] ) {
visitValue ( value , new _CompileValueConverter ( ) , targetIdentifiers ) ;
2016-04-30 19:13:03 -04:00
}
class _CompileValueConverter extends ValueTransformer {
2016-07-07 13:05:55 -04:00
visitOther ( value : any , targetIdentifiers : cpl.CompileIdentifierMetadata [ ] ) : any {
2016-11-30 13:52:51 -05:00
targetIdentifiers . push ( { reference : value } ) ;
2016-04-30 19:13:03 -04:00
}
}
2016-12-15 12:12:40 -05:00
function stringifyType ( type : any ) : string {
if ( type instanceof StaticSymbol ) {
return ` ${ type . name } in ${ type . filePath } ` ;
} else {
return stringify ( type ) ;
}
2016-12-15 20:07:26 -05:00
}
2017-01-27 16:19:00 -05:00
/ * *
* Indicates that a component is still being loaded in a synchronous compile .
* /
2017-08-16 12:00:03 -04:00
function componentStillLoadingError ( compType : Type ) {
2017-01-27 16:19:00 -05:00
const error =
Error ( ` Can't compile synchronously as ${ stringify ( compType ) } is still being loaded! ` ) ;
2017-08-16 12:00:03 -04:00
( error as any ) [ ERROR_COMPONENT_TYPE ] = compType ;
2017-01-27 16:19:00 -05:00
return error ;
2017-02-06 20:55:58 -05:00
}