Skip to content

Commit

Permalink
feat(core/util/bundler): clean code and support nested arrays resolution
Browse files Browse the repository at this point in the history
- export bundler function as Public API
  • Loading branch information
Hotell committed Jun 12, 2016
1 parent 272e22a commit 80608ed
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 140 deletions.
1 change: 1 addition & 0 deletions core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './src/core/di';
export { bundle } from './src/core/util'
export {
Directive,
Component,
Expand Down
150 changes: 18 additions & 132 deletions src/core/util/bundler.ts
Original file line number Diff line number Diff line change
@@ -1,158 +1,44 @@
import { global, isArray, isString, isType } from '../../facade/lang';
import { global } 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';
import { _isTypeRegistered, _normalizeProviders, _getNgModuleMetadataByType } from '../di/reflective_provider';
import { ListWrapper } from '../../facade/collections';

export function bundle( ComponentClass: Type, otherProviders: any[] = [], Module?: ng.IModule ): ng.IModule {
export function bundle( ComponentClass: Type, otherProviders: any[] = [], NgModule?: ng.IModule ): ng.IModule {

const ngModuleName = getInjectableName( ComponentClass );
const ngModule = Module || global.angular.module( ngModuleName, [] );
const ngModule = NgModule || 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 );
const [cmpName,cmpFactoryFn] = provide( ComponentClass );
const { providerName, providerMethod, moduleMethod } = _getNgModuleMetadataByType( ComponentClass );

if ( isTypeRegistered( cmpProvider[ 0 ], ngModule, '$compileProvider', 'directive' ) ) {
if ( _isTypeRegistered( cmpName, ngModule, providerName, providerMethod ) ) {
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;
}
ngModule[moduleMethod]( cmpName, cmpFactoryFn );

if ( isProviderLiteral( ViewProviderType ) ) {
const provider = createProvider( ViewProviderType );
const { method, name, value } = resolveReflectiveProvider( provider );
if ( !isTypeRegistered( name, ngModule, '$provide', method ) ) {
ngModule[ method ]( name, value );
}
return;
}
// 1. process component/directive decorator providers/viewProviders/pipes
_normalizeProviders( ngModule, providers );
_normalizeProviders( ngModule, viewProviders );
_normalizeProviders( ngModule, pipes );

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 );
ListWrapper.flattenDeep(directives).forEach( ( directiveType: Type ) => {
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 ) ) {

}
} );
// - directives can't be registered as via global providers only @Injectable,@Pipe,{provide:any,use*:any}
// registerProviders(ngModule, otherProviders);
_normalizeProviders( ngModule, otherProviders );

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 {

}
119 changes: 111 additions & 8 deletions test/core/util/bundler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@ 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';
import { Directive } from '../../../src/core/directives/decorators';

describe( `util/bundler`, () => {

let sandbox: Sinon.SinonSandbox;
global.angular = createNgModule() as any;
beforeEach( () => {
global.angular = createNgModule() as any;
sandbox = sinon.sandbox.create();
} );
beforeEach( () => {


} );
afterEach( () => {
sandbox.restore();
Expand Down Expand Up @@ -90,7 +87,7 @@ describe( `util/bundler`, () => {
selector: 'app',
directives: [ ChildOneComponent, ChildTwoComponent ],
providers: [ MySingleton, { provide: 'tokenAsClass', useClass: ViaProviderLiteralService } ],
template: `Hello App
template: `Hello App
<child-one></child-one>
<child-two></child-two>
`
Expand Down Expand Up @@ -130,17 +127,123 @@ describe( `util/bundler`, () => {
const actual = _invokeQueueToCompare( actualInvokeQueue, false );
const expected = _invokeQueueToCompare( expectedInvokeQueue, false );

// console.log( actual );
// console.log( 'actual:',actual );
// console.log( '========' );
// console.log( expected );
// console.log( 'expected:',expected );

expect( actual ).to.deep.equal( expected );
expect( ngModule.requires ).to.deep.equal( [ 'ui.bootstrap.modal','3rdParty' ] );

} );

it( `should support nested providers and normalize them`, () => {

const PluginFooDirectives = [ NestedComponent ];
const PluginFooProviders = [ MyService, MySingleton ];
const PluginFooPipes = [ UpsPipe ];

@Directive({selector:'[yo]'})
class YoDirective {}

@Component( {
selector: 'app-with-plugin',
template: 'hello',
directives: [ PluginFooDirectives, YoDirective ],
providers: [ PluginFooProviders, MyPrivateService ],
pipes: [ PluginFooPipes ]
} )
class AppWithPluginComponent {}

const ngModule = bundle( AppWithPluginComponent );

const expectedInvokeQueue = [
[ '$compileProvider', 'directive', provide( AppWithPluginComponent ) ],
[ '$provide', 'service', provide( MyService ) ],
[ '$provide', 'service', provide( MySingleton ) ],
[ '$provide', 'service', provide( MyPrivateService ) ],
[ '$filterProvider', 'register', provide( UpsPipe ) ],
[ '$compileProvider', 'directive', provide( NestedComponent ) ],
[ '$compileProvider', 'directive', provide( YoDirective ) ]
];
const actualInvokeQueue = (ngModule as any)._invokeQueue;
const actual = _invokeQueueToCompare( actualInvokeQueue, false );
const expected = _invokeQueueToCompare( expectedInvokeQueue, false );

// console.log( 'actual:',actual );
// console.log( '========' );
// console.log( 'expected:',expected );

expect( actual ).to.deep.equal( expected );

} );

it( `should allow ngModule.config within otherProviders setup`, () => {

@Injectable()
class MyDynamicService {}

configPhase.$inject = ['$provide'];
function configPhase($provide: ng.auto.IProvideService){
$provide.service( ...provide( MyDynamicService ) );
$provide.factory(
...provide(
'promiseWrapper',
{ deps: [ '$q' ], useFactory: ( value )=>( $q )=>$q.resolve() }
)
);
}

@Component( {
selector: 'pure-app',
template: 'hello'
} )
class PureAppComponent {}

const ngModule = bundle( PureAppComponent, [ configPhase ] );

const expectedInvokeQueue = [
[ '$compileProvider', 'directive', provide( PureAppComponent ) ],
[ '$provide', 'service', provide( MyDynamicService ) ],
[
'$provide', 'factory', provide(
'promiseWrapper',
{ deps: [ '$q' ], useFactory: ( value )=>( $q )=>$q.resolve() }
)
],
];
const expectedConfigBlocks = [
[ '$injector', 'invoke', [ configPhase ] ]
];

const actualConfigBlocks = (ngModule as any)._configBlocks;
const actualInvokeQueue = (ngModule as any)._invokeQueue;

expect( actualConfigBlocks ).to.deep.equal( expectedConfigBlocks );

_execConfigBlocks(ngModule);

const actual = _invokeQueueToCompare( actualInvokeQueue, false );
const expected = _invokeQueueToCompare( expectedInvokeQueue, false );

// console.log( 'actual:',actual );
// console.log( '========' );
// console.log( 'expected:',expected );

expect( actual ).to.deep.equal( expected );

} );

} );

function _execConfigBlocks( ngModule: any ) {
const configBlocks = ngModule._configBlocks;
configBlocks.forEach( ( config )=> {
const registeredFnArr = config[ 2 ];
const fn = registeredFnArr[ 0 ];
fn(ngModule);
} );
}

function _invokeQueueToCompare( invokeQueue: any, shouldSort = true ): [string,string,string] {

const processedQueue = invokeQueue
Expand Down

0 comments on commit 80608ed

Please sign in to comment.