├── .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 
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.