-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core/util): add bundler helper for collecting DI Tree via compon…
…ents
- Loading branch information
Showing
3 changed files
with
314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { global, isArray, isString, isType } from '../../facade/lang'; | ||
import { reflector } from '../reflection/reflection'; | ||
import { ComponentMetadata } from '../directives/metadata_directives'; | ||
import { getInjectableName, provide } from '../di/provider'; | ||
import { isProviderLiteral, createProvider, ProviderLiteral } from '../di/provider_util'; | ||
import { resolveReflectiveProvider } from '../di/reflective_provider'; | ||
import { getNgModuleMethodByType } from '../di/provider'; | ||
|
||
export function bundle( ComponentClass: Type, otherProviders: any[] = [], Module?: ng.IModule ): ng.IModule { | ||
|
||
const ngModuleName = getInjectableName( ComponentClass ); | ||
const ngModule = Module || global.angular.module( ngModuleName, [] ); | ||
const annotations = reflector.annotations( ComponentClass ); | ||
const cmpAnnotation: ComponentMetadata = annotations[ 0 ]; | ||
const { directives = [], pipes = [], providers = [], viewProviders = [] }={} = cmpAnnotation; | ||
|
||
// console.log( 'directives:', directives ); | ||
// console.log( 'pipes:', pipes ); | ||
// console.log( 'providers:', providers ); | ||
// console.log( 'viewProviders:', viewProviders ); | ||
|
||
|
||
// process component | ||
const cmpProvider = provide( ComponentClass ); | ||
|
||
if ( isTypeRegistered( cmpProvider[ 0 ], ngModule, '$compileProvider', 'directive' ) ) { | ||
return ngModule; | ||
} | ||
|
||
ngModule.directive( cmpProvider[ 0 ], cmpProvider[ 1 ] ); | ||
|
||
|
||
// 1. process component tree | ||
|
||
// step through all providers | ||
providers.forEach( ( ProviderType ) => { | ||
|
||
// @TODO | ||
// recursive | ||
if ( isArray( ProviderType ) ) { | ||
return; | ||
} | ||
|
||
if ( isString( ProviderType ) ) { | ||
ngModule.requires.push( ProviderType ); | ||
return; | ||
} | ||
|
||
if ( isProviderLiteral( ProviderType ) ) { | ||
const provider = createProvider( ProviderType ); | ||
const { method, name, value } = resolveReflectiveProvider( provider ); | ||
if ( !isTypeRegistered( name, ngModule, '$provide', method ) ) { | ||
ngModule[ method ]( name, value ); | ||
} | ||
return; | ||
} | ||
|
||
const serviceProvider = provide( ProviderType ); | ||
if ( !isTypeRegistered( serviceProvider[ 0 ], ngModule, '$provide', 'service' ) ) { | ||
ngModule.service( ...provide( ProviderType ) ); | ||
} | ||
|
||
} ); | ||
// step through all viewProviders | ||
viewProviders.forEach( ( ViewProviderType ) => { | ||
|
||
// @TODO | ||
// recursive | ||
if ( isArray( ViewProviderType ) ) { | ||
return; | ||
} | ||
|
||
if ( isString( ViewProviderType ) ) { | ||
ngModule.requires.push( ViewProviderType ); | ||
return; | ||
} | ||
|
||
if ( isProviderLiteral( ViewProviderType ) ) { | ||
const provider = createProvider( ViewProviderType ); | ||
const { method, name, value } = resolveReflectiveProvider( provider ); | ||
if ( !isTypeRegistered( name, ngModule, '$provide', method ) ) { | ||
ngModule[ method ]( name, value ); | ||
} | ||
return; | ||
} | ||
|
||
const serviceProvider = provide( ViewProviderType ); | ||
if ( !isTypeRegistered( serviceProvider[ 0 ], ngModule, '$provide', 'service' ) ) { | ||
ngModule.service( ...provide( ViewProviderType ) ); | ||
} | ||
|
||
} ); | ||
// step through all pipes | ||
pipes.forEach( ( PipeType: Type ) => { | ||
// @TODO | ||
// recursive | ||
if ( isArray( PipeType ) ) { | ||
return; | ||
} | ||
|
||
const pipeProvider = provide( PipeType ); | ||
if ( !isTypeRegistered( pipeProvider[ 0 ], ngModule, '$filterProvider', 'register' ) ) { | ||
ngModule.filter( ...provide( PipeType ) ); | ||
} | ||
} ); | ||
// step through all directives | ||
directives.forEach( ( directiveType: Type ) => { | ||
return bundle( directiveType, [], ngModule ); | ||
} ); | ||
|
||
// 2. process otherProviders argument | ||
// - providers can be string(ngModule reference), Type, StringMap(providerLiteral) | ||
otherProviders.forEach( ( providerType: string|Type|ProviderLiteral|any[] ) => { | ||
if ( isString( providerType ) ) { | ||
ngModule.requires.push( providerType ); | ||
} | ||
if ( isType( providerType ) ) { | ||
ngModule[ getNgModuleMethodByType(providerType) ]( ...provide(providerType) ); | ||
} | ||
if ( isProviderLiteral( providerType ) ) { | ||
const provider = createProvider( providerType ); | ||
const { method, name, value } = resolveReflectiveProvider( provider ); | ||
ngModule[ method ]( name, value ); | ||
} | ||
// @TODO | ||
// recursive | ||
if ( isArray( providerType ) ) { | ||
|
||
} | ||
} ); | ||
|
||
return ngModule; | ||
|
||
} | ||
|
||
function isTypeRegistered( | ||
findRegisteredType: string, | ||
ngModule: ng.IModule, | ||
instanceType: string, | ||
methodName: string | ||
): boolean { | ||
const invokeQueue: any[] = (ngModule as any)._invokeQueue; | ||
const types = invokeQueue | ||
.filter( ( [type,fnName]:[string,string] ) => { | ||
return type === instanceType && fnName === methodName; | ||
} ) | ||
.map( ( [type,fnName, registeredProvider]:[string,string,[string,any]] ) => { | ||
return registeredProvider | ||
} ); | ||
|
||
return types.some( ( [typeName,typeFn] )=> { | ||
return findRegisteredType === typeName; | ||
} ) | ||
} | ||
|
||
function registerProvider( ngModule: ng.IModule, provider: any ): void { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import * as sinon from 'sinon'; | ||
import { expect } from 'chai'; | ||
import { global } from '../../../src/facade/lang'; | ||
import { Component } from '../../../src/core/directives/decorators'; | ||
import { createNgModule } from '../../utils'; | ||
import { provide } from '../../../src/core/di'; | ||
import { bundle } from '../../../src/core/util/bundler'; | ||
import { Pipe } from '../../../src/core/pipes/decorators'; | ||
import { Injectable } from '../../../src/core/di/decorators'; | ||
import { OpaqueToken } from '../../../src/core/di/opaque_token'; | ||
|
||
describe( `util/bundler`, () => { | ||
|
||
let sandbox: Sinon.SinonSandbox; | ||
global.angular = createNgModule() as any; | ||
beforeEach( () => { | ||
sandbox = sinon.sandbox.create(); | ||
} ); | ||
beforeEach( () => { | ||
|
||
|
||
} ); | ||
afterEach( () => { | ||
sandbox.restore(); | ||
} ); | ||
|
||
describe( `#bundle`, () => { | ||
|
||
|
||
@Pipe( { name: 'ups' } ) | ||
class UpsPipe { | ||
transform( input: string ) {} | ||
} | ||
|
||
@Injectable() | ||
class SomeGlobalService { | ||
} | ||
|
||
@Injectable() | ||
class MySingleton { | ||
} | ||
|
||
@Injectable() | ||
class MyService { | ||
} | ||
|
||
@Injectable() | ||
class OtherService { | ||
} | ||
|
||
@Injectable() | ||
class MyPrivateService { | ||
} | ||
|
||
@Injectable() | ||
class ViaProviderLiteralService { | ||
} | ||
|
||
@Component( { | ||
selector: 'nested', | ||
template: `Im nested`, | ||
viewProviders: [ MyPrivateService ], | ||
pipes: [ UpsPipe ] | ||
} ) | ||
class NestedComponent { | ||
} | ||
|
||
@Component( { | ||
selector: 'child-one', | ||
directives: [ NestedComponent ], | ||
providers: [ MyService, 'ui.bootstrap.modal' ], | ||
pipes: [ UpsPipe ], | ||
template: `hello Im childOne <nested></nested>` | ||
} ) | ||
class ChildOneComponent { | ||
} | ||
|
||
const MyFactoryToken = new OpaqueToken( 'myFactory' ); | ||
@Component( { | ||
selector: 'child-two', | ||
directives: [ NestedComponent ], | ||
providers: [ OtherService, MyService, { provide: MyFactoryToken, deps: [ '$q' ], useFactory: ( $q )=>({}) } ], | ||
pipes: [ UpsPipe ], | ||
template: `hello Im childTwo <nested></nested>` | ||
} ) | ||
class ChildTwoComponent { | ||
} | ||
|
||
@Component( { | ||
selector: 'app', | ||
directives: [ ChildOneComponent, ChildTwoComponent ], | ||
providers: [ MySingleton, { provide: 'tokenAsClass', useClass: ViaProviderLiteralService } ], | ||
template: `Hello App | ||
<child-one></child-one> | ||
<child-two></child-two> | ||
` | ||
} ) | ||
class AppComponent { | ||
} | ||
|
||
it( `should create module which has name as root component selector`, () => { | ||
|
||
const ngModule = bundle( AppComponent ); | ||
|
||
expect( ngModule.name ).to.equal( 'app' ); | ||
expect( ngModule.requires ).to.deep.equal( ['ui.bootstrap.modal'] ); | ||
|
||
} ); | ||
|
||
it( `should parse whole component tree and register all providers,viewProviders,pipes,directives`, () => { | ||
|
||
const thirdPartyModule = createNgModule().module( '3rdParty', [] ).name; | ||
const ngModule = bundle( AppComponent, [ SomeGlobalService, thirdPartyModule ] ); | ||
|
||
const expectedInvokeQueue = [ | ||
[ '$compileProvider', 'directive', provide( AppComponent ) ], | ||
[ '$provide', 'service', provide( MySingleton ) ], | ||
[ '$provide', 'service', provide( 'tokenAsClass', { useClass: ViaProviderLiteralService } ) ], | ||
[ '$compileProvider', 'directive', provide( ChildOneComponent ) ], | ||
[ '$provide', 'service', provide( MyService ) ], | ||
[ '$filterProvider', 'register', provide( UpsPipe ) ], | ||
[ '$compileProvider', 'directive', provide( NestedComponent ) ], | ||
[ '$provide', 'service', provide( MyPrivateService ) ], | ||
[ '$compileProvider', 'directive', provide( ChildTwoComponent ) ], | ||
[ '$provide', 'service', provide( OtherService ) ], | ||
[ '$provide', 'factory', provide( MyFactoryToken, { useFactory: ()=>({}) } ) ], | ||
[ '$provide', 'service', provide( SomeGlobalService ) ] | ||
]; | ||
const actualInvokeQueue = (ngModule as any)._invokeQueue; | ||
const actual = _invokeQueueToCompare( actualInvokeQueue, false ); | ||
const expected = _invokeQueueToCompare( expectedInvokeQueue, false ); | ||
|
||
// console.log( actual ); | ||
// console.log( '========' ); | ||
// console.log( expected ); | ||
|
||
expect( actual ).to.deep.equal( expected ); | ||
expect( ngModule.requires ).to.deep.equal( [ 'ui.bootstrap.modal','3rdParty' ] ); | ||
|
||
} ); | ||
|
||
} ); | ||
|
||
function _invokeQueueToCompare( invokeQueue: any, shouldSort = true ): [string,string,string] { | ||
|
||
const processedQueue = invokeQueue | ||
.map( ( queueItem: any ) => { | ||
return [ queueItem[ 0 ], queueItem[ 1 ], queueItem[ 2 ][ 0 ] ] | ||
} ); | ||
return shouldSort | ||
? processedQueue.sort( ( a, b )=>+(a[ 0 ] > b[ 0 ]) || +(a[ 0 ] === b[ 0 ]) - 1 ) | ||
: processedQueue | ||
} | ||
|
||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters