├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── benchmark └── index.html ├── dist ├── bundle.js └── bundle.js.map ├── esdoc.json ├── examples ├── simple-amd-module │ ├── List.js │ ├── ListModel.js │ ├── ListView.js │ ├── README.md │ ├── config.js │ ├── esl.js │ ├── index.html │ ├── main.js │ └── package.json └── simple-es6-module │ ├── README.md │ ├── dist │ ├── bundle.js │ └── bundle.js.map │ ├── index.html │ ├── list.json │ ├── package.json │ ├── src │ ├── List.js │ ├── Loader.js │ ├── ThirdLoader.js │ ├── config.js │ ├── main.js │ └── third │ │ └── sdk.js │ └── webpack.config.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── scripts └── build.js ├── src ├── CircularError.js ├── DependencyTree.js ├── Injector.js ├── IoC.js ├── Loader.js ├── main.js ├── meta.js ├── plugins │ ├── AopPlugin.js │ ├── AutoPlugin.js │ ├── BasePlugin.js │ ├── ImportPlugin.js │ ├── ListPlugin.js │ ├── MapPlugin.js │ └── PropertyPlugin.js └── util.js └── test ├── assets ├── A.js ├── AutoInject.js ├── AutoInject1.js ├── B.js ├── C.js ├── D.js ├── E.js ├── F.js ├── MyFactory.js ├── MyUtil.js ├── aop │ ├── Fixture.js │ └── FixtureAspect.js ├── config.js ├── esl.js ├── import │ ├── A.js │ ├── Nest.js │ └── config.js ├── list │ ├── A.js │ ├── config.js │ └── list.js └── map │ ├── A.js │ └── config.js ├── spec ├── aop.js ├── auto.js ├── circular.js ├── import.js ├── integration.js ├── list.js └── map.js └── test-main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | node_modules 4 | test/coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo/ 2 | scripts/ 3 | test/ 4 | .travis.yml 5 | karma.conf.js 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | dist: trusty # needs Ubuntu Trusty 5 | sudo: false # no need for virtualization. 6 | addons: 7 | chrome: stable # have Travis install chrome stable. 8 | cache: 9 | directories: 10 | - node_modules 11 | script: 12 | - npm test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Exodia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | uioc ![building status](https://travis-ci.org/ecomfe/uioc.svg?branch=master) 2 | === 3 | 4 | # 关于 5 | uioc是用JavaScript写的一个轻量级IoC容器,为JavaScript应用提供了IoC功能。通过使用配置的方式管理模块依赖,最大程度的提高了模块的复用性。 6 | 7 | 在1.0版本中,增加了aop的支持,对应用从横向关注点上提供了复用支持。 8 | 9 | # 安装 10 | 11 | ```shell 12 | npm install uioc —save 13 | ``` 14 | # 基本使用 15 | 16 | ## Step 1:定义模块 17 | 18 | IoC最大的要求之一就是不要在模块中引入具体依赖的实现,对应在JavaScript中则是不要显示的引入依赖模块,仅在注入点面向依赖接口编程。 19 | 20 | ```javascript 21 | // List.js 22 | export default class List { 23 | // 构造函数注入实现了ListModel接口的依赖 24 | constructor(listModel) { 25 | this.model = listModel; 26 | } 27 | 28 | // 属性/接口注入实现了ListView接口的依赖 29 | setView(listView) { 30 | this.view = listView; 31 | } 32 | 33 | enter() { 34 | let data = this.model.load(); 35 | this.view.render(data); 36 | } 37 | } 38 | 39 | // MyListModel.js 40 | export default class MyListModel { 41 | load() { 42 | return {data: 'data'}; 43 | } 44 | } 45 | 46 | // MyListView.js 47 | export default class MyListView { 48 | render(data) { 49 | console.log(data); 50 | } 51 | } 52 | ``` 53 | 上述代码中在List类有两个依赖view和model,分别实现了ListModel和ListView(隐式)接口, 54 | 而MyListModel和MyListView类则是ListModel与ListView接口的具体实现。 55 | 56 | ## Step 2:定义IoC配置,实例化IoC容器 57 | 58 | ```javascript 59 | // ioc.js 60 | import {IoC} from 'uioc'; 61 | import List from './List'; 62 | import MyListModel from './MyListModel'; 63 | import MyListView from './MyListView'; 64 | 65 | let config = { 66 | components: { 67 | list: { 68 | creator: List, 69 | args: [ 70 | {$ref: 'listModel'} 71 | ], 72 | properties: { 73 | view: { 74 | $ref: {'listView'} 75 | } 76 | }, 77 | 78 | listModel: { 79 | creator: MyListModel 80 | }, 81 | 82 | listView: { 83 | creator: MyListView 84 | } 85 | } 86 | } 87 | }; 88 | 89 | let ioc = new IoC(config); 90 | 91 | export default ioc; 92 | ``` 93 | 94 | 上述代码中,声明了list, listModel, listView三个组件, 95 | 其中list通过$ref关键字声明了2个依赖:listModel是list的构造函数依赖, 96 | 会在实例化list的时候,将创建好的listModel依赖传入构造函数; 97 | listView是list的属性依赖,会在实例化list完成后,将创建好的listView赋值给list,赋值方式为有setter则调用setter,无setter则直接对象赋值。 98 | 99 | ## Step 3: 定义入口文件,从ioc实例获取入口组件启动整个应用 100 | 101 | ```javascript 102 | // main.js 103 | import ioc from 'ioc'; 104 | 105 | ioc.getComponent('list').then(list => list.enter()); 106 | ``` 107 | 上述代码中通过ioc容器实例获取了list组件,ioc容器将根据配置创建好list的相关依赖并注入给list,最终组装成完整的list实例传递给promise的resolve回调。 108 | 109 | # [基础特性](https://github.com/ecomfe/uioc/wiki/Base-Feature) 110 | 111 | # 高级特性 112 | 113 | - [插件机制](https://github.com/ecomfe/uioc/wiki/plugins) 114 | - [aop](https://github.com/ecomfe/uioc/wiki/aop) 115 | 116 | # [API](http://ecomfe.github.io/uioc/doc/) 117 | 118 | # [Changelog](https://github.com/ecomfe/uioc/wiki/Changelog) 119 | 120 | # [0.3.x版本文档](https://github.com/ecomfe/uioc/wiki/0.3.x-readme) 121 | -------------------------------------------------------------------------------- /benchmark/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IoC Skip Checking Circular Dependency Benchmark 6 | 7 | 8 | 11 | 12 | 13 | 14 | 101 | 102 |
103 | 104 | -------------------------------------------------------------------------------- /dist/bundle.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.uioc=e.uioc||{})}(this,function(e){"use strict";function t(e){return Object.prototype.toString.call(e)===l}function n(e,t,r,o,i){var u=e.module;"function"!=typeof e.creator&&u&&(r[u]=r[u]||[],r[u].push(e)),t.processConfig(e.id);var a=null;if(o){if(o.checkForCircular(e.id)){var s=e.id+" has circular dependencies ";throw new m(s,e)}o.addData(e),a=o.appendChild(new d)}for(var c=(i=i||e.argDeps.concat(e.propDeps).concat(e.setterDeps||[])).length-1;c>-1;--c)t.hasComponent(i[c])&&n(t.getComponentConfig(i[c]),t,r,a);return r}function r(){return"function"==typeof define&&define.amd&&"function"==typeof C.require?require:"undefined"!=typeof module&&module&&"exports"in module?function(e,t){return t.apply(void 0,f(e.map(function(e){return require(e)})))}:function(e,t){return t.apply(void 0,f(e.map(function(e){return C[e]})))}}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},u=function(){function e(e,t){for(var n=0;n-1;--n)return t.data[n].id&&t.data[n].id===e?t.data[n]:t.checkForCircular(e);return null}},{key:"addData",value:function(e,t){return(!t||!this.checkForCircular(e.id))&&(this.data.push(e),!0)}}]),e}(),m=function(e){function t(e,n){i(this,t);var r=c(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));return r.component=n,r}return s(t,e),u(t,[{key:"print",value:function(){if("undefined"!=typeof console){var e;(e=console).warn.apply(e,arguments)}}}]),t}(Error),g=function(){function e(t,n){i(this,e),this.amdLoader=r(),this.context=t,this.skipCheckingCircularDep=n}return u(e,[{key:"setLoaderFunction",value:function(e){this.amdLoader=e}},{key:"resolveDependentModules",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments[2],o=this.skipCheckingCircularDep?null:new d;return n(e,this.context,t,o,r)}},{key:"loadModuleMap",value:function(e){var t=this,n=Object.keys(e);return n.length?new Promise(function(r){t.amdLoader(n,function(){for(var t=arguments.length,o=Array(t),i=0;i1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2],r=t.$import;if(!e.hasComponent(r))throw new Error("$import "+r+" component, but it is not exist, please check!!");var o=e.getComponentConfig(r);return t.id=(-1!==n.indexOf("^uioc-")?"":"^uioc-")+n,t.$import=void 0,e.addComponent(t.id,a({},o,t)),t.id}}]),u(t,[{key:"onGetComponent",value:function(e,n,r){return this[k][n]?r:(this[k][n]=t.transformConfig(e,r),r)}}]),t}(),P=/^set[A-Z]/,_="set".length,j=Symbol("cache"),w=function(e){function t(){i(this,t);var e=c(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return e[j]=Object.create(null),e}return s(t,b),u(t,[{key:"name",get:function(){return"auto"}}],[{key:"getPropertyFromSetter",value:function(e,t){var n=null;return P.test(e)&&"function"==typeof t.value&&(n=e.charAt(_).toLowerCase()+e.slice(_+1)),n}},{key:"setProperty",value:function(e,t,n){e["set"+t.charAt(0).toUpperCase()+t.slice(1)](n)}}]),u(t,[{key:"afterCreateInstance",value:function(e,n,r){var o=this.resolveDependencies(e,n,r);return o.length?e.getComponent(o).then(function(e){return e.forEach(function(e,n){return t.setProperty(r,o[n],e)}),r}):Promise.resolve(r)}},{key:"resolveDependencies",value:function(e,n,r){if(this[j][n])return this[j][n];var o=e.getComponentConfig(n)||{};if(!o.auto)return this[j][n]=[],[];for(var i=o.properties||{},u=[],a=Object.create(null),s=r;s;s=Object.getPrototypeOf(s))!function(n){Object.getOwnPropertyNames(n).forEach(function(r){if(!a[r]){a[r]=!0;var o=Object.getOwnPropertyDescriptor(n,r);(r=t.getPropertyFromSetter(r,o))&&!p.hasOwn(i,r)&&e.hasComponent(r)&&u.push(r)}})}(s);return this[j][n]=u,o.setterDeps=u,u}}]),t}(),I=Symbol("cache"),D=function(e){function t(){i(this,t);var e=c(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return e[I]=Object.create(null),e}return s(t,b),u(t,[{key:"name",get:function(){return"property"}}],[{key:"getSetter",value:function(e){if(p.isObject(e)&&"string"==typeof e.$setter)return e.$setter}},{key:"setProperty",value:function(e,t,n,r){if(r)return e[r](n);var o="set"+t.charAt(0).toUpperCase()+t.slice(1);"function"==typeof e[o]?e[o](n):e[t]=n}}]),u(t,[{key:"afterCreateInstance",value:function(e,n,r){if(!e.hasComponent(n))return Promise.resolve(r);var o=e.getComponentConfig(n),i=this.resolveDependencies(e,n),u=o.properties;return e.getComponent(i).then(function(e){for(var n in u){var o=u[n],a=p.hasRef(o)?e[i.indexOf(o.$ref)]:o;t.setProperty(r,n,a,t.getSetter(o))}return r})}},{key:"resolveDependencies",value:function(e,t){if(this[I][t])return this[I][t];var n=this[I][t]=[],r=e.getComponentConfig(t),o=r.properties;for(var i in o){var u=o[i];p.hasRef(u)&&n.push(u.$ref)}return r.propDeps=n,n}}]),t}(),A=Symbol("cache"),S=function(e){function t(){i(this,t);var e=c(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return e[A]=Object.create(null),e}return s(t,b),u(t,[{key:"name",get:function(){return"list"}}],[{key:"has",value:function(e){return p.isObject(e)&&e.$list instanceof Array}}]),u(t,[{key:"onContainerInit",value:function(e,t){return e.addComponent(this.constructor.LIST_ID,this.constructor.LIST_COMPONENT_CONFIG),t}},{key:"onGetComponent",value:function(e,n,r){if(this[A][n])return r;var o=this.constructor,i=o.has,u=o.LIST_ID;r.args=r.args.map(function(e){return i(e)?{$import:u,args:e.$list}:e});var a=r.properties;for(var s in a){var c=a[s];t.has(c)&&(a[s]={$import:u,args:c.$list})}return this[A][n]=!0,r}}]),t}();S.LIST_COMPONENT_CONFIG={creator:function(){for(var e=arguments.length,t=Array(e),n=0;n3&&void 0!==arguments[3]?arguments[3]:[],o="class"===t?"createClassProxy":"createObjectProxy",i=this.constructor.AOP_ID;return e.getComponent(i).then(function(e){return r.reduce(function(t,n){var r=n.matcher,i=n.advices;return e[o](t,r,i)},n)})}},{key:"beforeCreateInstance",value:function(e,t,n){var r=e.getComponentConfig(t)||{};return this.canProxy(r,"class")?this.proxyAop(e,"class",r.creator,r.aopConfig.advisors).then(function(e){return r.creator=e}).then(function(){return n}):Promise.resolve(n)}},{key:"afterCreateInstance",value:function(e,t,n){var r=e.getComponentConfig(t)||{};return this.canProxy(r,"object")?this.proxyAop(e,"object",n,r.aopConfig.advisors):Promise.resolve(n)}},{key:"name",get:function(){return"aop"}}]),t}();F.AOP_COMPONENT_CONFIG={module:"uaop",scope:"static"},F.AOP_ID=Symbol("internalAop");var E=Symbol("collection"),N=Symbol("components"),M=Symbol("createComponent"),T=Symbol("createInstance"),G=Symbol("loader"),L=Symbol("injector"),R={},q=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};i(this,e),this[N]=Object.create(null),this[E]=new B([new S,new x,new O,new F,new D,new w]),this[E].addPlugins(t.plugins),this[G]=new g(this,!!t.skipCheckingCircularDep),this[L]=new v(this),t=this[E].onContainerInit(this,t),this.initConfig(t)}return u(e,[{key:"initConfig",value:function(e){e.loader&&this.setLoaderFunction(e.loader),this.addComponent(e.components||{})}},{key:"addComponent",value:function(e,t){if("object"!==(void 0===e?"undefined":o(e))){if(this.hasComponent(e))throw new Error(String(e)+" has been added!");this[N][e]=this[M].call(this,e,t)}else for(var n in e)this.addComponent(n,e[n])}},{key:"getComponent",value:function(e){var t=this;if(e instanceof Array)return Promise.all(e.map(function(e){return t.getComponent(e)}));var n=Object.create(null);if(!this.hasComponent(e))return e=String(e),Promise.reject(new Error("`"+e+"` has not been added to the Ioc"));var r=this.getComponentConfig(e);this.processConfig(e);try{n=this[G].resolveDependentModules(r,n,r.argDeps)}catch(e){return Promise.reject(e)}return this[G].loadModuleMap(n).then(function(){return t[T](e)})}},{key:"hasComponent",value:function(e){return!!this[N][e]}},{key:"getComponentConfig",value:function(e){return e?this[N][e]:this[N]}},{key:"setLoaderFunction",value:function(e){this[G].setLoaderFunction(e)}},{key:"dispose",value:function(){this[E].onContainerDispose(this),this[L].dispose(),this[N]=null}},{key:"addPlugins",value:function(e,t){this[E].addPlugins(e,t)}},{key:"getPlugins",value:function(){return this[E].getPlugins()}},{key:"removePlugin",value:function(e){return this[E].removePlugin(e)}},{key:"processConfig",value:function(e){var t=this.getComponentConfig(e);if(t=this[E].onGetComponent(this,e,t),this[N][e]=t,!t.argDeps)for(var n=t.argDeps=[],r=t.args,o=r.length-1;o>-1;--o)p.hasRef(r[o])&&n.push(r[o].$ref)}},{key:M,value:function(e,t){return t=this[E].onAddComponent(this,e,t),a({id:e,args:[],properties:{},argDeps:null,propDeps:null,setterDeps:null,scope:"transient",creator:null,module:void 0,isFactory:!1,auto:!1,instance:null},t)}},{key:T,value:function(e){var t=this;return this[E].beforeCreateInstance(this,e).then(function(n){if(n===R){var r=t.hasComponent(e)?t.getComponentConfig(e):null;return t[G].wrapCreator(r).then(function(e){return t[L].createInstance(e)})}return n}).then(function(n){return t[E].afterCreateInstance(t,e,n)})}}]),e}(),U=Symbol("plugins"),B=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];i(this,e),this[U]=t}return u(e,[{key:"onContainerInit",value:function(e,t){return this[U].reduce(function(t,n){return n.onContainerInit(e,t)},t)}},{key:"onAddComponent",value:function(e,t,n){return this[U].reduce(function(n,r){return r.onAddComponent(e,t,n)},n)}},{key:"onGetComponent",value:function(e,t,n){return this[U].reduce(function(n,r){return r.onGetComponent(e,t,n)},n)}},{key:"beforeCreateInstance",value:function(e,t){return this[U].reduce(function(n,r){return n.then(function(n){return Promise.resolve(r.beforeCreateInstance(e,t,n))})},Promise.resolve(R))}},{key:"afterCreateInstance",value:function(e,t,n){return this[U].reduce(function(n,r){return n.then(function(n){var o=r.afterCreateInstance(e,t,n);return p.isPromise(o)?o:Promise.resolve(n)})},Promise.resolve(n))}},{key:"onContainerDispose",value:function(e){this[U].forEach(function(t){return t.onContainerDispose(e)})}},{key:"addPlugins",value:function(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this[U].length;(e=this[U]).splice.apply(e,[n,0].concat(f(t)))}},{key:"getPlugins",value:function(){return this[U].slice(0)}},{key:"removePlugin",value:function(e){return"number"!=typeof e&&(e=-1===(e=this[U].indexOf(e))?this[U].length:e),!!this[U].splice(e,1).length}}]),e}();e.IoC=q,e.BasePlugin=b,Object.defineProperty(e,"__esModule",{value:!0})}); 2 | //# sourceMappingURL=bundle.js.map 3 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./doc", 4 | "experimentalProposal": { 5 | "classProperties": true, 6 | "objectRestSpread": true, 7 | "doExpressions": true, 8 | "functionBind": true, 9 | "exportExtensions": true, 10 | "dynamicImport": true 11 | } 12 | } -------------------------------------------------------------------------------- /examples/simple-amd-module/List.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | function List(entityName) { 3 | console.log('List init!'); 4 | this.entityName = entityName 5 | } 6 | 7 | List.prototype.enter = function () { 8 | console.log('[List enter]: entityName is ' + this.entityName); 9 | this.view.model = this.model; 10 | this.model.load(); 11 | this.view.render(); 12 | }; 13 | 14 | return List; 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /examples/simple-amd-module/ListModel.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | function ListModel() { 3 | console.log('ListModel init!'); 4 | this.stores = {}; 5 | } 6 | 7 | ListModel.prototype.load = function () { 8 | this.set('results', [ 9 | { 10 | name: 'creative1' 11 | } , 12 | { 13 | name: 'creative2' 14 | }, 15 | { 16 | name: 'creative3' 17 | }, 18 | { 19 | name: 'creative4' 20 | } 21 | ]); 22 | console.log('[ListModel load]'); 23 | 24 | }; 25 | 26 | ListModel.prototype.get = function (name) { 27 | var value = this.stores[name]; 28 | return typeof value === 'object' ? JSON.parse(JSON.stringify(value)) : value; 29 | }; 30 | 31 | ListModel.prototype.set = function (name, value) { 32 | this.stores[name] = typeof value === 'object' ? JSON.parse(JSON.stringify(value)) : value; 33 | }; 34 | 35 | return ListModel; 36 | }); -------------------------------------------------------------------------------- /examples/simple-amd-module/ListView.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | function ListView() { 3 | console.log('ListView init!'); 4 | } 5 | 6 | ListView.prototype.render = function () { 7 | console.log('[ListView render]'); 8 | var results = this.model.get('results'); 9 | var html = '
    '; 10 | results.forEach(function (item) { 11 | html += this.template.replace(/\${(.*)}/, function (match, $1) { 12 | return item[$1]; 13 | }); 14 | }, this); 15 | 16 | html += '
'; 17 | 18 | document.body.innerHTML = html; 19 | }; 20 | 21 | return ListView; 22 | }); -------------------------------------------------------------------------------- /examples/simple-amd-module/README.md: -------------------------------------------------------------------------------- 1 | simple amd module 2 | === 3 | 4 | # install 5 | 6 | npm install 7 | 8 | # start 9 | 10 | npm start -------------------------------------------------------------------------------- /examples/simple-amd-module/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by exodia on 14-4-17. 3 | */ 4 | define( 5 | { 6 | components: { 7 | // 组件名 8 | list: { 9 | // 构件的模块路径 10 | module: 'List', 11 | // 默认为 transient,每次获取构件,都将调用一次 creator 12 | scope: 'transient', 13 | 14 | // 将被用来创建实例的函数,falsity的值则调用模块返回的函数作为 creator 15 | // 若传入字符串,则将调用模块对应的方法 16 | // 若传入函数,则该函数作为 creator 创建实例 17 | // creator: 18 | 19 | // 传递给构造函数或工厂函数的参数 20 | args: [ 21 | { 22 | // 依赖 entityName 构件, 将作为参数注入 23 | $ref: 'entityName' 24 | } 25 | ], 26 | // 属性依赖配置 27 | properties: { 28 | model: { $ref: 'listModel' }, 29 | view: { $ref: 'listView' } 30 | } 31 | }, 32 | listModel: { 33 | module: 'ListModel' 34 | }, 35 | listView: { 36 | module: 'ListView', 37 | properties: { template: '
  • ${name}
  • ' } 38 | }, 39 | entityName: { 40 | isFactory: true, 41 | creator: function () { 42 | return 'creative'; 43 | } 44 | } 45 | } 46 | } 47 | ); -------------------------------------------------------------------------------- /examples/simple-amd-module/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/simple-amd-module/main.js: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | return { 3 | init: function () { 4 | var config = require('./config'); 5 | var IoC = require('ioc').IoC; 6 | var ioc = new IoC(config); 7 | ioc.getComponent('list').then(function (list) { 8 | list.enter(); 9 | }); 10 | } 11 | }; 12 | }); -------------------------------------------------------------------------------- /examples/simple-amd-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-mvc", 3 | "version": "1.0.0", 4 | "description": "simple amd mvc", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "open index.html", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "exodia", 11 | "license": "MIT", 12 | "dependencies": { 13 | "uioc": "^1.0.0-beta.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/simple-es6-module/README.md: -------------------------------------------------------------------------------- 1 | simple es6 module 2 | === 3 | 4 | # install 5 | 6 | npm install 7 | 8 | # start 9 | 10 | npm start 11 | 12 | # build 13 | 14 | npm run build 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/simple-es6-module/dist/bundle.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | /******/ 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ([ 44 | /* 0 */ 45 | /***/ function(module, exports, __webpack_require__) { 46 | 47 | 'use strict'; 48 | 49 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 50 | 51 | var _uioc = __webpack_require__(1); 52 | 53 | var _config = __webpack_require__(6); 54 | 55 | var _config2 = _interopRequireDefault(_config); 56 | 57 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 58 | 59 | var ioc = new _uioc.IoC(_config2.default); 60 | ioc.getComponent(['listA', 'listB']).then(function (_ref) { 61 | var _ref2 = _slicedToArray(_ref, 2); 62 | 63 | var listA = _ref2[0]; 64 | var listB = _ref2[1]; 65 | 66 | listA.render(); 67 | listB.render(); 68 | }).catch(function (e) { 69 | console.log(e); 70 | }); 71 | 72 | /***/ }, 73 | /* 1 */ 74 | /***/ function(module, exports, __webpack_require__) { 75 | 76 | /* WEBPACK VAR INJECTION */(function(module) {!function(e,t){ true?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.uioc=e.uioc||{})}(this,function(e){"use strict";function t(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function n(e,t){e.indexOf(t)===-1&&e.push(t)}function r(e){return Object.prototype.toString.call(e)===d}function o(e){return e&&"object"===("undefined"==typeof e?"undefined":c(e))&&"function"==typeof e.then}function i(){"undefined"!=typeof console&&Function.prototype.apply.call(console.warn,console,arguments)}function u(e){return r(e)&&"string"==typeof e.$ref}function a(e,t,n,r,o){var i=e.module;"function"!=typeof e.creator&&i&&(n[i]=n[i]||[],n[i].push(e)),t.processConfig(e.id);var u=r.checkForCircular(e.id);if(u){var s=e.id+" has circular dependencies ";throw new k(s,e)}r.addData(e);var c=r.appendChild(new O);o=o||e.argDeps.concat(e.propDeps).concat(e.setterDeps||[]);for(var f=o.length-1;f>-1;--f)t.hasComponent(o[f])&&a(t.getComponentConfig(o[f]),t,n,c);return n}function s(){return"function"=="function"&&__webpack_require__(3)&&"function"==typeof j.require?__webpack_require__(4):"undefined"!=typeof module&&module&&"exports"in module?function(e,t){return t.apply(void 0,y(e.map(function(e){return __webpack_require__(4)(e)})))}:void 0}var c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e},f=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},l=function(){function e(e,t){for(var n=0;n-1;--r)return t.data[r].id&&t.data[r].id===e?t.data[r]:t.checkForCircular(e);return null}},{key:"addData",value:function(e,t){return(!t||!this.checkForCircular(e.id))&&(this.data.push(e),!0)}}]),e}(),k=function(e){function t(e,n){f(this,t);var r=v(this,Object.getPrototypeOf(t).call(this,e));return r.component=n,r}return h(t,e),l(t,[{key:"print",value:function(){if("undefined"!=typeof console){var e;(e=console).warn.apply(e,arguments)}}}]),t}(Error),P=function(){function e(t){f(this,e),this.amdLoader=s(),this.context=t}return l(e,[{key:"setLoaderFunction",value:function(e){this.amdLoader=e}},{key:"resolveDependentModules",value:function(e){var t=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],n=arguments[2];return a(e,this.context,t,new O,n)}},{key:"loadModuleMap",value:function(e){var t=this,n=Object.keys(e);return new Promise(function(r){t.amdLoader(n,function(){for(var o=arguments.length,i=Array(o),u=0;u-1;--o)m.hasRef(r[o])&&n.push(r[o].$ref)}},{key:U,value:function(e,t){t=this[R].onAddComponent(this,e,t);var n=p({id:e,args:[],properties:{},argDeps:null,propDeps:null,setterDeps:null,scope:"transient",creator:null,module:void 0,isFactory:!1,auto:!1,instance:null},t);return"function"==typeof n.creator&&this[Z].wrapCreator(n),n}},{key:B,value:function(e){var t=this;return this[R].beforeCreateInstance(this,e).then(function(n){if(n===H){var r=t.hasComponent(e)?t.getComponentConfig(e):null;return t[z].createInstance(r)}return n}).then(function(n){return t[R].afterCreateInstance(t,e,n)})}}]),e}(),K=Symbol("plugins"),Q=function(){function e(){var t=arguments.length<=0||void 0===arguments[0]?[]:arguments[0];f(this,e),this[K]=t}return l(e,[{key:"onContainerInit",value:function(e,t){return this[K].reduce(function(t,n){return n.onContainerInit(e,t)},t)}},{key:"onAddComponent",value:function(e,t,n){return this[K].reduce(function(n,r){return r.onAddComponent(e,t,n)},n)}},{key:"onGetComponent",value:function(e,t,n){return this[K].reduce(function(n,r){return r.onGetComponent(e,t,n)},n)}},{key:"beforeCreateInstance",value:function(e,t){return this[K].reduce(function(n,r){return n.then(function(n){n=n===H?void 0:n;var o=r.beforeCreateInstance(e,t,n);return m.isPromise(o)?o:Promise.resolve(H)})},Promise.resolve(H))}},{key:"afterCreateInstance",value:function(e,t,n){return this[K].reduce(function(n,r){return n.then(function(n){var o=r.afterCreateInstance(e,t,n);return m.isPromise(o)?o:Promise.resolve(n)})},Promise.resolve(n))}},{key:"onContainerDispose",value:function(e){this[K].forEach(function(t){return t.onContainerDispose(e)})}},{key:"addPlugins",value:function(){var e,t=arguments.length<=0||void 0===arguments[0]?[]:arguments[0],n=arguments.length<=1||void 0===arguments[1]?this[K].length:arguments[1];(e=this[K]).splice.apply(e,[n,0].concat(y(t)))}},{key:"getPlugins",value:function(){return this[K].slice(0)}},{key:"removePlugin",value:function(e){return"number"!=typeof e&&(e=this[K].indexOf(e),e=e===-1?this[K].length:e),!!this[K].splice(e,1).length}}]),e}();e.IoC=J,e.BasePlugin=w}); 77 | 78 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)(module))) 79 | 80 | /***/ }, 81 | /* 2 */ 82 | /***/ function(module, exports) { 83 | 84 | module.exports = function(module) { 85 | if(!module.webpackPolyfill) { 86 | module.deprecate = function() {}; 87 | module.paths = []; 88 | // module.parent = undefined by default 89 | module.children = []; 90 | module.webpackPolyfill = 1; 91 | } 92 | return module; 93 | } 94 | 95 | 96 | /***/ }, 97 | /* 3 */ 98 | /***/ function(module, exports) { 99 | 100 | /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {module.exports = __webpack_amd_options__; 101 | 102 | /* WEBPACK VAR INJECTION */}.call(exports, {})) 103 | 104 | /***/ }, 105 | /* 4 */ 106 | /***/ function(module, exports, __webpack_require__) { 107 | 108 | var map = { 109 | "./bundle": 1, 110 | "./bundle.js": 1 111 | }; 112 | function webpackContext(req) { 113 | return __webpack_require__(webpackContextResolve(req)); 114 | }; 115 | function webpackContextResolve(req) { 116 | return map[req] || (function() { throw new Error("Cannot find module '" + req + "'.") }()); 117 | }; 118 | webpackContext.keys = function webpackContextKeys() { 119 | return Object.keys(map); 120 | }; 121 | webpackContext.resolve = webpackContextResolve; 122 | module.exports = webpackContext; 123 | webpackContext.id = 4; 124 | 125 | 126 | /***/ }, 127 | /* 5 */, 128 | /* 6 */ 129 | /***/ function(module, exports, __webpack_require__) { 130 | 131 | 'use strict'; 132 | 133 | Object.defineProperty(exports, "__esModule", { 134 | value: true 135 | }); 136 | 137 | var _List = __webpack_require__(7); 138 | 139 | var _List2 = _interopRequireDefault(_List); 140 | 141 | var _Loader = __webpack_require__(8); 142 | 143 | var _Loader2 = _interopRequireDefault(_Loader); 144 | 145 | var _ThirdLoader = __webpack_require__(9); 146 | 147 | var _ThirdLoader2 = _interopRequireDefault(_ThirdLoader); 148 | 149 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 150 | 151 | exports.default = { 152 | components: { 153 | listA: { 154 | creator: _List2.default, 155 | args: [document.getElementById('a'), { $ref: 'loader' }] 156 | }, 157 | listB: { 158 | creator: _List2.default, 159 | args: [document.getElementById('b'), { $ref: 'thirdLoader' }] 160 | }, 161 | loader: { 162 | creator: _Loader2.default, 163 | args: ['list.json'] 164 | }, 165 | thirdLoader: { 166 | creator: _ThirdLoader2.default 167 | } 168 | } 169 | }; 170 | 171 | /***/ }, 172 | /* 7 */ 173 | /***/ function(module, exports) { 174 | 175 | "use strict"; 176 | 177 | Object.defineProperty(exports, "__esModule", { 178 | value: true 179 | }); 180 | 181 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 182 | 183 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; } 184 | 185 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 186 | 187 | // List.js 188 | 189 | var List = function () { 190 | function List(container, loader) { 191 | _classCallCheck(this, List); 192 | 193 | this.container = container; 194 | this.loader = loader; 195 | } 196 | 197 | _createClass(List, [{ 198 | key: "render", 199 | value: function () { 200 | var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { 201 | return regeneratorRuntime.wrap(function _callee$(_context) { 202 | while (1) { 203 | switch (_context.prev = _context.next) { 204 | case 0: 205 | _context.next = 2; 206 | return this.loader.load(); 207 | 208 | case 2: 209 | this.container.textContent = _context.sent; 210 | 211 | case 3: 212 | case "end": 213 | return _context.stop(); 214 | } 215 | } 216 | }, _callee, this); 217 | })); 218 | 219 | function render() { 220 | return _ref.apply(this, arguments); 221 | } 222 | 223 | return render; 224 | }() 225 | }]); 226 | 227 | return List; 228 | }(); 229 | 230 | exports.default = List; 231 | 232 | /***/ }, 233 | /* 8 */ 234 | /***/ function(module, exports) { 235 | 236 | "use strict"; 237 | 238 | Object.defineProperty(exports, "__esModule", { 239 | value: true 240 | }); 241 | 242 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 243 | 244 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; } 245 | 246 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 247 | 248 | var Loader = function () { 249 | function Loader(url) { 250 | _classCallCheck(this, Loader); 251 | 252 | this.url = url; 253 | } 254 | 255 | _createClass(Loader, [{ 256 | key: "load", 257 | value: function () { 258 | var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { 259 | var result; 260 | return regeneratorRuntime.wrap(function _callee$(_context) { 261 | while (1) { 262 | switch (_context.prev = _context.next) { 263 | case 0: 264 | _context.next = 2; 265 | return fetch(this.url); 266 | 267 | case 2: 268 | result = _context.sent; 269 | return _context.abrupt("return", result.json()); 270 | 271 | case 4: 272 | case "end": 273 | return _context.stop(); 274 | } 275 | } 276 | }, _callee, this); 277 | })); 278 | 279 | function load() { 280 | return _ref.apply(this, arguments); 281 | } 282 | 283 | return load; 284 | }() 285 | }]); 286 | 287 | return Loader; 288 | }(); 289 | 290 | exports.default = Loader; 291 | 292 | /***/ }, 293 | /* 9 */ 294 | /***/ function(module, exports, __webpack_require__) { 295 | 296 | 'use strict'; 297 | 298 | Object.defineProperty(exports, "__esModule", { 299 | value: true 300 | }); 301 | 302 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 303 | 304 | var _sdk = __webpack_require__(10); 305 | 306 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; } 307 | 308 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 309 | 310 | var ThirdServiceLoader = function () { 311 | function ThirdServiceLoader() { 312 | _classCallCheck(this, ThirdServiceLoader); 313 | } 314 | 315 | _createClass(ThirdServiceLoader, [{ 316 | key: 'load', 317 | value: function () { 318 | var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() { 319 | return regeneratorRuntime.wrap(function _callee$(_context) { 320 | while (1) { 321 | switch (_context.prev = _context.next) { 322 | case 0: 323 | return _context.abrupt('return', (0, _sdk.request)()); 324 | 325 | case 1: 326 | case 'end': 327 | return _context.stop(); 328 | } 329 | } 330 | }, _callee, this); 331 | })); 332 | 333 | function load() { 334 | return _ref.apply(this, arguments); 335 | } 336 | 337 | return load; 338 | }() 339 | }]); 340 | 341 | return ThirdServiceLoader; 342 | }(); 343 | 344 | exports.default = ThirdServiceLoader; 345 | 346 | /***/ }, 347 | /* 10 */ 348 | /***/ function(module, exports) { 349 | 350 | 'use strict'; 351 | 352 | Object.defineProperty(exports, "__esModule", { 353 | value: true 354 | }); 355 | exports.request = request; 356 | function request() { 357 | return Promise.resolve('thirdInfo1,thirdInfo2,thirdInfo3'); 358 | } 359 | 360 | /***/ } 361 | /******/ ]); 362 | //# sourceMappingURL=bundle.js.map -------------------------------------------------------------------------------- /examples/simple-es6-module/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | simple module 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/simple-es6-module/list.json: -------------------------------------------------------------------------------- 1 | "item1,item2,item3,item4" -------------------------------------------------------------------------------- /examples/simple-es6-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-es6-module", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "lite-server", 8 | "build": "webpack" 9 | }, 10 | "author": "d_xinxin@163.com", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "babel-loader": "^6.2.4", 14 | "babel-preset-es2015": "^6.9.0", 15 | "babel-preset-stage-0": "^6.5.0", 16 | "lite-server": "^2.2.2", 17 | "source-map-loader": "^0.1.5", 18 | "webpack": "^1.13.1" 19 | }, 20 | "dependencies": { 21 | "babel-polyfill": "^6.9.1", 22 | "uioc": "^1.0.0-beta.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple-es6-module/src/List.js: -------------------------------------------------------------------------------- 1 | // List.js 2 | export default class List { 3 | constructor(container, loader) { 4 | this.container = container; 5 | this.loader = loader; 6 | } 7 | 8 | async render() { 9 | this.container.textContent = await this.loader.load(); 10 | } 11 | } -------------------------------------------------------------------------------- /examples/simple-es6-module/src/Loader.js: -------------------------------------------------------------------------------- 1 | export default class Loader { 2 | constructor(url) { 3 | this.url = url; 4 | } 5 | 6 | async load() { 7 | let result = await fetch(this.url); 8 | return result.json(); 9 | } 10 | } -------------------------------------------------------------------------------- /examples/simple-es6-module/src/ThirdLoader.js: -------------------------------------------------------------------------------- 1 | import {request} from './third/sdk'; 2 | export default class ThirdServiceLoader { 3 | async load() { 4 | return request(); 5 | } 6 | } -------------------------------------------------------------------------------- /examples/simple-es6-module/src/config.js: -------------------------------------------------------------------------------- 1 | import List from './List'; 2 | import Loader from './Loader'; 3 | import ThirdLoader from './ThirdLoader' 4 | 5 | export default { 6 | components: { 7 | listA: { 8 | creator: List, 9 | args: [document.getElementById('a'), {$ref: 'loader'}] 10 | }, 11 | listB: { 12 | creator: List, 13 | args: [document.getElementById('b'), {$ref: 'thirdLoader'}] 14 | }, 15 | loader: { 16 | creator: Loader, 17 | args: ['list.json'] 18 | }, 19 | thirdLoader: { 20 | creator: ThirdLoader 21 | } 22 | } 23 | }; -------------------------------------------------------------------------------- /examples/simple-es6-module/src/main.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'uioc'; 2 | import config from './config'; 3 | 4 | let ioc = new IoC(config); 5 | ioc.getComponent(['listA', 'listB']).then(([listA, listB]) => { 6 | listA.render(); 7 | listB.render(); 8 | }).catch(function (e) { 9 | console.log(e); 10 | }); -------------------------------------------------------------------------------- /examples/simple-es6-module/src/third/sdk.js: -------------------------------------------------------------------------------- 1 | export function request() { 2 | return Promise.resolve('thirdInfo1,thirdInfo2,thirdInfo3'); 3 | } -------------------------------------------------------------------------------- /examples/simple-es6-module/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/main.js', 5 | output: { 6 | path: __dirname + '/dist', 7 | filename: 'bundle.js' 8 | }, 9 | module: { 10 | preLoaders: [ 11 | { 12 | include: [ 13 | path.resolve(__dirname, 'node_modules/uioc/dist') 14 | ], 15 | loader: 'source-map-loader' 16 | } 17 | ], 18 | loaders: [ 19 | { 20 | include: [ 21 | path.resolve(__dirname, 'src'), 22 | ], 23 | loader: 'babel', 24 | query: { 25 | presets: ['es2015', 'stage-0'], 26 | } 27 | } 28 | ] 29 | }, 30 | devtool: 'source-map' 31 | }; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Jun 03 2014 23:30:03 GMT+0800 (CST) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '.', 9 | 10 | // frameworks to use 11 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 12 | frameworks: ['jasmine', 'requirejs'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'node_modules/babel-polyfill/dist/polyfill.js', 18 | 'test/assets/esl.js', 19 | 'test/test-main.js', 20 | {pattern: 'dist/**/*.js', included: false}, 21 | {pattern: 'src/**/*.js', included: false}, 22 | {pattern: 'test/**/*.js', included: false}, 23 | {pattern: 'node_modules/uaop/dist/bundle.js', included: false}, 24 | {pattern: 'node_modules/uaop/src/*.js', included: false}, 25 | {pattern: 'node_modules/uaop/dist/bundle.js.map', included: false} 26 | ], 27 | 28 | // list of files to exclude 29 | exclude: [], 30 | 31 | // preprocess matching files before serving them to the browser 32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 33 | preprocessors: { 34 | 'test/spec/**/*.js': ['babel', 'sourcemap'], 35 | 'test/assets/aop/*.js': ['babel', 'sourcemap'], 36 | 'src/**/*.js': process.env.TRAVIS ? ['babel', 'sourcemap', 'coverage'] : ['babel', 'sourcemap'], 37 | 'node_modules/uaop/src/*.js': ['babel', 'sourcemap'] 38 | }, 39 | 40 | babelPreprocessor: { 41 | options: { 42 | presets: ['es2015', 'stage-0'], 43 | sourceMap: 'inline', 44 | plugins: ['transform-es2015-modules-umd'] 45 | }, 46 | sourceFileName: function (file) { 47 | return file.originalPath; 48 | } 49 | }, 50 | 51 | // test results reporter to use 52 | // possible values: 'dots', 'progress' 53 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 54 | reporters: ['progress', 'coverage'], 55 | 56 | coverageReporter: { 57 | type: 'html', 58 | dir: 'test/coverage/' 59 | }, 60 | 61 | // web server port 62 | port: 9876, 63 | 64 | 65 | // enable / disable colors in the output (reporters and logs) 66 | colors: true, 67 | 68 | 69 | // level of logging 70 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 71 | logLevel: config.LOG_INFO, 72 | 73 | 74 | // enable / disable watching file and executing tests whenever any file changes 75 | autoWatch: true, 76 | 77 | 78 | // start these browsers 79 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 80 | browsers: process.env.TRAVIS ? ['PureHeadlessChrome'] : ['Chrome'], 81 | 82 | customLaunchers: { 83 | Edge: { 84 | base: 'IE', 85 | 'x-ua-compatible': 'IE=edge' 86 | }, 87 | IE10: { 88 | base: 'IE', 89 | 'x-ua-compatible': 'IE=EmulateIE10' 90 | }, 91 | IE9: { 92 | base: 'IE', 93 | 'x-ua-compatible': 'IE=EmulateIE9' 94 | }, 95 | PureHeadlessChrome: { 96 | base: 'ChromeHeadless', 97 | flags: ['--disable-translate', '--disable-extensions'] 98 | } 99 | }, 100 | 101 | // Continuous Integration mode 102 | // if true, Karma captures browsers, runs the tests and exits 103 | singleRun: false 104 | }); 105 | }; 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uioc", 3 | "version": "1.2.1", 4 | "description": "An IoC Framework", 5 | "main": "dist/bundle.js", 6 | "jsnext:main": "src/main.js", 7 | "maintainers": { 8 | "name": "Exodia", 9 | "email": "d_xinxin@163.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/ecomfe/uioc.git" 14 | }, 15 | "keywords": [ 16 | "ioc", 17 | "di" 18 | ], 19 | "scripts": { 20 | "test": "karma start --single-run --browsers PureHeadlessChrome", 21 | "start": "karma start", 22 | "build": "node scripts/build.js", 23 | "doc": "esdoc -c esdoc.json" 24 | }, 25 | "files": [ 26 | "dist", 27 | "src", 28 | "esdoc.json", 29 | "README.md" 30 | ], 31 | "author": { 32 | "name": "Exodia", 33 | "email": "d_xinxin@163.com", 34 | "url": "github.com/Exodia" 35 | }, 36 | "contributors": [ 37 | "otakustay(http://otakustay.com)", 38 | "ycycwx(https://github.com/ycycwx)", 39 | "strwind(https://github.com/strwind)", 40 | "srhb18(https://github.com/srhb18)" 41 | ], 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/ecomfe/uioc/issues" 45 | }, 46 | "homepage": "https://github.com/ecomfe/uioc", 47 | "devDependencies": { 48 | "babel-plugin-transform-es2015-modules-umd": "^6.6.5", 49 | "babel-polyfill": "^6.7.2", 50 | "babel-preset-es2015": "^6.6.0", 51 | "babel-preset-es2015-rollup": "^3.0.0", 52 | "babel-preset-stage-0": "^6.5.0", 53 | "esdoc": "^0.5.2", 54 | "jasmine-core": "^2.6.4", 55 | "karma": "^1.7.0", 56 | "karma-babel-preprocessor": "^6.0.1", 57 | "karma-chrome-launcher": "^2.2.0", 58 | "karma-coverage": "^1.1.1", 59 | "karma-firefox-launcher": "^1.0.1", 60 | "karma-ie-launcher": "^1.0.0", 61 | "karma-jasmine": "^1.1.0", 62 | "karma-requirejs": "^1.1.0", 63 | "karma-safari-launcher": "^1.0.0", 64 | "karma-sourcemap-loader": "^0.3.7", 65 | "requirejs": "^2.2.0", 66 | "rollup": "^0.43.0", 67 | "rollup-plugin-babel": "^2.4.0", 68 | "rollup-plugin-uglify": "^2.0.1" 69 | }, 70 | "dependencies": { 71 | "uaop": "0.0.1-alpha.2" 72 | }, 73 | "edp": { 74 | "main": "main" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const rollup = require('rollup'); 2 | const fs = require('fs'); 3 | const pkg = require('../package.json'); 4 | const uglify = require('rollup-plugin-uglify'); 5 | const babel = require('rollup-plugin-babel'); 6 | const external = Object.keys(pkg.dependencies); 7 | 8 | rollup.rollup({ 9 | entry: 'src/main.js', 10 | plugins: [ 11 | babel({presets: ['es2015-rollup', 'stage-1']}), 12 | uglify() 13 | ], 14 | external: external 15 | }).then( 16 | bundle => { 17 | bundle.write({ 18 | dest: pkg['main'], 19 | format: 'umd', 20 | moduleName: 'uioc', 21 | sourceMap: true 22 | }); 23 | } 24 | ).catch(err => console.log('build umd fail: ', err)); 25 | -------------------------------------------------------------------------------- /src/CircularError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 循环依赖错误 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | /** 7 | * @private 8 | */ 9 | export default class CircularError extends Error { 10 | constructor(message, component) { 11 | super(message); 12 | this.component = component; 13 | } 14 | 15 | print(...args) { 16 | if (typeof console !== 'undefined') { 17 | console.warn(...args); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/DependencyTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file DependencyNode 依赖树 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | /** 7 | * @private 8 | */ 9 | export default class DependencyNode { 10 | data = []; 11 | children = []; 12 | parent = null; 13 | 14 | appendChild(node) { 15 | node.parent = this; 16 | this.children.push(node); 17 | return node; 18 | } 19 | 20 | checkForCircular(id) { 21 | let node = this.parent; 22 | if (node !== null) { 23 | let data = node.data; 24 | for (let i = data.length - 1; i > -1; --i) { 25 | if (node.data[i].id && node.data[i].id === id) { 26 | return node.data[i]; 27 | } 28 | 29 | return node.checkForCircular(id); 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | 36 | addData(data, checkForCircular) { 37 | if (checkForCircular && this.checkForCircular(data.id)) { 38 | return false; 39 | } 40 | 41 | this.data.push(data); 42 | return true; 43 | } 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Injector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Injector 依赖注入类 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | import u from './util'; 7 | 8 | const STORE = Symbol('store'); 9 | const GET_INSTANCE = Symbol('getInstance'); 10 | 11 | /** 12 | * @private 13 | */ 14 | export default class Injector { 15 | 16 | constructor(context) { 17 | this.context = context; 18 | this[STORE] = Object.create(null); 19 | } 20 | 21 | createInstance(component) { 22 | if (!component) { 23 | return Promise.resolve(null); 24 | } 25 | 26 | switch (component.scope) { 27 | case 'singleton': 28 | let id = component.id; 29 | if (!(id in this[STORE])) { 30 | this[STORE][id] = this[GET_INSTANCE](component).then(instance => this[STORE][id] = instance); 31 | } 32 | return Promise.resolve(this[STORE][id]); 33 | case 'transient': 34 | return this[GET_INSTANCE](component); 35 | case 'static': 36 | return Promise.resolve(component.creator); 37 | } 38 | } 39 | 40 | injectArgs({args}) { 41 | return Promise.all(args.map(arg => u.hasRef(arg) ? this.context.getComponent(arg.$ref) : arg)); 42 | } 43 | 44 | dispose() { 45 | let store = this[STORE]; 46 | for (let k in store) { 47 | let instance = store[k]; 48 | instance && typeof instance.dispose === 'function' && instance.dispose(); 49 | } 50 | 51 | this[STORE] = null; 52 | } 53 | 54 | [GET_INSTANCE](component) { 55 | return this.injectArgs(component).then(args => component.creator(...args)); 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/IoC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file IoC 容器类 3 | * @author exodia (d_xinxin@163.com) 4 | */ 5 | 6 | import Injector from './Injector'; 7 | import u from './util'; 8 | import Loader from './Loader'; 9 | import ImportPlugin from './plugins/ImportPlugin'; 10 | import AutoPlugin from './plugins/AutoPlugin'; 11 | import PropertyPlugin from './plugins/PropertyPlugin'; 12 | import ListPlugin from './plugins/ListPlugin'; 13 | import MapPlugin from './plugins/MapPlugin'; 14 | import AopPlugin from './plugins/AopPlugin'; 15 | 16 | const PLUGIN_COLLECTION = Symbol('collection'); 17 | const COMPONENTS = Symbol('components'); 18 | const CREATE_COMPONENT = Symbol('createComponent'); 19 | const CREATE_INSTANCE = Symbol('createInstance'); 20 | const LOADER = Symbol('loader'); 21 | const INJECTOR = Symbol('injector'); 22 | const NULL = {}; 23 | 24 | /** 25 | * IoC 容器类 26 | */ 27 | export default class IoC { 28 | /** 29 | * 根据配置实例化一个 IoC 容器 30 | * 31 | * @param {IoCConfig} [config] ioc 容器配置 32 | */ 33 | constructor(config = {}) { 34 | /** 35 | * @private 36 | */ 37 | this[COMPONENTS] = Object.create(null); 38 | 39 | /** 40 | * @private 41 | */ 42 | this[PLUGIN_COLLECTION] = new PluginCollection([ 43 | new ListPlugin(), 44 | new MapPlugin(), 45 | new ImportPlugin(), 46 | new AopPlugin(), 47 | new PropertyPlugin(), 48 | new AutoPlugin() 49 | ]); 50 | this[PLUGIN_COLLECTION].addPlugins(config.plugins); 51 | 52 | /** 53 | * @private 54 | */ 55 | this[LOADER] = new Loader(this, !!config.skipCheckingCircularDep); 56 | 57 | /** 58 | * @private 59 | */ 60 | this[INJECTOR] = new Injector(this); 61 | 62 | config = this[PLUGIN_COLLECTION].onContainerInit(this, config); 63 | this.initConfig(config); 64 | } 65 | 66 | /** 67 | * 初始化配置 68 | * @param {IoCConfig} iocConfig ioc 配置 69 | * @protected 70 | */ 71 | initConfig(iocConfig) { 72 | 73 | if (iocConfig.loader) { 74 | this.setLoaderFunction(iocConfig.loader); 75 | } 76 | 77 | this.addComponent(iocConfig.components || {}); 78 | } 79 | 80 | /** 81 | * 82 | * 向容器中注册组件 83 | * 84 | * @param {string | Object.} id 组件 id 或者组件配置集合 85 | * @param {ComponentConfig} [config] 组件配置, 第一个参数为组件 id 时有效 86 | * @example 87 | * ioc.addComponent('list', { 88 | * // 构造函数创建构件 new creator, 或者字符串,字符串则为 amd 模块名 89 | * creator: require('./List'), 90 | * scope: 'transient', 91 | * args: [{$ref: 'entityName'}], 92 | * 93 | * // 属性注入, 不设置$setter, 则直接instance.xxx = xxx 94 | * properties: { 95 | * model: {$ref: 'listModel'}, 96 | * view: {$ref: 'listView'}, 97 | * name: 'xxxx' // 未设置$ref/$import操作符,'xxxx' 即为依赖值 98 | * } 99 | * }); 100 | * 101 | * ioc.addComponent({ 102 | * listData: { 103 | * creator: 'ListData', 104 | * scope: 'transient', 105 | * properties: { 106 | * data: { 107 | * $import: 'requestStrategy', // 创建匿名组件,默认继承 requestStrategy 的配置, 108 | * args: ['list', 'list'] // 重写 requestStrategy 的 args 配置 109 | * } 110 | * } 111 | * } 112 | * }); 113 | */ 114 | addComponent(id, config) { 115 | if (typeof id === 'object') { 116 | for (let k in id) { 117 | this.addComponent(k, id[k]); 118 | } 119 | return; 120 | } 121 | 122 | if (this.hasComponent(id)) { 123 | throw new Error(`${String(id)} has been added!`); 124 | } 125 | else { 126 | this[COMPONENTS][id] = this[CREATE_COMPONENT].call(this, id, config); 127 | } 128 | } 129 | 130 | /** 131 | * 获取组件实例 132 | * 133 | * @param {string | string[]} id 单个组件 id 字符串或者一系列组件 id 数组 134 | * @return {Promise<*> | Promise<*[]>} 值为组件实例(传入参数为组件数组时, 值为组件实例数组)的 promise 135 | */ 136 | getComponent(id) { 137 | if (id instanceof Array) { 138 | return Promise.all(id.map(id => this.getComponent(id))); 139 | } 140 | let moduleMap = Object.create(null); 141 | 142 | if (!this.hasComponent(id)) { 143 | id = String(id); 144 | return Promise.reject(new Error(`\`${id}\` has not been added to the Ioc`)); 145 | } 146 | else { 147 | let config = this.getComponentConfig(id); 148 | this.processConfig(id); 149 | try { 150 | moduleMap = this[LOADER].resolveDependentModules(config, moduleMap, config.argDeps); 151 | } 152 | catch (e) { 153 | return Promise.reject(e); 154 | } 155 | } 156 | 157 | return this[LOADER].loadModuleMap(moduleMap).then(() => this[CREATE_INSTANCE](id)); 158 | } 159 | 160 | /** 161 | * 检测是否注册过某个组件 162 | * 163 | * @param {string} id 组件 id 164 | * @return {bool} 165 | */ 166 | hasComponent(id) { 167 | return !!this[COMPONENTS][id]; 168 | } 169 | 170 | /** 171 | * 获取组件配置,不传入则返回所有组件配置 172 | * 173 | * @ignore 174 | * @param {string} [id] 组件id 175 | * @return {*} 176 | */ 177 | getComponentConfig(id) { 178 | return id ? this[COMPONENTS][id] : this[COMPONENTS]; 179 | } 180 | 181 | /** 182 | * 设置 IoC 的模块加载器 183 | * 184 | * @param {Function} amdLoader 符合 AMD 规范的模块加载器 185 | */ 186 | setLoaderFunction(amdLoader) { 187 | this[LOADER].setLoaderFunction(amdLoader); 188 | } 189 | 190 | /** 191 | * 销毁容器,会遍历容器中的单例,如果有设置 dispose,调用他们的 dispose 方法 192 | */ 193 | dispose() { 194 | this[PLUGIN_COLLECTION].onContainerDispose(this); 195 | this[INJECTOR].dispose(); 196 | this[COMPONENTS] = null; 197 | } 198 | 199 | /** 200 | * 在指定位置添加插件 201 | * 202 | * @param {ILifeCircleHook[]} plugins 插件数组 203 | * @param {number} [pos] 插入位置, 默认为当前 ioc 容器插件队列末尾 204 | */ 205 | addPlugins(plugins, pos) { 206 | this[PLUGIN_COLLECTION].addPlugins(plugins, pos); 207 | } 208 | 209 | /** 210 | * 获取当前实例的插件队列 211 | * 212 | * @return {ILifeCircleHook[]} 213 | */ 214 | getPlugins() { 215 | return this[PLUGIN_COLLECTION].getPlugins(); 216 | } 217 | 218 | /** 219 | * 移除指定的插件或指定位置的插件 220 | * 221 | * @param {number | ILifeCircleHook} pluginOrPos 插件实例或者插件位置 222 | * @return {bool} 成功移除返回 true 223 | */ 224 | removePlugin(pluginOrPos) { 225 | return this[PLUGIN_COLLECTION].removePlugin(pluginOrPos); 226 | } 227 | 228 | // todo: to be private 229 | /** 230 | * @ignore 231 | */ 232 | processConfig(id) { 233 | let config = this.getComponentConfig(id); 234 | config = this[PLUGIN_COLLECTION].onGetComponent(this, id, config); 235 | this[COMPONENTS][id] = config; 236 | if (!config.argDeps) { 237 | let deps = config.argDeps = []; 238 | let args = config.args; 239 | for (let i = args.length - 1; i > -1; --i) { 240 | u.hasRef(args[i]) && deps.push(args[i].$ref); 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * @private 247 | */ 248 | [CREATE_COMPONENT](id, config) { 249 | config = this[PLUGIN_COLLECTION].onAddComponent(this, id, config); 250 | return { 251 | id: id, 252 | args: [], 253 | properties: {}, 254 | argDeps: null, 255 | propDeps: null, 256 | setterDeps: null, 257 | scope: 'transient', 258 | creator: null, 259 | module: undefined, 260 | isFactory: false, 261 | auto: false, 262 | instance: null, 263 | ...config 264 | }; 265 | } 266 | 267 | /** 268 | * @private 269 | */ 270 | [CREATE_INSTANCE](id) { 271 | return this[PLUGIN_COLLECTION].beforeCreateInstance(this, id) 272 | .then( 273 | instance => { 274 | if (instance === NULL) { 275 | let component = this.hasComponent(id) ? this.getComponentConfig(id) : null; 276 | return this[LOADER] 277 | .wrapCreator(component) 278 | .then(component => this[INJECTOR].createInstance(component)); 279 | } 280 | 281 | return instance; 282 | } 283 | ) 284 | .then(instance => this[PLUGIN_COLLECTION].afterCreateInstance(this, id, instance)); 285 | } 286 | } 287 | 288 | const PLUGINS = Symbol('plugins'); 289 | 290 | class PluginCollection { 291 | constructor(plugins = []) { 292 | this[PLUGINS] = plugins; 293 | } 294 | 295 | onContainerInit(ioc, iocConfig) { 296 | return this[PLUGINS].reduce( 297 | (config, plugin) => plugin.onContainerInit(ioc, config), 298 | iocConfig 299 | ); 300 | } 301 | 302 | onAddComponent(ioc, componentId, initialComponentConfig) { 303 | return this[PLUGINS].reduce( 304 | (componentConfig, plugin) => plugin.onAddComponent(ioc, componentId, componentConfig), 305 | initialComponentConfig 306 | ); 307 | } 308 | 309 | onGetComponent(ioc, componentId, initialComponentConfig) { 310 | return this[PLUGINS].reduce( 311 | (componentConfig, plugin) => plugin.onGetComponent(ioc, componentId, componentConfig), 312 | initialComponentConfig 313 | ); 314 | } 315 | 316 | beforeCreateInstance(ioc, componentId) { 317 | return this[PLUGINS].reduce( 318 | (instancePromise, plugin) => instancePromise.then( 319 | instance => Promise.resolve(plugin.beforeCreateInstance(ioc, componentId, instance)) 320 | ), 321 | Promise.resolve(NULL) 322 | ); 323 | } 324 | 325 | afterCreateInstance(ioc, componentId, instance) { 326 | return this[PLUGINS].reduce( 327 | (instancePromise, plugin) => instancePromise.then( 328 | instance => { 329 | let result = plugin.afterCreateInstance(ioc, componentId, instance); 330 | return u.isPromise(result) ? result : Promise.resolve(instance); 331 | } 332 | ), 333 | Promise.resolve(instance) 334 | ); 335 | } 336 | 337 | onContainerDispose(ioc) { 338 | this[PLUGINS].forEach(plugin => plugin.onContainerDispose(ioc)); 339 | } 340 | 341 | addPlugins(plugins = [], pos = this[PLUGINS].length) { 342 | this[PLUGINS].splice(pos, 0, ...plugins); 343 | } 344 | 345 | getPlugins() { 346 | return this[PLUGINS].slice(0); 347 | } 348 | 349 | removePlugin(pluginOrPos) { 350 | if (typeof pluginOrPos !== 'number') { 351 | pluginOrPos = this[PLUGINS].indexOf(pluginOrPos); 352 | pluginOrPos = pluginOrPos === -1 ? this[PLUGINS].length : pluginOrPos; 353 | } 354 | 355 | return !!this[PLUGINS].splice(pluginOrPos, 1).length; 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/Loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 组件模块加载类 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | import DependencyTree from './DependencyTree'; 7 | import CircularError from './CircularError'; 8 | 9 | /** 10 | * @private 11 | */ 12 | export default class Loader { 13 | amdLoader = getDefaultLoader(); 14 | 15 | constructor(context, skipCheckingCircularDep) { 16 | this.context = context; 17 | this.skipCheckingCircularDep = skipCheckingCircularDep; 18 | } 19 | 20 | setLoaderFunction(amdGlobalLoader) { 21 | this.amdLoader = amdGlobalLoader; 22 | } 23 | 24 | resolveDependentModules(componentConfig, result = {}, deps) { 25 | let depTree = this.skipCheckingCircularDep ? null : new DependencyTree(); 26 | return getDependentModules(componentConfig, this.context, result, depTree, deps); 27 | } 28 | 29 | loadModuleMap(moduleMap) { 30 | let moduleIds = Object.keys(moduleMap); 31 | if(!moduleIds.length) { 32 | return Promise.resolve(); 33 | } 34 | 35 | return new Promise(resolve => { 36 | this.amdLoader( 37 | moduleIds, 38 | (...modules) => { 39 | modules.forEach( 40 | (factory, index) => { 41 | let moduleId = moduleIds[index]; 42 | moduleMap[moduleId].forEach( 43 | config => { 44 | if (typeof config.creator !== 'function') { 45 | config.creator = config.creator || factory; 46 | } 47 | } 48 | ); 49 | } 50 | ); 51 | resolve(); 52 | } 53 | ); 54 | }); 55 | } 56 | 57 | wrapCreator(config, factory) { 58 | let creator = config.creator; 59 | 60 | // 给字面量组件和非工厂组件套一层 creator,后面构造实例就可以无需分支判断,直接调用 component.creator 61 | if (!config.isFactory && config.scope !== 'static') { 62 | config.creator = function (...args) { 63 | return new creator(...args); 64 | }; 65 | } 66 | 67 | return Promise.resolve(config); 68 | } 69 | } 70 | 71 | function getDependentModules(component, context, result, depTree, deps) { 72 | let module = component.module; 73 | if (typeof component.creator !== 'function' && module) { 74 | result[module] = result[module] || []; 75 | result[module].push(component); 76 | } 77 | context.processConfig(component.id); 78 | 79 | let child = null; 80 | // depTree 为 null 表示跳过循环检测 81 | if (depTree) { 82 | let circular = depTree.checkForCircular(component.id); 83 | if (circular) { 84 | let msg = `${component.id} has circular dependencies `; 85 | throw new CircularError(msg, component); 86 | } 87 | 88 | depTree.addData(component); 89 | child = depTree.appendChild(new DependencyTree()); 90 | } 91 | 92 | 93 | deps = deps || component.argDeps.concat(component.propDeps).concat(component.setterDeps || []); 94 | for (let i = deps.length - 1; i > -1; --i) { 95 | if (context.hasComponent(deps[i])) { 96 | getDependentModules(context.getComponentConfig(deps[i]), context, result, child); 97 | } 98 | } 99 | 100 | return result; 101 | } 102 | 103 | const global = Function('return this')(); 104 | 105 | function getDefaultLoader() { 106 | if (typeof define === 'function' && define.amd && typeof global.require === 'function') { 107 | return require; 108 | } 109 | 110 | if (typeof module !== 'undefined' && module && 'exports' in module) { 111 | return (ids, cb) => cb(...(ids.map(id => require(id)))); 112 | } 113 | 114 | return (ids, cb) => cb(...(ids.map(id => global[id]))); 115 | } 116 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file main IoC 入口 3 | * @author exodia (d_xinxin@163.com) 4 | */ 5 | 6 | export IoC from './IoC'; 7 | export BasePlugin from './plugins/BasePlugin'; -------------------------------------------------------------------------------- /src/meta.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 元数据定义 3 | */ 4 | 5 | /** 6 | * ioc 生命周期接口 7 | * 8 | * @interface 9 | */ 10 | class ILifeCircleHook { 11 | /** 12 | * 插件名称 13 | * 14 | * @type {string} 15 | */ 16 | get name() { 17 | throw new Error('need to be implement'); 18 | } 19 | 20 | /** 21 | * 容器实例化时调用,传入 ioc 容器和当前配置作为参数, 可以在此拦截容器级的配置, 22 | * 返回一个新的容器级配置提供给 ioc 使用, ioc将基于新的配置做后续操作。 23 | * 24 | * @param {IoC} ioc ioc 实例 25 | * @param {IoCConfig} iocConfig 容器配置 26 | * 27 | * @return {IoCConfig} 扩展后的 ioc 容器配置 28 | */ 29 | onContainerInit(ioc, iocConfig) {} 30 | 31 | /** 32 | * 注册组件时调用,传入 ioc 容器,当前组件 id ,可以在此拦截组件级的配置, 33 | * 返回一个新的组件配置提供给 ioc 使用,ioc 将基于此配置做后续操作。 34 | * 35 | * @param {IoC} ioc ioc 实例 36 | * @param {string} componentId 当前待添加的组件 id 37 | * @param {ComponentConfig} componentConfig 当前待添加的组件配置 38 | * 39 | * @return {ComponentConfig} 扩展后的组件配置 40 | */ 41 | onAddComponent(ioc, componentId, componentConfig) {} 42 | 43 | /** 44 | * 获取组件时调用,传入 ioc 容器,当前组件 id,可以在此拦截组件级的配置, 45 | * 返回一个新的组件配置提供给 ioc 使用,ioc 将基于此配置做后续操作。 46 | * 47 | * @param {IoC} ioc ioc 实例 48 | * @param {string} componentId 当前要获取的组件 id 49 | * @param {ComponentConfig} componentConfig 当前要获取的组件配置 50 | * 51 | * @return {ComponentConfig} 扩展后的组件配置 52 | */ 53 | onGetComponent(ioc, componentId, componentConfig) {} 54 | 55 | /** 56 | * 创建组件实例前调用,传入 ioc 容器,当前组件 id,和当前已经创建的实例(可能没有), 57 | * 返回一个值为实例的 promise 给 ioc 使用,若不想覆盖现有实例,则直接返回一个 Promise。 58 | * 59 | * @param {IoC} ioc ioc 实例 60 | * @param {string} componentId 当前组件 id 61 | * @param {*} [instance] 当前已创建的实例 62 | * 63 | * @return {Promise<*>} 64 | */ 65 | beforeCreateInstance(ioc, componentId, instance) {} 66 | 67 | /** 68 | * 创建组件实例后调用,传入 ioc 容器,当前组件 id,和当前已经创建的实例, 69 | * 返回一个值为实例的 promise 给 ioc 使用。 70 | * 71 | * @param {IoC} ioc ioc 实例 72 | * @param {string} componentId 当前组件 id 73 | * @param {*} instance 当前已创建的实例 74 | * 75 | * @return {Promise<*>} 76 | */ 77 | afterCreateInstance(ioc, componentId, instance) {} 78 | 79 | /** 80 | * ioc 容器销毁时调用 81 | * 82 | * @param {IoC} ioc ioc 实例 83 | */ 84 | onContainerDispose(ioc) {} 85 | } 86 | 87 | 88 | /** 89 | * ioc 容器配置 90 | * 91 | * @typedef {Object} IoCConfig 92 | * 93 | * @property {Function} [config.loader=require] 符合 AMD 规范的模块加载器,默认为全局的 require 94 | * @property {Object.} [config.components] 95 | * 批量组件配置, 其中每个key 为组件 id,值为构建配置对象。 96 | * 97 | * @property {ILifeCircleHook[]} [config.plugins] ioc 插件 98 | * @property {boolean} [config.skipCheckingCircularDep=false] 是否跳过循环依赖检测 99 | */ 100 | 101 | /** 102 | * 组件配置对象 103 | * 104 | * @typedef {Object} ComponentConfig 105 | * 106 | * @property {Function|string} creator 创建组件的函数或模块名称 107 | * @property {boolean} [isFactory=false] 是否为工厂函数,默认false,会通过 new 方式调用,true 时直接调用 108 | * @property {('transient'|'singleton'|'static')} [scope='transient'] 109 | * 组件作用域,默认为 transient,每次获取组件,都会新建一个实例返回,若为 singleton,则会返回同一个实例,若为 static,则直接返回creator 110 | * @property {DependencyConfig[]} args 传递给组件构造函数的参数, 111 | * 获取组件时,根据 args 的配置,自动创建其依赖,作为构造函数参数传入 112 | * @property {Object.} [properties] 附加给组件实例的属性, 113 | * 获取组件时,IoC 会根据 properties 的配置,自动创建其依赖, 作为属性注入组件实例。 114 | * **note:** 若组件实例存在 ```set + 属性名首字母大些的方法```,则会调用此方法,并将依赖传入, 115 | * 否则简单的调用 ```this.{propertyName} = {property}``` 116 | * @property {AopConfig} [aopConfig] aop 配置对象,内置aop机制会读取该配置进行相关的拦截。 117 | */ 118 | 119 | /** 120 | * 组件依赖配置对象,用于配置组件的依赖,若未配置$ref与$import,则本身作为依赖值,否则将根据$ref/$import的声明查找依赖。 121 | * 122 | * @typedef {* | Object} DependencyConfig 123 | * 124 | * @property {string} $ref 声明依赖的组件,获取组件时,会自动创建其声明的依赖组件并注入 125 | * @property {string} $import 导入指定组件的配置,将创建一个匿名组件配置,其余的配置将覆盖掉导入的配置 126 | * @property {DependencyConfig[]} $list 声明数组形式的依赖,获取组件时,会创建一个数组,数组元素根据其对应$list中所声明的配置进行创建 127 | * @property {Object} $map 声明对象(映射表)形式的依赖,获取组件时,会创建一个对象, 128 | * 对象的属性根据其对应$map中所声明的配置进行创建 129 | */ 130 | 131 | /** 132 | * @typedef {Object} AopConfig 133 | * 134 | * @property {Advisor} advisors 切面配置数组,每个元素都是一个切面, 135 | * 切面是通知和切点的结合, 通知和切点共同定义了切面的全部内容 - 是什么, 在何时和何处完成功能. 136 | * 137 | * @property {('object'|'class')} [proxyTarget='object'] 拦截目标,默认为 'object', 138 | * 设置为 'object' 会基于对象实例进行拦截,设置为'class'则基于类拦截,两者区别见:https://github.com/ecomfe/uioc/issues/69 139 | */ 140 | 141 | /** 142 | * @typedef {Object} Advisor 143 | * 144 | * @property {string | Function | RegExp} matcher 切点(连接点筛选)功能, 为 string, RegExp, Function 三个类型之一, 145 | * 指定符合匹配条件的方法才应用特定的拦截/通知逻辑 146 | * @property {Object} advices 通知对象, 拥有 before, afterReturning, afterThrowing, after, around 中一个或多个方法; 147 | * 148 | * 详见:https://github.com/ecomfe/aop/blob/develop/README.md#切面aspectadvisor 149 | * 150 | * before: 在函数/方法执行前调用指定逻辑 151 | * 152 | * after: 在函数/方法执行后调用指定逻辑, 无论函数/方法是否执行成功 153 | * 154 | * afterReturning: 在函数/方法执行成功后调用指定逻辑 155 | * 156 | * afterThrowing: 在方法抛出异常后调用指定逻辑 157 | * 158 | * around: 在函数/方法调用之前和调用之后执行自定义的指定逻辑 159 | * 160 | */ 161 | -------------------------------------------------------------------------------- /src/plugins/AopPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file aop插件 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | import BasePlugin from './BasePlugin'; 7 | 8 | /** 9 | * @private 10 | * @ignore 11 | */ 12 | export default class AopPlugin extends BasePlugin { 13 | static AOP_COMPONENT_CONFIG = { 14 | module: 'uaop', 15 | scope: 'static' 16 | }; 17 | 18 | static AOP_ID = Symbol('internalAop'); 19 | 20 | get name() { 21 | return 'aop'; 22 | } 23 | 24 | /** 25 | * @override 26 | */ 27 | onContainerInit(ioc, iocConfig) { 28 | ioc.addComponent(this.constructor.AOP_ID, this.constructor.AOP_COMPONENT_CONFIG); 29 | return iocConfig; 30 | } 31 | 32 | /** 33 | * 是否需要代理 34 | * 35 | * @param {Object} config 配置 36 | * @param {string} type 代理类型[class|object] 37 | * @return {boolean} 38 | */ 39 | canProxy(config, type) { 40 | if (!('aopConfig' in config)) { 41 | return false; 42 | } 43 | 44 | // 默认代理对象为 object 45 | let {proxyTarget = 'object'} = config.aopConfig; 46 | return proxyTarget === type; 47 | } 48 | 49 | /** 50 | * 类或对象代理过程 51 | * 52 | * @param {IoC} ioc ioc 实例 53 | * @param {string} type 代理类型[class|object] 54 | * @param {Function} initial 待拦截的类或对象 55 | * @param {Array.} [advisors=[]] 切面 56 | * @return {Promise.} 57 | */ 58 | proxyAop(ioc, type, initial, advisors = []) { 59 | const METHOD = type === 'class' ? 'createClassProxy' : 'createObjectProxy'; 60 | const AOP_ID = this.constructor.AOP_ID; 61 | 62 | return ioc.getComponent(AOP_ID).then( 63 | aop => advisors.reduce( 64 | (target, {matcher, advices}) => aop[METHOD](target, matcher, advices), 65 | initial 66 | ) 67 | ); 68 | } 69 | 70 | /** 71 | * @override 72 | */ 73 | beforeCreateInstance(ioc, componentId, instance) { 74 | let config = ioc.getComponentConfig(componentId) || {}; 75 | let proxyTarget = 'class'; 76 | 77 | if (this.canProxy(config, proxyTarget)) { 78 | return this 79 | .proxyAop(ioc, proxyTarget, config.creator, config.aopConfig.advisors) 80 | .then(ProxyClass => config.creator = ProxyClass) 81 | .then(() => instance); 82 | } 83 | 84 | return Promise.resolve(instance); 85 | } 86 | 87 | /** 88 | * @override 89 | */ 90 | afterCreateInstance(ioc, componentId, instance) { 91 | let config = ioc.getComponentConfig(componentId) || {}; 92 | let proxyTarget = 'object'; 93 | 94 | return this.canProxy(config, proxyTarget) 95 | ? this.proxyAop(ioc, proxyTarget, instance, config.aopConfig.advisors) 96 | : Promise.resolve(instance); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/plugins/AutoPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 自动注入插件 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | import u from '../util'; 6 | import BasePlugin from './BasePlugin'; 7 | 8 | const SETTER_REGEX = /^set[A-Z]/; 9 | const SET_LENGTH = 'set'.length; 10 | const CACHE = Symbol('cache'); 11 | 12 | /** 13 | * @private 14 | */ 15 | export default class AutoPlugin extends BasePlugin { 16 | get name() { 17 | return 'auto'; 18 | } 19 | 20 | static getPropertyFromSetter(name, descriptor) { 21 | let prop = null; 22 | if (SETTER_REGEX.test(name) && typeof descriptor.value === 'function') { 23 | prop = name.charAt(SET_LENGTH).toLowerCase() + name.slice(SET_LENGTH + 1); 24 | } 25 | 26 | return prop; 27 | } 28 | 29 | static setProperty(instance, propertyName, value) { 30 | let method = 'set' + propertyName.charAt(0).toUpperCase() + propertyName.slice(1); 31 | instance[method](value); 32 | } 33 | 34 | constructor() { 35 | super(); 36 | this[CACHE] = Object.create(null); 37 | } 38 | 39 | afterCreateInstance(ioc, id, instance) { 40 | let deps = this.resolveDependencies(ioc, id, instance); 41 | if (!deps.length) { 42 | return Promise.resolve(instance); 43 | } 44 | 45 | return ioc.getComponent(deps).then( 46 | components => { 47 | components.forEach( 48 | (component, index) => AutoPlugin.setProperty(instance, deps[index], component) 49 | ); 50 | return instance; 51 | } 52 | ); 53 | } 54 | 55 | resolveDependencies(ioc, id, instance) { 56 | if (this[CACHE][id]) { 57 | return this[CACHE][id]; 58 | } 59 | 60 | let config = ioc.getComponentConfig(id) || {}; 61 | if (!config.auto) { 62 | this[CACHE][id] = []; 63 | return []; 64 | } 65 | 66 | let exclude = config.properties || {}; 67 | let deps = []; 68 | let resultSet = Object.create(null); 69 | for (let proto = instance; proto; proto = Object.getPrototypeOf(proto)) { 70 | let properties = Object.getOwnPropertyNames(proto); 71 | properties.forEach( 72 | prop => { 73 | if (!resultSet[prop]) { 74 | resultSet[prop] = true; 75 | let descriptor = Object.getOwnPropertyDescriptor(proto, prop); 76 | prop = AutoPlugin.getPropertyFromSetter(prop, descriptor); 77 | // 有属性,未和属性配置冲突,且组件已注册 78 | prop && !u.hasOwn(exclude, prop) && ioc.hasComponent(prop) && deps.push(prop); 79 | } 80 | } 81 | ); 82 | } 83 | 84 | this[CACHE][id] = deps; 85 | config.setterDeps = deps; 86 | return deps; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/plugins/BasePlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 基础插件类, 实现了默认的钩子接口, 继承此类实现自定义的生命周期钩子 3 | * 4 | * @implements {ILifeCircleHook} 5 | */ 6 | export default class BasePlugin { 7 | /** 8 | * @abstract 9 | */ 10 | get name() { 11 | throw new Error('need to be implement'); 12 | } 13 | 14 | /** 15 | * @see {@link ILifeCircleHook#onContainerInit} 16 | */ 17 | onContainerInit(ioc, iocConfig) { 18 | return iocConfig; 19 | } 20 | 21 | /** 22 | * @see {@link ILifeCircleHook#onAddComponent} 23 | */ 24 | onAddComponent(ioc, componentId, componentConfig) { 25 | return componentConfig; 26 | } 27 | 28 | /** 29 | * @see {@link ILifeCircleHook#onGetComponent} 30 | */ 31 | onGetComponent(ioc, componentId, componentConfig) { 32 | return componentConfig; 33 | } 34 | 35 | /** 36 | * @see {@link ILifeCircleHook#beforeCreateInstance} 37 | */ 38 | beforeCreateInstance(ioc, componentId, instance) { 39 | return Promise.resolve(instance); 40 | } 41 | 42 | /** 43 | * @see {@link ILifeCircleHook#afterCreateInstance} 44 | */ 45 | afterCreateInstance(ioc, componentId, instance) { 46 | return Promise.resolve(instance); 47 | } 48 | 49 | /** 50 | * @see {@link ILifeCircleHook#onContainerDispose} 51 | */ 52 | onContainerDispose(ioc) {} 53 | } 54 | -------------------------------------------------------------------------------- /src/plugins/ImportPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 导入插件 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | import BasePlugin from './BasePlugin'; 7 | import u from '../util'; 8 | 9 | const ANONY_PREFIX = '^uioc-'; 10 | const CACHE = Symbol('cache'); 11 | 12 | /** 13 | * @private 14 | */ 15 | export default class ImportPlugin extends BasePlugin { 16 | 17 | static has(obj) { 18 | return u.isObject(obj) && typeof obj.$import === 'string'; 19 | } 20 | 21 | static transformConfig(ioc, config) { 22 | // 解析构造函数参数 23 | let argConfigs = config.args; 24 | let id = null; 25 | let deps = argConfigs.reduce( 26 | (result, argConfig, index) => { 27 | if (ImportPlugin.has(argConfig)) { 28 | // 给匿名组件配置生成一个 ioc 构件id 29 | id = ImportPlugin.createAnonymousConfig(ioc, argConfig, `${config.id}-$arg.${index}.`); 30 | argConfigs[index] = {$ref: id}; 31 | result.push(id); 32 | } 33 | return result; 34 | }, 35 | [] 36 | ); 37 | 38 | // 解析属性 39 | let props = config.properties; 40 | for (let k in props) { 41 | if (ImportPlugin.has(props[k])) { 42 | id = ImportPlugin.createAnonymousConfig(ioc, props[k], `${config.id}-$prop.${k}.`); 43 | props[k] = {$ref: id}; 44 | deps.push(id); 45 | } 46 | } 47 | 48 | return deps 49 | } 50 | 51 | static createAnonymousConfig(ioc, config = {}, idPrefix) { 52 | let importId = config.$import; 53 | if (!ioc.hasComponent(importId)) { 54 | throw new Error(`$import ${importId} component, but it is not exist, please check!!`); 55 | } 56 | 57 | let refConfig = ioc.getComponentConfig(importId); 58 | config.id = (idPrefix.indexOf(ANONY_PREFIX) !== -1 ? '' : ANONY_PREFIX) + idPrefix; 59 | config.$import = undefined; 60 | ioc.addComponent(config.id, {...refConfig, ...config}); 61 | 62 | return config.id; 63 | } 64 | 65 | get name() { 66 | return 'import'; 67 | } 68 | 69 | constructor() { 70 | super(); 71 | this[CACHE] = Object.create(null); 72 | } 73 | 74 | /** 75 | * @override 76 | */ 77 | onGetComponent(ioc, id, config) { 78 | if (this[CACHE][id]) { 79 | return config; 80 | } 81 | 82 | this[CACHE][id] = ImportPlugin.transformConfig(ioc, config); 83 | 84 | return config; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/plugins/ListPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 数组集合插件 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | import u from '../util'; 7 | import BasePlugin from './BasePlugin'; 8 | 9 | const CACHE = Symbol('cache'); 10 | 11 | /** 12 | * @private 13 | */ 14 | export default class ListPlugin extends BasePlugin { 15 | static LIST_COMPONENT_CONFIG = { 16 | creator(...args) { 17 | return args; 18 | }, 19 | isFactory: true 20 | }; 21 | 22 | static LIST_ID = `${Date.now()}_list`; 23 | 24 | static has(obj) { 25 | return u.isObject(obj) && obj.$list instanceof Array; 26 | } 27 | 28 | get name() { 29 | return 'list'; 30 | } 31 | 32 | constructor() { 33 | super(); 34 | this[CACHE] = Object.create(null); 35 | } 36 | 37 | /** 38 | * @override 39 | */ 40 | onContainerInit(ioc, iocConfig) { 41 | ioc.addComponent(this.constructor.LIST_ID, this.constructor.LIST_COMPONENT_CONFIG); 42 | return iocConfig; 43 | } 44 | 45 | /** 46 | * @override 47 | */ 48 | onGetComponent(ioc, id, config) { 49 | if (this[CACHE][id]) { 50 | return config; 51 | } 52 | 53 | const {has, LIST_ID} = this.constructor; 54 | 55 | // {$list: [{}, {}]} => {$import: List.LIST_ID, args: []} 56 | config.args = config.args.map( 57 | argConfig => has(argConfig) ? {$import: LIST_ID, args: argConfig.$list} : argConfig 58 | ); 59 | 60 | let properties = config.properties; 61 | for (let k in properties) { 62 | let property = properties[k]; 63 | if (ListPlugin.has(property)) { 64 | properties[k] = {$import: LIST_ID, args: property.$list}; 65 | } 66 | } 67 | 68 | this[CACHE][id] = true; 69 | return config; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/plugins/MapPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 字典集合插件 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | import u from '../util'; 7 | import BasePlugin from './BasePlugin'; 8 | 9 | const CACHE = Symbol('cache'); 10 | 11 | /** 12 | * @private 13 | */ 14 | export default class MapPlugin extends BasePlugin { 15 | static MAP_COMPONENT_CONFIG = { 16 | creator: Object, 17 | isFactory: true 18 | }; 19 | 20 | static MAP_ID = `${(new Date()).getTime()}_map`; 21 | 22 | static has(obj) { 23 | return u.isObject(obj) && u.isObject(obj.$map); 24 | } 25 | 26 | get name() { 27 | return 'map'; 28 | } 29 | 30 | constructor() { 31 | super(); 32 | this[CACHE] = Object.create(null); 33 | } 34 | 35 | /** 36 | * @override 37 | */ 38 | onContainerInit(ioc, iocConfig) { 39 | ioc.addComponent(this.constructor.MAP_ID, this.constructor.MAP_COMPONENT_CONFIG); 40 | return iocConfig; 41 | } 42 | 43 | /** 44 | * @override 45 | */ 46 | onGetComponent(ioc, id, config) { 47 | if (this[CACHE][id]) { 48 | return config; 49 | } 50 | 51 | const {has, MAP_ID} = this.constructor; 52 | 53 | // {$map: {}} => {$import: Map.MAP_ID, properties: {}} 54 | config.args = config.args.map( 55 | argConfig => has(argConfig) ? {$import: MAP_ID, properties: argConfig.$map} : argConfig 56 | ); 57 | 58 | let properties = config.properties; 59 | for (let k in properties) { 60 | let property = properties[k]; 61 | if (MapPlugin.has(property)) { 62 | properties[k] = {$import: MAP_ID, properties: property.$map}; 63 | } 64 | } 65 | 66 | return config; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/plugins/PropertyPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 属性插件 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | 6 | import u from '../util'; 7 | import BasePlugin from './BasePlugin'; 8 | 9 | const CACHE = Symbol('cache'); 10 | 11 | /** 12 | * @private 13 | */ 14 | export default class PropertyPlugin extends BasePlugin { 15 | static getSetter(obj) { 16 | if (u.isObject(obj) && typeof obj.$setter === 'string') { 17 | return obj.$setter; 18 | } 19 | } 20 | 21 | static setProperty(instance, propertyName, value, setterName) { 22 | if (setterName) { 23 | return instance[setterName](value); 24 | } 25 | 26 | let method = 'set' + propertyName.charAt(0).toUpperCase() + propertyName.slice(1); 27 | typeof instance[method] === 'function' ? instance[method](value) : (instance[propertyName] = value); 28 | } 29 | 30 | get name() { 31 | return 'property'; 32 | } 33 | 34 | constructor() { 35 | super(); 36 | this[CACHE] = Object.create(null); 37 | } 38 | 39 | /** 40 | * @override 41 | */ 42 | afterCreateInstance(ioc, componentId, instance) { 43 | if (!ioc.hasComponent(componentId)) { 44 | return Promise.resolve(instance); 45 | } 46 | 47 | let config = ioc.getComponentConfig(componentId); 48 | let deps = this.resolveDependencies(ioc, componentId); 49 | let props = config.properties; 50 | return ioc.getComponent(deps).then( 51 | components => { 52 | for (let k in props) { 53 | let property = props[k]; 54 | let value = u.hasRef(property) ? components[deps.indexOf(property.$ref)] : property; 55 | PropertyPlugin.setProperty(instance, k, value, PropertyPlugin.getSetter(property)); 56 | } 57 | return instance; 58 | } 59 | ); 60 | } 61 | 62 | resolveDependencies(ioc, id) { 63 | if (this[CACHE][id]) { 64 | return this[CACHE][id]; 65 | } 66 | 67 | let deps = this[CACHE][id] = []; 68 | let config = ioc.getComponentConfig(id); 69 | let properties = config.properties; 70 | for (let k in properties) { 71 | let property = properties[k]; 72 | u.hasRef(property) && deps.push(property.$ref); 73 | } 74 | 75 | config.propDeps = deps; 76 | 77 | return deps; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 工具集合 3 | * @author exodia(d_xinxin@163.com) 4 | */ 5 | const OBJECT = Object.prototype.toString.call({}); 6 | 7 | function hasOwn(object, key) { 8 | return Object.prototype.hasOwnProperty.call(object, key); 9 | } 10 | 11 | function addToSet(arr, el) { 12 | arr.indexOf(el) === -1 && arr.push(el); 13 | } 14 | 15 | function isObject(obj) { 16 | return Object.prototype.toString.call(obj) === OBJECT; 17 | } 18 | 19 | function isPromise(obj) { 20 | return obj && typeof obj === 'object' && typeof obj.then === 'function'; 21 | } 22 | 23 | function warn() { 24 | if (typeof console !== 'undefined') { 25 | Function.prototype.apply.call(console.warn, console, arguments); 26 | } 27 | } 28 | 29 | function hasRef(obj) { 30 | return isObject(obj) && typeof obj.$ref === 'string'; 31 | } 32 | 33 | export default { 34 | hasOwn, 35 | addToSet, 36 | isObject, 37 | isPromise, 38 | hasRef: hasRef, 39 | warn 40 | } 41 | -------------------------------------------------------------------------------- /test/assets/A.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | var A = function (b) { 3 | this.b = b; 4 | }; 5 | 6 | A.prototype.hello = function () { 7 | return "Hello " + this.b.name; 8 | }; 9 | 10 | return A; 11 | }); -------------------------------------------------------------------------------- /test/assets/AutoInject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by exodia on 14-6-27. 3 | */ 4 | define( 5 | function () { 6 | function AutoInject(a, b) { 7 | this.a = a; 8 | this.b = b; 9 | this.e = null; 10 | this.setCCalledCount = 0; 11 | } 12 | 13 | AutoInject.prototype.setC = function (c) { 14 | this.c = c; 15 | ++this.setCCalledCount; 16 | }; 17 | 18 | AutoInject.prototype.setD = function (d) { 19 | this.d = d; 20 | }; 21 | 22 | AutoInject.prototype.sete = function (e) { 23 | this.e = e; 24 | }; 25 | 26 | AutoInject.prototype.setMyFactory = function (obj) { 27 | this.myFactory = obj; 28 | }; 29 | 30 | AutoInject.prototype.settest = function () { 31 | 32 | }; 33 | 34 | AutoInject.prototype.setd = function () { 35 | 36 | }; 37 | 38 | AutoInject.prototype.setUnRegisterComponent = function () { 39 | 40 | }; 41 | 42 | return AutoInject; 43 | } 44 | ); -------------------------------------------------------------------------------- /test/assets/AutoInject1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by exodia on 14-6-27. 3 | */ 4 | define( 5 | function (require) { 6 | var AutoInject = require('./AutoInject'); 7 | 8 | function AutoInject1() { 9 | AutoInject.apply(this, arguments); 10 | } 11 | 12 | AutoInject1.prototype = new AutoInject(); 13 | 14 | AutoInject1.prototype.setF = function (f) { 15 | this.f = f; 16 | }; 17 | 18 | return AutoInject1; 19 | } 20 | ); -------------------------------------------------------------------------------- /test/assets/B.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var B = function (name, c) { 4 | this.name = name; 5 | this.c = c; 6 | 7 | this.util = null; 8 | }; 9 | 10 | B.prototype.useUtil = function () { 11 | 12 | var arr = [ 1, 2, 3 ]; 13 | return this.util.isArray(arr); 14 | }; 15 | 16 | return B; 17 | }); -------------------------------------------------------------------------------- /test/assets/C.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var C = function (str, number, bool, nully) { 4 | this.str = str; 5 | this.number = number; 6 | this.bool = bool; 7 | this.nully = nully; 8 | }; 9 | 10 | return C; 11 | }); -------------------------------------------------------------------------------- /test/assets/D.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var D = function () { 4 | this.str = null; 5 | this.number = null; 6 | this.bool = null; 7 | this.nully = null; 8 | this.fromMethod = null; 9 | this.fromMethodArray = null; 10 | }; 11 | 12 | D.prototype.bye = function () { 13 | return "Bye!"; 14 | }; 15 | 16 | D.prototype.setFromMethod = function (value) { 17 | this.fromMethod = value; 18 | }; 19 | 20 | D.prototype.setFromMethodArray = function (arr) { 21 | this.fromMethodArray = arr; 22 | }; 23 | 24 | return D; 25 | }); -------------------------------------------------------------------------------- /test/assets/E.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var E = function (argsObj) { 4 | this.str = argsObj.str || null; 5 | this.number = argsObj.number | null; 6 | this.bool = argsObj.bool || null; 7 | this.nully = argsObj.nully || null; 8 | this.obj = argsObj.obj || null; 9 | }; 10 | 11 | return E; 12 | }); -------------------------------------------------------------------------------- /test/assets/F.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var F = function () { 4 | this.$ = null; 5 | }; 6 | 7 | F.prototype.isNumber = function (number) { 8 | return this.$.isNumeric(number); 9 | }; 10 | 11 | return F; 12 | }); -------------------------------------------------------------------------------- /test/assets/MyFactory.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var MyFactory = function () { 4 | 5 | this.defaultCode = 12345; 6 | }; 7 | 8 | MyFactory.prototype.createNumber = function () { 9 | 10 | return this.defaultCode; 11 | }; 12 | 13 | return MyFactory; 14 | }); -------------------------------------------------------------------------------- /test/assets/MyUtil.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var MyUtil = function (obj) { 4 | this.obj = obj; 5 | }; 6 | 7 | MyUtil.prototype.isArray = function (obj) { 8 | 9 | if (Array.isArray) { 10 | return Array.isArray(obj); 11 | } 12 | return Object.prototype.toString.call(obj) === "[object Array]"; 13 | }; 14 | 15 | MyUtil.prototype.isNumber = function () { 16 | //just a test fn 17 | }; 18 | 19 | MyUtil.prototype.isFunction = function () { 20 | //just a test fn 21 | }; 22 | 23 | MyUtil.prototype.isString = function () { 24 | 25 | }; 26 | 27 | MyUtil.factoryCreator = function (a, b) { 28 | return { 29 | a: a, 30 | b: b 31 | }; 32 | }; 33 | 34 | MyUtil.creator = function (a, b) { 35 | this.a = a; 36 | this.b = b; 37 | }; 38 | 39 | return MyUtil; 40 | }); -------------------------------------------------------------------------------- /test/assets/aop/Fixture.js: -------------------------------------------------------------------------------- 1 | class BaseFixture { 2 | static applyArgs = null; 3 | static applyScope = null; 4 | 5 | constructor() { 6 | Object.assign(this, { 7 | count1: 0, 8 | count2: 0, 9 | method1Result: {}, 10 | method2Result: {}, 11 | throwError: null 12 | }); 13 | } 14 | 15 | method1() { 16 | ++this.count1; 17 | if (this.throwError) { 18 | throw this.throwError; 19 | } 20 | return this.method1Result; 21 | } 22 | 23 | method2() { 24 | ++this.count2; 25 | return this.method2Result; 26 | } 27 | } 28 | 29 | export default class Fixture extends BaseFixture { 30 | constructor() { 31 | super(); 32 | Object.assign(this, { 33 | count3: 0, 34 | fooCount: 0, 35 | method3Result: {}, 36 | fooResult: {}, 37 | print: () => this.x 38 | }); 39 | } 40 | 41 | get propertyValue() { 42 | return 'propertyValue'; 43 | } 44 | 45 | get callMethod3() { 46 | return this.method3; 47 | } 48 | 49 | foo() { 50 | ++this.fooCount; 51 | return this.fooResult; 52 | } 53 | 54 | method3() { 55 | ++this.count3; 56 | return this.method3Result; 57 | } 58 | } 59 | 60 | Object.defineProperties(Fixture, { 61 | foo: { 62 | writable: false, 63 | configurable: false, 64 | enumerable: false 65 | }, 66 | method3: { 67 | writable: false, 68 | configurable: false, 69 | enumerable: false 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /test/assets/aop/FixtureAspect.js: -------------------------------------------------------------------------------- 1 | export default class FixtureAspect { 2 | static getAdvices() { 3 | let testAspect = new this(); 4 | return { 5 | aspect: testAspect, 6 | before: testAspect.before.bind(testAspect), 7 | around: testAspect.around.bind(testAspect), 8 | afterReturning: testAspect.afterReturning.bind(testAspect), 9 | afterThrowing: testAspect.afterThrowing.bind(testAspect), 10 | after: testAspect.after.bind(testAspect) 11 | }; 12 | } 13 | 14 | constructor() { 15 | this.beforeLog = {count: 0}; 16 | this.afterLog = {count: 0}; 17 | this.afterReturningLog = {count: 0}; 18 | this.afterThrowingLog = {count: 0}; 19 | this.aroundLog = {count: 0}; 20 | } 21 | 22 | before(...args) { 23 | this.log('before'); 24 | this.beforeLog.args = args; 25 | } 26 | 27 | around(joinPoint) { 28 | this.log('around'); 29 | this.aroundLog.joinPoint = joinPoint; 30 | let target = joinPoint.target; 31 | if (target.applyArgs) { 32 | return joinPoint.proceedApply(target.applyScope, target.applyArgs); 33 | } 34 | else { 35 | return joinPoint.proceed(); 36 | } 37 | } 38 | 39 | afterReturning(result) { 40 | this.log('afterReturning'); 41 | this.afterReturningLog.return = result; 42 | } 43 | 44 | afterThrowing(e) { 45 | this.log('afterThrowing'); 46 | this.afterThrowingLog.error = e; 47 | } 48 | 49 | after() { 50 | this.log('after'); 51 | } 52 | 53 | log(advice) { 54 | ++this[`${advice}Log`].count; 55 | this[`${advice}Log`].time = Date.now(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/assets/config.js: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | function merge() { 3 | var ret = {}; 4 | for (var i = 0, len = arguments.length; i < len; ++i) { 5 | var arg = arguments[i]; 6 | for (var k in arg) { 7 | ret[k] = arg[k]; 8 | } 9 | } 10 | 11 | return ret; 12 | } 13 | 14 | return function (overrides) { 15 | overrides = overrides || {}; 16 | var listConfig = require('./list/config')(); 17 | var mapConfig = require('./map/config')(); 18 | var importConfig = require('./import/config')(); 19 | var MyUtil = require('./MyUtil'); 20 | var config = { 21 | components: { 22 | a: { 23 | module: 'A', 24 | args: [ 25 | {$ref: 'b'} 26 | ] 27 | }, 28 | a2: { 29 | module: 'A', 30 | args: [ 31 | { 32 | $import: 'b', 33 | args: [ 34 | 'Tony Blair', 35 | {$ref: 'c'} 36 | ], 37 | properties: { 38 | util: {$ref: 'anotherUtil'} 39 | } 40 | } 41 | ] 42 | }, 43 | b: { 44 | module: 'B', 45 | args: [ 46 | 'Geoff Capes', 47 | {$ref: 'c'} 48 | ] 49 | }, 50 | b2: { 51 | module: 'B', 52 | args: [ 53 | 'Tony Blair', 54 | {$ref: 'c'} 55 | ], 56 | properties: { 57 | util: {$ref: 'anotherUtil'} 58 | } 59 | }, 60 | b3: { 61 | module: 'B', 62 | properties: { 63 | util: {$ref: 'myUtil'} 64 | } 65 | }, 66 | c: { 67 | module: 'C', 68 | args: ['String', 99, true, null], 69 | properties: { 70 | cProp: 'cProp' 71 | } 72 | }, 73 | c2: { 74 | module: 'C', 75 | args: ['String', {$ref: 'myFactory'}, true, null] 76 | }, 77 | d: { 78 | module: 'D', 79 | properties: { 80 | number: 88, 81 | str: 'hi', 82 | bool: false, 83 | nully: null, 84 | b: {$ref: 'b2'}, 85 | fromMethod: 'set', 86 | fromMethodArray: ['one', 'two'] 87 | } 88 | }, 89 | d2: { 90 | module: 'D', 91 | properties: { 92 | b: {$ref: 'b'} 93 | }, 94 | scope: 'singleton' 95 | }, 96 | d3: { 97 | module: 'D' 98 | }, 99 | d4: { 100 | module: 'D' 101 | }, 102 | e: { 103 | module: 'E', 104 | args: [ 105 | { 106 | str: 'str', 107 | number: 77, 108 | obj: {}, 109 | bool: true, 110 | nully: null 111 | } 112 | ] 113 | }, 114 | autoInject: { 115 | module: 'AutoInject', 116 | args: [ 117 | {$ref: 'a'}, 118 | {$ref: 'b'} 119 | ], 120 | properties: { 121 | myFactory: 'myFactory', 122 | anotherAutoInject: { 123 | $ref: 'autoInject1' 124 | } 125 | }, 126 | auto: true 127 | }, 128 | autoInject1: { 129 | module: 'AutoInject1', 130 | args: [ 131 | {$ref: 'a'}, 132 | {$ref: 'b'} 133 | ], 134 | auto: true 135 | }, 136 | creatorFn: { 137 | creator: function (a) { 138 | this.a = a; 139 | }, 140 | scope: 'singleton', 141 | args: [ 142 | {$ref: 'a'} 143 | ], 144 | properties: { 145 | b: {$ref: 'b'} 146 | } 147 | }, 148 | myFactory: { 149 | module: 'MyFactory', 150 | scope: 'singleton' 151 | }, 152 | myUtil: { 153 | module: 'MyUtil', 154 | scope: 'singleton' 155 | }, 156 | anotherUtil: { 157 | module: 'MyUtil' 158 | }, 159 | utilCreator: { 160 | creator: MyUtil.creator, 161 | args: [ 162 | {$ref: 'a'}, 163 | {$ref: 'b'} 164 | ], 165 | properties: { 166 | c: {$ref: 'c'} 167 | } 168 | }, 169 | utilFactoryCreator: { 170 | creator: MyUtil.factoryCreator, 171 | args: [ 172 | {$ref: 'a'}, 173 | {$ref: 'b'} 174 | ], 175 | properties: { 176 | c: {$ref: 'c'} 177 | } 178 | }, 179 | jquery: { 180 | module: 'jquery', 181 | scope: 'static' 182 | }, 183 | f: { 184 | module: 'F', 185 | properties: { 186 | $: {$ref: 'jquery'} 187 | } 188 | }, 189 | circular1: { 190 | module: 'A', 191 | properties: { 192 | a: 1, 193 | b: {$ref: 'circular2'} 194 | } 195 | }, 196 | circular2: { 197 | module: 'A', 198 | properties: { 199 | a: 2, 200 | b: {$ref: 'circular3'} 201 | } 202 | }, 203 | circular3: { 204 | module: 'A', 205 | args: [ 206 | {$ref: 'circular1'} 207 | ], 208 | properties: { 209 | a: 3 210 | } 211 | }, 212 | x: { 213 | module: 'X' //shouldn't exist 214 | } 215 | } 216 | }; 217 | config.components = merge(config.components, listConfig, mapConfig, importConfig); 218 | config.skipCheckingCircularDep = overrides.skipCheckingCircularDep; 219 | return config; 220 | }; 221 | }); -------------------------------------------------------------------------------- /test/assets/import/A.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | function A(obj) { 4 | this.obj = obj; 5 | } 6 | 7 | return A; 8 | } 9 | ); -------------------------------------------------------------------------------- /test/assets/import/Nest.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | function Nest(obj, f, f1) { 4 | this.obj = obj; 5 | this.f = f; 6 | this.f1 = f1; 7 | } 8 | 9 | Nest.prototype.setImportA = function (importA) { 10 | this.importA = importA; 11 | }; 12 | 13 | Nest.prototype.setD = function (d) { 14 | this.d = d; 15 | }; 16 | 17 | Nest.prototype.isNumber = function (v) { 18 | return this.f.isNumber(v); 19 | }; 20 | 21 | return Nest; 22 | } 23 | ); -------------------------------------------------------------------------------- /test/assets/import/config.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | return function () { 4 | return { 5 | // import test 6 | importA: { 7 | module: 'import/A', 8 | args: [ 9 | { 10 | $import: 'a2', 11 | properties: { 12 | importTest: 'importTest' 13 | } 14 | } 15 | ], 16 | properties: { 17 | myUtil: { 18 | $import: 'myUtil', 19 | properties: { 20 | importProp: 'importProp', 21 | importRef: { 22 | $ref: 'utilCreator' 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | importNest: { 29 | module: 'import/Nest', 30 | args: [ 31 | { 32 | $import: 'a', 33 | properties: { 34 | util: { 35 | $import: 'myUtil', 36 | args: [ 37 | { 38 | $import: 'a', 39 | properties: { 40 | argImportProp: 'argImportProp', 41 | d3: { 42 | $import: 'd3', 43 | properties: { 44 | d3Prop: 'd3Prop' 45 | } 46 | } 47 | } 48 | } 49 | ], 50 | properties: { 51 | importProp: 'importProp' 52 | } 53 | } 54 | } 55 | }, 56 | { 57 | $import: 'f' 58 | }, 59 | { 60 | $import: 'f', 61 | properties: { 62 | repeatImport: 'repeatImport' 63 | } 64 | } 65 | ], 66 | properties: { 67 | c: { 68 | $import: 'c', 69 | properties: { 70 | cProp: 'nestProp' 71 | } 72 | }, 73 | c1: { 74 | $import: 'c', 75 | properties: { 76 | repeatImport: 'repeatImport' 77 | } 78 | } 79 | }, 80 | scope: 'singleton', 81 | auto: true 82 | } 83 | }; 84 | }; 85 | } 86 | ); -------------------------------------------------------------------------------- /test/assets/list/A.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | function A(listArg1, listArg2) { 4 | this.listArg1 = listArg1; 5 | this.listArg2 = listArg2; 6 | } 7 | 8 | A.prototype.setListProperty = function (listProperty) { 9 | this.listProperty = listProperty; 10 | }; 11 | 12 | return A; 13 | } 14 | ); -------------------------------------------------------------------------------- /test/assets/list/config.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | return function () { 4 | return { 5 | simpleList: { 6 | module: 'list/A', 7 | args: [ 8 | { 9 | $list: [ 10 | {literalObject: 1}, 11 | {$ref: 'a'}, 12 | {$ref: 'b'}, 13 | 'literalValue' 14 | ] 15 | }, 16 | { 17 | $list: [ 18 | {$ref: 'a2'}, 19 | {$ref: 'b2'}, 20 | {$ref: 'autoInject'} 21 | ] 22 | } 23 | ], 24 | properties: { 25 | listProp: { 26 | $list: [ 27 | {$ref: 'myUtil'}, 28 | {$ref: 'b3'}, 29 | {$ref: 'autoInject1'} 30 | ] 31 | }, 32 | normalProp: 'normalProp' 33 | } 34 | }, 35 | nestList: { 36 | module: 'list/A', 37 | args: [ 38 | { 39 | $list: [ 40 | {$list: ['literalValue', {$ref: 'a'}, {$ref: 'b'}]}, 41 | {$ref: 'a'}, 42 | {$ref: 'b'}, 43 | 'literalValue' 44 | ] 45 | }, 46 | { 47 | $list: [ 48 | {$ref: 'a2'}, 49 | {$ref: 'b2'}, 50 | {$list: [{$ref: 'autoInject'}, 'normalValue']} 51 | ] 52 | } 53 | ], 54 | properties: { 55 | nestProp: { 56 | $list: [ 57 | {$ref: 'myUtil'}, 58 | {$ref: 'b3'}, 59 | {$list: [{$ref: 'autoInject'}, 'normalValue']} 60 | ] 61 | } 62 | } 63 | }, 64 | importNestList: { 65 | module: 'list/A', 66 | args: [ 67 | { 68 | $list: [ 69 | { 70 | $list: [ 71 | 'literalValue', 72 | { 73 | $import: 'a', 74 | properties: {importProp: 'importProp'} 75 | }, 76 | {$ref: 'b'} 77 | ] 78 | }, 79 | { 80 | $import: 'b', 81 | properties: {importProp: 'importProp'} 82 | }, 83 | 'literalValue' 84 | ] 85 | }, 86 | { 87 | $list: [ 88 | {$ref: 'a2'}, 89 | {$ref: 'b2'}, 90 | {$list: [{$ref: 'autoInject'}, 'normalValue']} 91 | ] 92 | } 93 | ], 94 | properties: { 95 | importListProp: { 96 | $list: [ 97 | { 98 | $import: 'myUtil', 99 | properties: {prop: 'prop'} 100 | }, 101 | {$ref: 'b3'}, 102 | {$list: [{$import: 'autoInject', properties: {prop: 'prop'}}, 'normalValue']} 103 | ] 104 | }, 105 | nestProp: { 106 | $list: [ 107 | {$ref: 'myUtil'}, 108 | {$ref: 'b3'}, 109 | {$list: [{$ref: 'autoInject'}, 'normalValue']} 110 | ] 111 | } 112 | } 113 | } 114 | }; 115 | }; 116 | } 117 | ); -------------------------------------------------------------------------------- /test/assets/list/list.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | function A(listArg1, listArg2) { 4 | this.listArg1 = listArg1; 5 | this.listArg2 = listArg2; 6 | } 7 | 8 | A.prototype.setListProperty = function (listProperty) { 9 | this.listProperty = listProperty; 10 | }; 11 | 12 | return A; 13 | } 14 | ); -------------------------------------------------------------------------------- /test/assets/map/A.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | function A(arg1, arg2) { 4 | this.arg1 = arg1; 5 | this.arg2 = arg2; 6 | } 7 | 8 | A.prototype.setImportListProp = function (importListProp) { 9 | this.importListProp = importListProp; 10 | this.isCalled = true; 11 | }; 12 | 13 | return A; 14 | } 15 | ); -------------------------------------------------------------------------------- /test/assets/map/config.js: -------------------------------------------------------------------------------- 1 | define( 2 | function (require) { 3 | return function () { 4 | return { 5 | simpleMap: { 6 | module: 'map/A', 7 | args: [ 8 | { 9 | $map: { 10 | literalObject: {value: 1}, 11 | a: {$ref: 'a'}, 12 | b: {$ref: 'b'}, 13 | literalValue: 'literalValue' 14 | } 15 | }, 16 | { 17 | $map: { 18 | a2: {$ref: 'a2'}, 19 | b2: {$ref: 'b2'}, 20 | autoInject: {$ref: 'autoInject'} 21 | } 22 | } 23 | ], 24 | properties: { 25 | mapProp: { 26 | $map: { 27 | myUtil: { 28 | $ref: 'myUtil' 29 | }, 30 | b3: { 31 | $ref: 'b3' 32 | }, 33 | autoInject1: { 34 | $ref: 'autoInject1' 35 | } 36 | } 37 | }, 38 | normalProp: 'normalProp' 39 | } 40 | }, 41 | nestMap: { 42 | module: 'map/A', 43 | args: [ 44 | { 45 | $map: { 46 | mapCollection: { 47 | $map: { 48 | literalValue: 'literalValue', 49 | a: {$ref: 'a'}, 50 | b: {$ref: 'b'} 51 | } 52 | }, 53 | a: {$ref: 'a'}, 54 | b: {$ref: 'b'}, 55 | literalValue: 'literalValue' 56 | } 57 | }, 58 | { 59 | $map: { 60 | a2: {$ref: 'a2'}, 61 | b2: {$ref: 'b2'}, 62 | list: {$list: [{$ref: 'autoInject'}, 'normalValue']} 63 | } 64 | } 65 | ], 66 | properties: { 67 | nestProp: { 68 | $map: { 69 | myUtil: { 70 | $ref: 'myUtil' 71 | }, 72 | b3: { 73 | $ref: 'b3' 74 | }, 75 | map: { 76 | $map: { 77 | list: {$list: [{$ref: 'autoInject'}, 'normalValue']} 78 | } 79 | } 80 | } 81 | } 82 | } 83 | }, 84 | importNestMap: { 85 | module: 'map/A', 86 | args: [ 87 | { 88 | $list: [ 89 | { 90 | $map: { 91 | literalValue: 'literalValue', 92 | a: { 93 | $import: 'a', 94 | properties: {importProp: 'importProp'} 95 | }, 96 | b: {$ref: 'b'} 97 | } 98 | }, 99 | { 100 | $import: 'b', 101 | properties: {importProp: 'importProp'} 102 | }, 103 | 'literalValue' 104 | ] 105 | }, 106 | { 107 | $list: [ 108 | {$ref: 'a2'}, 109 | {$ref: 'b2'}, 110 | { 111 | $map: { 112 | autoInject: {$import: 'autoInject', properties: {importProp: 'importProp'}}, 113 | normalValue: 'normalValue' 114 | } 115 | } 116 | ] 117 | } 118 | ], 119 | properties: { 120 | importListProp: { 121 | $list: [ 122 | { 123 | $import: 'myUtil', 124 | properties: {prop: 'prop'} 125 | }, 126 | {$ref: 'b3'}, 127 | { 128 | $map: { 129 | autoInject: {$import: 'autoInject', properties: {importProp: 'importProp'}}, 130 | normalValue: 'normalValue' 131 | } 132 | } 133 | ] 134 | }, 135 | nestProp: { 136 | $map: { 137 | myUtil: { 138 | $ref: 'myUtil' 139 | }, 140 | b3: { 141 | $ref: 'b3' 142 | }, 143 | nestListMap: { 144 | $map: { 145 | list: {$list: [{$import: 'autoInject'}]}, 146 | normalValue: 'normalValue', 147 | importUtil: {$import: 'myUtil'} 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | }; 155 | }; 156 | } 157 | ); -------------------------------------------------------------------------------- /test/spec/aop.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'ioc'; 2 | import FixtureAspect from 'aop/FixtureAspect'; 3 | 4 | function getIoCWithAopConfig(aopConfig, properties = {}) { 5 | return new IoC({ 6 | components: { 7 | fixture: { 8 | module: 'aop/Fixture', 9 | properties, 10 | aopConfig 11 | } 12 | }, 13 | loader(ids, cb) { 14 | require(ids, (...modules) => { 15 | modules = modules.map( 16 | module => module.__esModule ? ('default' in module ? module['default'] : module) : module); 17 | cb(...modules); 18 | }); 19 | } 20 | }); 21 | } 22 | 23 | function testMethod(proxy, aspect, methodName) { 24 | let originLog = { 25 | beforeCount: aspect.beforeLog.count, 26 | aroundCount: aspect.aroundLog.count, 27 | afterReturningCount: aspect.afterReturningLog.count, 28 | afterThrowingCount: aspect.afterThrowingLog.count, 29 | afterCount: aspect.afterLog.count 30 | }; 31 | 32 | proxy[methodName](1, 2, 3); 33 | 34 | expect(aspect.beforeLog.count).toBe(originLog.beforeCount + 1); 35 | expect(aspect.beforeLog.args.toString()).toBe('1,2,3'); 36 | 37 | expect(aspect.aroundLog.count).toBe(originLog.aroundCount + 1); 38 | expect(aspect.aroundLog.joinPoint.target).toBe(proxy); 39 | expect(aspect.aroundLog.joinPoint.args.toString()).toBe('1,2,3'); 40 | 41 | expect(aspect.afterReturningLog.count).toBe(originLog.afterReturningCount + 1); 42 | expect(aspect.afterReturningLog.return).toBe(proxy[methodName + 'Result']); 43 | 44 | expect(aspect.afterThrowingLog.count).toBe(originLog.afterThrowingCount); 45 | 46 | expect(aspect.afterLog.count).toBe(originLog.afterCount + 1); 47 | 48 | // exception 49 | if (methodName === 'method1') { 50 | proxy.throwError = new Error('error'); 51 | aspect.afterReturningLog.return = null; 52 | try { 53 | expect(() => proxy[methodName]('exception')).toThrowError('error'); 54 | } 55 | catch (e) { 56 | 57 | } 58 | expect(aspect.beforeLog.count).toBe(originLog.beforeCount + 2); 59 | expect(aspect.beforeLog.args.toString()).toBe('exception'); 60 | 61 | expect(aspect.aroundLog.count).toBe(originLog.aroundCount + 2); 62 | expect(aspect.aroundLog.joinPoint.target).toBe(proxy); 63 | expect(aspect.aroundLog.joinPoint.args.toString()).toBe('exception'); 64 | expect(aspect.aroundLog.joinPoint.method).toBe(methodName); 65 | 66 | expect(aspect.afterReturningLog.count).toBe(originLog.afterReturningCount + 1); 67 | expect(aspect.afterReturningLog.return).toBe(null); 68 | 69 | expect(aspect.afterThrowingLog.count).toBe(originLog.afterThrowingCount + 1); 70 | expect(aspect.afterThrowingLog.error).toBe(proxy.throwError); 71 | 72 | expect(aspect.afterLog.count).toBe(originLog.afterCount + 2); 73 | } 74 | } 75 | 76 | describe('AopPlugin literal advices test: object proxy', () => { 77 | let fixtureAdvices = null; 78 | 79 | beforeEach(() => fixtureAdvices = FixtureAspect.getAdvices()); 80 | 81 | it('should intercept the string-matched method', done => { 82 | let ioc = getIoCWithAopConfig({ 83 | proxyTarget: 'object', 84 | advisors: [ 85 | { 86 | matcher: 'method1', 87 | advices: fixtureAdvices 88 | } 89 | ] 90 | }); 91 | 92 | ioc.getComponent('fixture').then(proxy => { 93 | testMethod(proxy, fixtureAdvices.aspect, 'method1'); 94 | expect(proxy.count1).toBe(2); 95 | done(); 96 | }); 97 | }); 98 | 99 | it('should intercept the RegExp-matched method', done => { 100 | let ioc = getIoCWithAopConfig({ 101 | proxyTarget: 'object', 102 | advisors: [ 103 | { 104 | matcher: /method/, 105 | advices: fixtureAdvices 106 | }, 107 | { 108 | matcher: 'propertyValue', 109 | advices: fixtureAdvices 110 | }, 111 | { 112 | matcher: 'callMethod3', 113 | advices: fixtureAdvices 114 | } 115 | ] 116 | }); 117 | 118 | ioc.getComponent('fixture').then(proxy => { 119 | testMethod(proxy, fixtureAdvices.aspect, 'method1'); 120 | expect(proxy.count1).toBe(2); 121 | testMethod(proxy, fixtureAdvices.aspect, 'method2'); 122 | expect(proxy.count2).toBe(1); 123 | testMethod(proxy, fixtureAdvices.aspect, 'method3'); 124 | expect(proxy.count3).toBe(1); 125 | 126 | // 不影响返回值非函数的 get 127 | expect(proxy.propertyValue).toBe('propertyValue'); 128 | 129 | // 返回值为 function 的 get 会动态创建函数代理 130 | expect(proxy.callMethod3 !== proxy.callMethod3).toBe(true); 131 | done(); 132 | }); 133 | }); 134 | 135 | it('should intercept the Function-matched method', done => { 136 | let ioc = getIoCWithAopConfig({ 137 | proxyTarget: 'object', 138 | advisors: [ 139 | { 140 | matcher: property => ['callMethod3', 'foo'].indexOf(property) !== -1, 141 | advices: fixtureAdvices 142 | } 143 | ] 144 | }); 145 | 146 | ioc.getComponent('fixture').then( 147 | proxy => { 148 | testMethod(proxy, fixtureAdvices.aspect, 'foo'); 149 | expect(proxy.fooCount).toBe(1); 150 | 151 | let aspect = fixtureAdvices.aspect; 152 | let originLog = { 153 | beforeCount: aspect.beforeLog.count, 154 | aroundCount: aspect.aroundLog.count, 155 | afterReturningCount: aspect.afterReturningLog.count, 156 | afterThrowingCount: aspect.afterThrowingLog.count, 157 | afterCount: aspect.afterLog.count 158 | }; 159 | 160 | let result = proxy.callMethod3('callMethod3'); 161 | expect(result).toBe(proxy.method3Result); 162 | expect(aspect.beforeLog.count).toBe(originLog.beforeCount + 1); 163 | expect(aspect.beforeLog.args.toString()).toBe('callMethod3'); 164 | 165 | expect(aspect.aroundLog.count).toBe(originLog.aroundCount + 1); 166 | expect(aspect.aroundLog.joinPoint.target).toBe(proxy); 167 | expect(aspect.aroundLog.joinPoint.args.toString()).toBe('callMethod3'); 168 | 169 | expect(aspect.afterReturningLog.count).toBe(originLog.afterReturningCount + 1); 170 | expect(aspect.afterReturningLog.return).toBe(result); 171 | 172 | expect(aspect.afterThrowingLog.count).toBe(originLog.afterThrowingCount); 173 | 174 | expect(aspect.afterLog.count).toBe(originLog.afterCount + 1); 175 | done(); 176 | } 177 | ); 178 | }); 179 | 180 | it('should intercept object after create instance', done => { 181 | let ioc = getIoCWithAopConfig( 182 | { 183 | proxyTarget: 'object', 184 | advisors: [ 185 | { 186 | matcher: 'method1', 187 | advices: { 188 | around() {} 189 | } 190 | } 191 | ] 192 | }, 193 | {x: 1} 194 | ); 195 | 196 | ioc.getComponent('fixture').then(proxy => { 197 | expect(proxy.x).toBe(1); 198 | expect(proxy.print()).toBe(undefined); 199 | done(); 200 | }); 201 | }); 202 | }); 203 | 204 | describe('AopPlugin literal advices test: class proxy', () => { 205 | let fixtureAdvices = null; 206 | 207 | beforeEach(() => fixtureAdvices = FixtureAspect.getAdvices()); 208 | 209 | it('should intercept the string-matched method', done => { 210 | let ioc = getIoCWithAopConfig({ 211 | proxyTarget: 'class', 212 | advisors: [ 213 | { 214 | matcher: 'method1', 215 | advices: fixtureAdvices 216 | } 217 | ] 218 | }); 219 | 220 | ioc.getComponent('fixture').then(proxy => { 221 | testMethod(proxy, fixtureAdvices.aspect, 'method1'); 222 | expect(proxy.count1).toBe(2); 223 | done(); 224 | }); 225 | }); 226 | 227 | it('should intercept the RegExp-matched method', done => { 228 | let ioc = getIoCWithAopConfig({ 229 | proxyTarget: 'class', 230 | advisors: [ 231 | { 232 | matcher: /method/, 233 | advices: fixtureAdvices 234 | }, 235 | { 236 | matcher: 'propertyValue', 237 | advices: fixtureAdvices 238 | }, 239 | { 240 | matcher: 'callMethod3', 241 | advices: fixtureAdvices 242 | } 243 | ] 244 | }); 245 | 246 | ioc.getComponent('fixture').then(proxy => { 247 | testMethod(proxy, fixtureAdvices.aspect, 'method1'); 248 | expect(proxy.count1).toBe(2); 249 | testMethod(proxy, fixtureAdvices.aspect, 'method2'); 250 | expect(proxy.count2).toBe(1); 251 | testMethod(proxy, fixtureAdvices.aspect, 'method3'); 252 | expect(proxy.count3).toBe(1); 253 | 254 | // 不影响返回值非函数的 get 255 | expect(proxy.propertyValue).toBe('propertyValue'); 256 | 257 | // 返回值为 function 的 get 会动态创建函数代理 258 | expect(proxy.callMethod3 !== proxy.callMethod3).toBe(true); 259 | 260 | done(); 261 | }); 262 | }); 263 | 264 | it('should intercept the Function-matched method', done => { 265 | let ioc = getIoCWithAopConfig({ 266 | proxyTarget: 'class', 267 | advisors: [ 268 | { 269 | matcher: property => ['callMethod3', 'foo'].indexOf(property) !== -1, 270 | advices: fixtureAdvices 271 | } 272 | ] 273 | }); 274 | 275 | ioc.getComponent('fixture').then(proxy => { 276 | testMethod(proxy, fixtureAdvices.aspect, 'foo'); 277 | expect(proxy.fooCount).toBe(1); 278 | 279 | let aspect = fixtureAdvices.aspect; 280 | let originLog = { 281 | beforeCount: aspect.beforeLog.count, 282 | aroundCount: aspect.aroundLog.count, 283 | afterReturningCount: aspect.afterReturningLog.count, 284 | afterThrowingCount: aspect.afterThrowingLog.count, 285 | afterCount: aspect.afterLog.count 286 | }; 287 | 288 | let result = proxy.callMethod3('callMethod3'); 289 | expect(result).toBe(proxy.method3Result); 290 | expect(aspect.beforeLog.count).toBe(originLog.beforeCount + 1); 291 | expect(aspect.beforeLog.args.toString()).toBe('callMethod3'); 292 | 293 | expect(aspect.aroundLog.count).toBe(originLog.aroundCount + 1); 294 | expect(aspect.aroundLog.joinPoint.target).toBe(proxy); 295 | expect(aspect.aroundLog.joinPoint.args.toString()).toBe('callMethod3'); 296 | 297 | expect(aspect.afterReturningLog.count).toBe(originLog.afterReturningCount + 1); 298 | expect(aspect.afterReturningLog.return).toBe(result); 299 | 300 | expect(aspect.afterThrowingLog.count).toBe(originLog.afterThrowingCount); 301 | 302 | expect(aspect.afterLog.count).toBe(originLog.afterCount + 1); 303 | done(); 304 | }); 305 | }); 306 | 307 | it('should intercept class before create instance', done => { 308 | let ioc = getIoCWithAopConfig( 309 | { 310 | proxyTarget: 'class', 311 | advisors: [ 312 | { 313 | matcher: 'method1', 314 | advices: { 315 | around() {} 316 | } 317 | } 318 | ] 319 | }, 320 | {x: 1} 321 | ); 322 | 323 | ioc.getComponent('fixture').then(proxy => { 324 | expect(proxy.x).toBe(1); 325 | expect(proxy.print()).toBe(1); 326 | done(); 327 | }); 328 | }); 329 | }); 330 | -------------------------------------------------------------------------------- /test/spec/auto.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'ioc'; 2 | import A from 'A'; 3 | import B from 'B'; 4 | import C from 'C'; 5 | import D from 'D'; 6 | import MyUtil from 'MyUtil'; 7 | import AutoInject from 'AutoInject'; 8 | import AutoInject1 from 'AutoInject1'; 9 | import MyFactory from 'MyFactory'; 10 | import config from 'config'; 11 | 12 | describe('auto inject test', () => { 13 | 14 | function assertInstanceOf(Constructor, instance) { 15 | expect(instance instanceof Constructor).toBe(true); 16 | } 17 | 18 | function assertNull(v) { 19 | expect(v).toBeNull(); 20 | } 21 | 22 | let iocInstance = null; 23 | beforeEach(() => iocInstance = new IoC(config())); 24 | 25 | it('normal inject', done => { 26 | spyOn(AutoInject.prototype, 'setd'); 27 | spyOn(AutoInject.prototype, 'settest'); 28 | iocInstance.getComponent('autoInject').then( 29 | autoInject => { 30 | assertInstanceOf(A, autoInject.a); 31 | assertInstanceOf(B, autoInject.b); 32 | assertInstanceOf(C, autoInject.c); 33 | assertInstanceOf(D, autoInject.d); 34 | assertInstanceOf(MyUtil, autoInject.d.b.util); 35 | 36 | assertNull(autoInject.e); 37 | expect(autoInject.setd).not.toHaveBeenCalled(); 38 | expect(autoInject.settest).not.toHaveBeenCalled(); 39 | 40 | let anotherInject = autoInject.anotherAutoInject; 41 | assertInstanceOf(AutoInject, anotherInject); 42 | assertInstanceOf(AutoInject1, anotherInject); 43 | assertInstanceOf(A, anotherInject.a); 44 | assertInstanceOf(B, anotherInject.b); 45 | assertInstanceOf(C, anotherInject.c); 46 | assertInstanceOf(D, anotherInject.d); 47 | assertInstanceOf(MyUtil, anotherInject.d.b.util); 48 | 49 | assertNull(anotherInject.e); 50 | done(); 51 | } 52 | ); 53 | }); 54 | 55 | it('setter and property priority', done => { 56 | iocInstance.getComponent('autoInject').then( 57 | autoInject => { 58 | expect(autoInject.myFactory).toBe('myFactory'); 59 | expect(autoInject.setCCalledCount).toBe(1); 60 | assertInstanceOf(MyFactory, autoInject.anotherAutoInject.myFactory); 61 | done(); 62 | } 63 | ); 64 | }); 65 | 66 | it('setter dependency which has no register', done => { 67 | spyOn(AutoInject.prototype, 'setUnRegisterComponent'); 68 | iocInstance.getComponent('autoInject').then( 69 | autoInject => { 70 | expect(autoInject.setUnRegisterComponent).not.toHaveBeenCalled(); 71 | done(); 72 | } 73 | ); 74 | }); 75 | 76 | }); -------------------------------------------------------------------------------- /test/spec/circular.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'ioc'; 2 | import config from 'config'; 3 | 4 | describe('Ioc Circular Dependency Checking Test: ', () => { 5 | 6 | it('checking circular', async done => { 7 | let iocInstance = new IoC(config()); 8 | try { 9 | await iocInstance.getComponent('circular1'); 10 | } 11 | catch (e) { 12 | expect(e.message).toBe('circular3 has circular dependencies '); 13 | done(); 14 | } 15 | }); 16 | 17 | it('skip checking circular', async done => { 18 | let iocInstance = new IoC(config({skipCheckingCircularDep: true})); 19 | try { 20 | await iocInstance.getComponent('circular1'); 21 | } 22 | catch (e) { 23 | expect(e.constructor === RangeError).toBe(true); 24 | done(); 25 | } 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/spec/import.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'ioc'; 2 | import A from 'A'; 3 | import B from 'B'; 4 | import C from 'C'; 5 | import D from 'D'; 6 | import F from 'F'; 7 | import MyUtil from 'MyUtil'; 8 | import ImportA from 'import/A'; 9 | import Nest from 'import/Nest'; 10 | 11 | import config from 'config'; 12 | 13 | describe('import test: ', () => { 14 | let iocInstance = null; 15 | beforeEach(() => iocInstance = new IoC(config())); 16 | 17 | 18 | it('simple import', done => { 19 | iocInstance.getComponent('importA').then( 20 | importIns => { 21 | let obj = importIns.obj; 22 | expect(obj instanceof A).toBe(true); 23 | expect(obj.importTest).toBe('importTest'); 24 | expect(obj.b instanceof B).toBe(true); 25 | expect(obj.b.name).toBe('Tony Blair'); 26 | expect(obj.b.c instanceof C).toBe(true); 27 | expect(obj.b.util instanceof MyUtil).toBe(true); 28 | 29 | let myUtil = importIns.myUtil; 30 | expect(myUtil instanceof MyUtil).toBe(true); 31 | expect(myUtil.importProp).toBe('importProp'); 32 | 33 | let importRef = myUtil.importRef; 34 | expect(importRef instanceof MyUtil.creator).toBe(true); 35 | expect(importRef.a instanceof A).toBe(true); 36 | expect(importRef.b instanceof B).toBe(true); 37 | 38 | done(); 39 | } 40 | ); 41 | }); 42 | 43 | it('nested import', done => { 44 | iocInstance.getComponent('importNest').then( 45 | nest => { 46 | expect(nest instanceof Nest).toBe(true); 47 | expect(nest.f instanceof F).toBe(true); 48 | expect(nest.f1 instanceof F).toBe(true); 49 | expect(nest.f1.repeatImport).toBe('repeatImport'); 50 | 51 | expect(nest.c instanceof C).toBe(true); 52 | expect(nest.c1 instanceof C).toBe(true); 53 | expect(nest.c.cProp).toBe('nestProp'); 54 | expect(nest.c1.repeatImport).toBe('repeatImport'); 55 | 56 | let a = nest.obj; 57 | expect(a instanceof A).toBe(true); 58 | expect(a.util instanceof MyUtil).toBe(true); 59 | expect(a.util.importProp).toBe('importProp'); 60 | 61 | let utilObj = a.util.obj; 62 | expect(utilObj instanceof A).toBe(true); 63 | expect(utilObj.argImportProp).toBe('argImportProp'); 64 | 65 | let utilObjD3 = utilObj.d3; 66 | expect(utilObjD3 instanceof D).toBe(true); 67 | expect(utilObjD3.d3Prop).toBe('d3Prop'); 68 | 69 | done(); 70 | } 71 | ); 72 | }); 73 | 74 | it('nested import with auto and singleton', done => { 75 | iocInstance.getComponent('importNest').then( 76 | nest1 => iocInstance.getComponent('importNest').then(nest2 => [nest1, nest2]) 77 | ).then(([nest1, nest2]) => { 78 | expect(nest1).toBe(nest2); 79 | expect(nest1.importA instanceof ImportA).toBe(true); 80 | expect(nest1.d instanceof D).toBe(true); 81 | expect(nest1.isNumber(1)).toBe(true); 82 | 83 | done(); 84 | }); 85 | }); 86 | 87 | it('nested parallel import with auto and singleton', done => { 88 | Promise.all([ 89 | iocInstance.getComponent('importNest'), 90 | iocInstance.getComponent('importNest') 91 | ]).then(([nest1, nest2]) => { 92 | expect(nest1).toBe(nest2); 93 | expect(nest1.importA instanceof ImportA).toBe(true); 94 | expect(nest1.d instanceof D).toBe(true); 95 | expect(nest1.isNumber(1)).toBe(true); 96 | 97 | done(); 98 | }); 99 | }) 100 | }); 101 | -------------------------------------------------------------------------------- /test/spec/integration.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'ioc'; 2 | import A from 'A'; 3 | import B from 'B'; 4 | import C from 'C'; 5 | import D from 'D'; 6 | import MyUtil from 'MyUtil'; 7 | import MyFactory from 'MyFactory'; 8 | import config from 'config'; 9 | 10 | describe('Ioc Integration Test: ', () => { 11 | let iocInstance = null; 12 | 13 | function assertInstanceOf(Constructor, instance) { 14 | expect(instance instanceof Constructor).toBe(true); 15 | } 16 | 17 | function assertSame(a, b) { 18 | expect(a).toBe(b); 19 | } 20 | 21 | function assertEqual(a, b) { 22 | expect(a).toEqual(b); 23 | } 24 | 25 | function assertNull(v) { 26 | expect(v).toBeNull(); 27 | } 28 | 29 | beforeEach(() => iocInstance = new IoC(config())); 30 | 31 | it('customLoader', async done => { 32 | let calledWidthArgs = {}; 33 | iocInstance = new IoC(); 34 | iocInstance.addComponent(config().components); 35 | iocInstance.setLoaderFunction( 36 | (...args) => { 37 | calledWidthArgs[args[0][0]] = 1; 38 | return require(...args); 39 | } 40 | ); 41 | let myFactory = await iocInstance.getComponent('myFactory'); 42 | assertInstanceOf(MyFactory, myFactory); 43 | expect(calledWidthArgs.MyFactory).toBe(1); 44 | done(); 45 | }); 46 | 47 | it('simpleInstance', async done => { 48 | let a = await iocInstance.getComponent('a'); 49 | assertInstanceOf(A, a); 50 | done(); 51 | }); 52 | 53 | it('multiInstantiate', async done => { 54 | let [a, b, c] = await iocInstance.getComponent(['a', 'b', 'c']); 55 | assertInstanceOf(A, a); 56 | assertInstanceOf(B, b); 57 | assertInstanceOf(C, c); 58 | done(); 59 | }); 60 | 61 | it('simpleInstanceNull', async done => { 62 | let [a, b] = await iocInstance.getComponent(['a', 'b']); 63 | assertInstanceOf(A, a); 64 | assertInstanceOf(B, b); 65 | done(); 66 | }); 67 | 68 | it('singletonInstance', async done => { 69 | let [factory1, factory2] = await iocInstance.getComponent(['myFactory', 'myFactory']); 70 | assertInstanceOf(MyFactory, factory1); 71 | assertSame(factory1, factory2); 72 | done(); 73 | }); 74 | 75 | it('simpleConstructorInjectLiterals', async done => { 76 | let c = await iocInstance.getComponent('c'); 77 | assertSame(c.str, 'String'); 78 | assertSame(c.number, 99); 79 | assertSame(c.bool, true); 80 | expect(c.nully).toBeNull(); 81 | done(); 82 | }); 83 | 84 | it('simpleConstructorInjectDependency', async done => { 85 | let [a, a2] = await iocInstance.getComponent(['a', 'a2']); 86 | assertInstanceOf(A, a); 87 | assertInstanceOf(A, a2); 88 | 89 | assertInstanceOf(B, a.b); 90 | assertInstanceOf(B, a2.b); 91 | 92 | assertInstanceOf(C, a.b.c); 93 | assertInstanceOf(C, a2.b.c); 94 | assertSame(a.b.c.str, 'String'); 95 | assertSame(a.b.c.number, 99); 96 | assertSame(a.b.c.bool, true); 97 | assertNull(a.b.c.nully); 98 | expect(a.b.c.cProp).toBe('cProp'); 99 | 100 | done(); 101 | }); 102 | 103 | it('simplePropertyInjectLiterals', async done => { 104 | let d = await iocInstance.getComponent('d'); 105 | assertSame(d.str, 'hi'); 106 | assertSame(d.number, 88); 107 | assertSame(d.bool, false); 108 | assertNull(d.nully); 109 | assertSame(d.fromMethod, 'set'); 110 | assertEqual(d.fromMethodArray, ['one', 'two']); 111 | 112 | done(); 113 | }); 114 | 115 | it('simplePropertyInjectDependency', async done => { 116 | let d = await iocInstance.getComponent('d'); 117 | assertInstanceOf(D, d); 118 | assertInstanceOf(B, d.b); 119 | assertInstanceOf(C, d.b.c); 120 | 121 | assertSame(d.b.c.str, 'String'); 122 | assertSame(d.b.c.number, 99); 123 | assertSame(d.b.c.bool, true); 124 | assertNull(d.b.c.nully); 125 | assertSame(d.b.name, 'Tony Blair'); 126 | 127 | assertInstanceOf(MyUtil, d.b.util); 128 | 129 | done(); 130 | }); 131 | 132 | it('Simple Creator Function', async done => { 133 | let creatorFn = await iocInstance.getComponent('creatorFn'); 134 | assertInstanceOf(A, creatorFn.a); 135 | assertInstanceOf(B, creatorFn.b); 136 | creatorFn.dispose = () => {}; 137 | spyOn(creatorFn, 'dispose'); 138 | iocInstance.dispose(); 139 | expect(creatorFn.dispose).toHaveBeenCalled(); 140 | done(); 141 | }); 142 | 143 | it('utilsInject', async done => { 144 | let b3 = await iocInstance.getComponent('b3'); 145 | assertSame(b3.useUtil(), true); 146 | done(); 147 | }); 148 | 149 | it('utilCreator', async done => { 150 | let utilCreator = await iocInstance.getComponent('utilCreator'); 151 | assertInstanceOf(MyUtil.creator, utilCreator); 152 | assertInstanceOf(A, utilCreator.a); 153 | assertInstanceOf(B, utilCreator.b); 154 | assertInstanceOf(C, utilCreator.c); 155 | done(); 156 | }); 157 | 158 | it('utilFactoryCreator', async done => { 159 | let utilFactoryCreator = await iocInstance.getComponent('utilFactoryCreator'); 160 | expect(utilFactoryCreator.constructor).toBe(Object); 161 | assertInstanceOf(A, utilFactoryCreator.a); 162 | assertInstanceOf(B, utilFactoryCreator.b); 163 | assertInstanceOf(C, utilFactoryCreator.c); 164 | done(); 165 | }); 166 | 167 | it('should return the same and right object when scope is static ', async done => { 168 | let [f1, f2] = await iocInstance.getComponent(['f', 'f']); 169 | assertSame(f1.isNumber(999), true); 170 | assertSame(f1.isNumber('NaN'), false); 171 | assertSame(f1.$, f2.$); 172 | done(); 173 | }); 174 | 175 | it('should throw error when add an existing component', done => { 176 | let id = Symbol('id'); 177 | iocInstance.addComponent(id, {creator: Object}); 178 | try { 179 | iocInstance.addComponent(id, {creator: Object}); 180 | } 181 | catch (e) { 182 | expect(e.message).toBe(`${String(id)} has been added!`); 183 | done(); 184 | } 185 | }); 186 | 187 | it('should return a rejected promise when getting an non-existing component', async done => { 188 | try { 189 | await iocInstance.getComponent('z'); 190 | } 191 | catch (e) { 192 | expect(e.message).toBe(`\`z\` has not been added to the Ioc`); 193 | done(); 194 | } 195 | }); 196 | }); -------------------------------------------------------------------------------- /test/spec/list.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'ioc'; 2 | import A from 'A'; 3 | import B from 'B'; 4 | import MyUtil from 'MyUtil'; 5 | import ListA from 'list/A'; 6 | import AutoInject from 'AutoInject'; 7 | import AutoInject1 from 'AutoInject1'; 8 | import config from 'config'; 9 | 10 | describe('list test: ', () => { 11 | let iocInstance = null; 12 | beforeEach(() => iocInstance = new IoC(config())); 13 | 14 | it('simple list', done => { 15 | iocInstance.getComponent('simpleList').then( 16 | simpleList => { 17 | expect(simpleList instanceof ListA).toBe(true); 18 | 19 | let listArg1 = simpleList.listArg1; 20 | let listArg2 = simpleList.listArg2; 21 | expect(listArg1 instanceof Array).toBe(true); 22 | expect(listArg1.length).toBe(4); 23 | expect(listArg1[0]).toEqual({literalObject: 1}); 24 | expect(listArg1[1] instanceof A).toBe(true); 25 | expect(listArg1[2] instanceof B).toBe(true); 26 | expect(listArg1[3]).toBe('literalValue'); 27 | 28 | expect(listArg2 instanceof Array).toBe(true); 29 | expect(listArg2.length).toBe(3); 30 | expect(listArg2[0] instanceof A).toBe(true); 31 | expect(listArg2[1] instanceof B).toBe(true); 32 | expect(listArg2[2] instanceof AutoInject).toBe(true); 33 | 34 | let listProp = simpleList.listProp; 35 | expect(listProp instanceof Array).toBe(true); 36 | expect(listProp.length).toBe(3); 37 | expect(listProp[0] instanceof MyUtil).toBe(true); 38 | expect(listProp[1] instanceof B).toBe(true); 39 | 40 | // myUtil is singleton 41 | expect(listProp[1].util).toBe(listProp[0]); 42 | expect(listProp[2] instanceof AutoInject1).toBe(true); 43 | 44 | done(); 45 | } 46 | ); 47 | }); 48 | 49 | it('nest list', done => { 50 | iocInstance.getComponent('nestList').then( 51 | nestList => { 52 | 53 | expect(nestList instanceof ListA).toBe(true); 54 | 55 | let listArg1 = nestList.listArg1; 56 | let listArg2 = nestList.listArg2; 57 | expect(listArg1 instanceof Array).toBe(true); 58 | expect(listArg1.length).toBe(4); 59 | expect(listArg1[1] instanceof A).toBe(true); 60 | expect(listArg1[1].b instanceof B).toBe(true); 61 | 62 | expect(listArg1[2] instanceof B).toBe(true); 63 | expect(listArg1[3]).toBe('literalValue'); 64 | 65 | // nest list 66 | let list = listArg1[0]; 67 | expect(list[0]).toBe('literalValue'); 68 | expect(list[1] instanceof A).toBe(true); 69 | expect(list[2] instanceof B).toBe(true); 70 | 71 | expect(listArg2 instanceof Array).toBe(true); 72 | expect(listArg2.length).toBe(3); 73 | expect(listArg2[0] instanceof A).toBe(true); 74 | expect(listArg2[1] instanceof B).toBe(true); 75 | 76 | // nest list 77 | list = listArg2[2]; 78 | expect(list[0] instanceof AutoInject).toBe(true); 79 | expect(list[1]).toBe('normalValue'); 80 | 81 | 82 | let listProp = nestList.nestProp; 83 | expect(listProp instanceof Array).toBe(true); 84 | expect(listProp.length).toBe(3); 85 | expect(listProp[0] instanceof MyUtil).toBe(true); 86 | expect(listProp[1] instanceof B).toBe(true); 87 | 88 | list = listProp[2]; 89 | expect(list[0] instanceof AutoInject).toBe(true); 90 | expect(list[1]).toBe('normalValue'); 91 | 92 | done(); 93 | } 94 | ); 95 | }); 96 | 97 | it('nest list with import', done => { 98 | iocInstance.getComponent('importNestList').then( 99 | importNestList => { 100 | 101 | expect(importNestList instanceof ListA).toBe(true); 102 | 103 | let listArg1 = importNestList.listArg1; 104 | expect(listArg1 instanceof Array).toBe(true); 105 | expect(listArg1.length).toBe(3); 106 | expect(listArg1[2]).toBe('literalValue'); 107 | expect(listArg1[1] instanceof B).toBe(true); 108 | expect(listArg1[1].importProp).toBe('importProp'); 109 | 110 | let list = listArg1[0]; 111 | expect(list[0]).toBe('literalValue'); 112 | expect(list[1] instanceof A).toBe(true); 113 | expect(list[1].importProp).toBe('importProp'); 114 | expect(list[2] instanceof B).toBe(true); 115 | 116 | let listProp = importNestList.nestProp; 117 | expect(listProp instanceof Array).toBe(true); 118 | expect(listProp.length).toBe(3); 119 | expect(listProp[0] instanceof MyUtil).toBe(true); 120 | expect(listProp[1] instanceof B).toBe(true); 121 | list = listProp[2]; 122 | expect(list[0] instanceof AutoInject).toBe(true); 123 | expect(list[1]).toBe('normalValue'); 124 | 125 | let nestProp = importNestList.importListProp; 126 | expect(nestProp[0] instanceof MyUtil).toBe(true); 127 | expect(nestProp[0].prop).toBe('prop'); 128 | expect(nestProp[1] instanceof B).toBe(true); 129 | 130 | list = nestProp[2]; 131 | expect(list[0] instanceof AutoInject).toBe(true); 132 | expect(list[0].prop).toBe('prop'); 133 | expect(list[1]).toBe('normalValue'); 134 | 135 | done(); 136 | } 137 | ); 138 | 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/spec/map.js: -------------------------------------------------------------------------------- 1 | import {IoC} from 'ioc'; 2 | import A from 'A'; 3 | import B from 'B'; 4 | import MyUtil from 'MyUtil'; 5 | import MapA from 'map/A'; 6 | import AutoInject from 'AutoInject'; 7 | import AutoInject1 from 'AutoInject1'; 8 | import config from 'config'; 9 | 10 | describe('map test: ', () => { 11 | 12 | let iocInstance = null; 13 | beforeEach(() => iocInstance = new IoC(config())); 14 | 15 | it('simple map', done => { 16 | iocInstance.getComponent('simpleMap').then( 17 | simpleMap => { 18 | expect(simpleMap instanceof MapA).toBe(true); 19 | 20 | let arg1 = simpleMap.arg1; 21 | let arg2 = simpleMap.arg2; 22 | 23 | expect(arg1.literalObject).toEqual({value: 1}); 24 | expect(arg1.a instanceof A).toBe(true); 25 | expect(arg1.b instanceof B).toBe(true); 26 | expect(arg1.literalValue).toBe('literalValue'); 27 | 28 | expect(arg2.a2 instanceof A).toBe(true); 29 | expect(arg2.b2 instanceof B).toBe(true); 30 | expect(arg2.autoInject instanceof AutoInject).toBe(true); 31 | 32 | let mapProp = simpleMap.mapProp; 33 | expect(mapProp.myUtil instanceof MyUtil).toBe(true); 34 | expect(mapProp.b3 instanceof B).toBe(true); 35 | expect(mapProp.autoInject1 instanceof AutoInject1).toBe(true); 36 | 37 | expect(simpleMap.normalProp).toBe('normalProp'); 38 | 39 | done(); 40 | } 41 | ); 42 | }); 43 | 44 | it('nest map', done => { 45 | iocInstance.getComponent('nestMap').then( 46 | nestMap => { 47 | expect(nestMap instanceof MapA).toBe(true); 48 | 49 | let arg1 = nestMap.arg1; 50 | let arg2 = nestMap.arg2; 51 | expect(arg1.a instanceof A).toBe(true); 52 | expect(arg1.b instanceof B).toBe(true); 53 | expect(arg1.literalValue).toBe('literalValue'); 54 | expect(arg1.mapCollection.literalValue).toBe('literalValue'); 55 | expect(arg1.mapCollection.a instanceof A).toBe(true); 56 | expect(arg1.mapCollection.b instanceof B).toBe(true); 57 | 58 | expect(arg2.a2 instanceof A).toBe(true); 59 | expect(arg2.b2 instanceof B).toBe(true); 60 | expect(arg2.list[0] instanceof AutoInject).toBe(true); 61 | expect(arg2.list[1]).toBe('normalValue'); 62 | 63 | let prop = nestMap.nestProp; 64 | expect(prop.myUtil instanceof MyUtil).toBe(true); 65 | expect(prop.b3 instanceof B).toBe(true); 66 | expect(prop.map.list[0] instanceof AutoInject).toBe(true); 67 | expect(prop.map.list[1]).toBe('normalValue'); 68 | 69 | 70 | done(); 71 | } 72 | ); 73 | }); 74 | 75 | it('nest map with import and list', done => { 76 | iocInstance.getComponent('importNestMap').then( 77 | importNestMap => { 78 | 79 | expect(importNestMap instanceof MapA).toBe(true); 80 | 81 | let arg1 = importNestMap.arg1; 82 | expect(arg1 instanceof Array).toBe(true); 83 | expect(arg1.length).toBe(3); 84 | expect(arg1[2]).toBe('literalValue'); 85 | expect(arg1[1] instanceof B).toBe(true); 86 | expect(arg1[1].importProp).toBe('importProp'); 87 | 88 | let map = arg1[0]; 89 | expect(map.literalValue).toBe('literalValue'); 90 | expect(map.a instanceof A).toBe(true); 91 | expect(map.a.importProp).toBe('importProp'); 92 | expect(map.b instanceof B).toBe(true); 93 | 94 | let arg2 = importNestMap.arg2; 95 | expect(arg2[0] instanceof A).toBe(true); 96 | expect(arg2[1] instanceof B).toBe(true); 97 | 98 | map = arg2[2]; 99 | expect(map.autoInject instanceof AutoInject).toBe(true); 100 | expect(map.autoInject.importProp).toBe('importProp'); 101 | expect(map.normalValue).toBe('normalValue'); 102 | 103 | let listProp = importNestMap.importListProp; 104 | expect(listProp instanceof Array).toBe(true); 105 | expect(listProp.length).toBe(3); 106 | expect(listProp[0] instanceof MyUtil).toBe(true); 107 | expect(listProp[0].prop).toBe('prop'); 108 | expect(listProp[1] instanceof B).toBe(true); 109 | expect(importNestMap.isCalled).toBe(true); 110 | 111 | 112 | map = listProp[2]; 113 | expect(map.autoInject instanceof AutoInject).toBe(true); 114 | expect(map.autoInject.importProp).toBe('importProp'); 115 | expect(map.normalValue).toBe('normalValue'); 116 | 117 | let nestProp = importNestMap.nestProp; 118 | expect(nestProp.myUtil instanceof MyUtil).toBe(true); 119 | expect(nestProp.b3 instanceof B).toBe(true); 120 | 121 | map = nestProp.nestListMap; 122 | expect(map.list[0] instanceof AutoInject).toBe(true); 123 | expect(map.importUtil instanceof MyUtil).toBe(true); 124 | expect(map.normalValue).toBe('normalValue'); 125 | 126 | done(); 127 | } 128 | ); 129 | 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/test-main.js: -------------------------------------------------------------------------------- 1 | var TEST_REGEXP = /(test\/spec)/i; 2 | var allTestFiles = []; 3 | 4 | // Get a list of all the test files to include 5 | Object.keys(window.__karma__.files).forEach(function (file) { 6 | if (TEST_REGEXP.test(file)) { 7 | // Normalize paths to RequireJS module names. 8 | // If you require sub-dependencies of test files to be loaded as-is (requiring file extension) 9 | // then do not normalize the paths 10 | var normalizedTestModule = file.replace('/base/test/', '').replace('.js', ''); 11 | allTestFiles.push(normalizedTestModule); 12 | } 13 | }); 14 | 15 | require.config({ 16 | // Karma serves files under /base, which is the basePath from your config file 17 | baseUrl: '/base/test/assets', 18 | 19 | packages: [ 20 | { 21 | name: 'ioc', 22 | location: '../../src' 23 | }, 24 | { 25 | name: 'spec', 26 | location: '../spec' 27 | }, 28 | { 29 | name: 'uaop', 30 | location: '../../node_modules/uaop/src' 31 | } 32 | ], 33 | paths: { 34 | jquery: 'https://code.jquery.com/jquery-1.12.4.min' 35 | } 36 | }); 37 | 38 | define.amd.jQuery = true; 39 | require(allTestFiles, window.__karma__.start); --------------------------------------------------------------------------------