├── .coveralls.yml ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build ├── js │ ├── index.bundle.js │ └── index.bundle.js.map └── lib │ ├── fluder.js │ └── fluder.js.map ├── design └── fluder-design.png ├── docs ├── api.md └── design.md ├── example ├── todoReact │ ├── actions │ │ └── todoAction.js │ ├── constants │ │ └── constants.js │ ├── css │ │ └── index.css │ ├── dispatcher │ │ └── index.js │ ├── index.css │ ├── index.html │ ├── index.js │ ├── stores │ │ └── todoStore.js │ └── views │ │ ├── footer.js │ │ ├── header.js │ │ ├── lines.js │ │ ├── root.js │ │ └── todoapp.js └── todoVue │ ├── .babelrc │ ├── .editorconfig │ ├── .eslintignore │ ├── .gitignore │ ├── README.md │ ├── build │ ├── build.js │ ├── dev-client.js │ ├── dev-server.js │ ├── utils.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js │ ├── config │ ├── dev.env.js │ ├── index.js │ ├── prod.env.js │ └── test.env.js │ ├── index.html │ ├── package.json │ ├── src │ ├── App.vue │ ├── actions │ │ └── todoAction.js │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── todoApp.vue │ │ ├── todoFooter.vue │ │ ├── todoHeader.vue │ │ └── todoMain.vue │ ├── constants │ │ └── index.js │ ├── localStore │ │ └── store.js │ ├── main.js │ └── stores │ │ └── todoStore.js │ └── static │ └── .gitkeep ├── package.json ├── src ├── actionCreator.js ├── actionStoreCreator.js ├── applyMiddleware.js ├── fluder.js ├── index.js ├── queue.js ├── storeCreator.js └── tools.js ├── test ├── actionCreate.spec.js ├── actionStoreCreate.spec.js ├── applyMiddleware.spec.js ├── fluder.spec.js ├── queue.spec.js ├── src │ ├── constants.js │ ├── formAction.js │ ├── formStore.js │ ├── index.js │ ├── middleware.js │ ├── todoAction.js │ └── todoStore.js └── storeCreate.spec.js ├── webpack.config.js └── webpack.lib.config.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: BzWSHP1hl9MDkqkE9ZkwEDcMT7EZ1AMP8 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/** 2 | /coverage/** 3 | /docs/** 4 | /example/** 5 | /node_modules/** 6 | /test/** 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard" 3 | # "extends": "airbnb",//es6 4 | } 5 | 6 | # { 7 | # "parserOptions": { 8 | # "ecmaFeatures": { 9 | # "experimentalObjectRestSpread": true, 10 | # "jsx": true 11 | # }, 12 | # "sourceType": "module" 13 | # }, 14 | # 15 | # "env": { 16 | # "es6": true, 17 | # "node": true 18 | # }, 19 | # 20 | # "plugins": [ 21 | # "standard", 22 | # "promise" 23 | # ], 24 | # 25 | # "globals": { 26 | # "document": false, 27 | # "navigator": false, 28 | # "window": false 29 | # }, 30 | # 31 | # "rules": { 32 | # "accessor-pairs": 2, 33 | # "arrow-spacing": [2, { "before": true, "after": true }], 34 | # "block-spacing": [2, "always"], 35 | # "brace-style": [2, "1tbs", { "allowSingleLine": true }], 36 | # "camelcase": [2, { "properties": "never" }], 37 | # "comma-dangle": [2, "never"], 38 | # "comma-spacing": [2, { "before": false, "after": true }], 39 | # "comma-style": [2, "last"], 40 | # "constructor-super": 2, 41 | # "curly": [2, "multi-line"], 42 | # "dot-location": [2, "property"], 43 | # "eol-last": 2, 44 | # "eqeqeq": [2, "allow-null"], 45 | # "func-call-spacing": [2, "never"], 46 | # "handle-callback-err": [2, "^(err|error)$" ], 47 | # "indent": [2, 2, { "SwitchCase": 1 }], 48 | # "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 49 | # "keyword-spacing": [2, { "before": true, "after": true }], 50 | # "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 51 | # "new-parens": 2, 52 | # "no-array-constructor": 2, 53 | # "no-caller": 2, 54 | # "no-class-assign": 2, 55 | # "no-cond-assign": 2, 56 | # "no-const-assign": 2, 57 | # "no-constant-condition": [2, { "checkLoops": false }], 58 | # "no-control-regex": 2, 59 | # "no-debugger": 2, 60 | # "no-delete-var": 2, 61 | # "no-dupe-args": 2, 62 | # "no-dupe-class-members": 2, 63 | # "no-dupe-keys": 2, 64 | # "no-duplicate-case": 2, 65 | # "no-duplicate-imports": 2, 66 | # "no-empty-character-class": 2, 67 | # "no-empty-pattern": 2, 68 | # "no-eval": 2, 69 | # "no-ex-assign": 2, 70 | # "no-extend-native": 2, 71 | # "no-extra-bind": 2, 72 | # "no-extra-boolean-cast": 2, 73 | # "no-extra-parens": [2, "functions"], 74 | # "no-fallthrough": 2, 75 | # "no-floating-decimal": 2, 76 | # "no-func-assign": 2, 77 | # "no-global-assign": 2, 78 | # "no-implied-eval": 2, 79 | # "no-inner-declarations": [2, "functions"], 80 | # "no-invalid-regexp": 2, 81 | # "no-irregular-whitespace": 2, 82 | # "no-iterator": 2, 83 | # "no-label-var": 2, 84 | # "no-labels": [2, { "allowLoop": false, "allowSwitch": false }], 85 | # "no-lone-blocks": 2, 86 | # "no-mixed-spaces-and-tabs": 2, 87 | # "no-multi-spaces": 2, 88 | # "no-multi-str": 2, 89 | # "no-multiple-empty-lines": [2, { "max": 1 }], 90 | # "no-native-reassign": 2, 91 | # "no-negated-in-lhs": 2, 92 | # "no-new": 2, 93 | # "no-new-func": 2, 94 | # "no-new-object": 2, 95 | # "no-new-require": 2, 96 | # "no-new-symbol": 2, 97 | # "no-new-wrappers": 2, 98 | # "no-obj-calls": 2, 99 | # "no-octal": 2, 100 | # "no-octal-escape": 2, 101 | # "no-path-concat": 2, 102 | # "no-proto": 2, 103 | # "no-redeclare": 2, 104 | # "no-regex-spaces": 2, 105 | # "no-return-assign": [2, "except-parens"], 106 | # "no-self-assign": 2, 107 | # "no-self-compare": 2, 108 | # "no-sequences": 2, 109 | # "no-shadow-restricted-names": 2, 110 | # "no-sparse-arrays": 2, 111 | # "no-tabs": 2, 112 | # "no-template-curly-in-string": 2, 113 | # "no-this-before-super": 2, 114 | # "no-throw-literal": 2, 115 | # "no-trailing-spaces": 2, 116 | # "no-undef": 2, 117 | # "no-undef-init": 2, 118 | # "no-unexpected-multiline": 2, 119 | # "no-unmodified-loop-condition": 2, 120 | # "no-unneeded-ternary": [2, { "defaultAssignment": false }], 121 | # "no-unreachable": 2, 122 | # "no-unsafe-finally": 2, 123 | # "no-unsafe-negation": 2, 124 | # "no-unused-vars": [2, { "vars": "all", "args": "none" }], 125 | # "no-useless-call": 2, 126 | # "no-useless-computed-key": 2, 127 | # "no-useless-constructor": 2, 128 | # "no-useless-escape": 2, 129 | # "no-useless-rename": 2, 130 | # "no-whitespace-before-property": 2, 131 | # "no-with": 2, 132 | # "object-property-newline": [2, { "allowMultiplePropertiesPerLine": true }], 133 | # "one-var": [2, { "initialized": "never" }], 134 | # "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], 135 | # "padded-blocks": [2, "never"], 136 | # "quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }], 137 | # "rest-spread-spacing": [2, "never"], 138 | # "semi": [2, "never"], 139 | # "semi-spacing": [2, { "before": false, "after": true }], 140 | # "space-before-blocks": [2, "always"], 141 | # "space-before-function-paren": [2, "always"], 142 | # "space-in-parens": [2, "never"], 143 | # "space-infix-ops": 2, 144 | # "space-unary-ops": [2, { "words": true, "nonwords": false }], 145 | # "spaced-comment": [2, "always", { "line": { "markers": ["*package", "!", ","] }, "block": { "balanced": true, "markers": ["*package", "!", ","], "exceptions": ["*"] } }], 146 | # "template-curly-spacing": [2, "never"], 147 | # "unicode-bom": [2, "never"], 148 | # "use-isnan": 2, 149 | # "valid-typeof": 2, 150 | # "wrap-iife": [2, "any"], 151 | # "yield-star-spacing": [2, "both"], 152 | # "yoda": [2, "never"], 153 | # 154 | # "standard/object-curly-even-spacing": [2, "either"], 155 | # "standard/array-bracket-even-spacing": [2, "either"], 156 | # "standard/computed-property-even-spacing": [2, "even"], 157 | # 158 | # "promise/param-names": 2 159 | # } 160 | # } 161 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | script: 5 | - npm run test 6 | after_script: 7 | - npm run cov 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016- imChenJian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fluder 2 | 3 | [Unidirectional DataFlow State Management](https://coderwin.github.io/Fluder) 4 | 5 | 更加轻量,更加便捷,更加高效。[没有框架限制/React、Vue完美使用] 6 | 7 | [![Build Status](https://travis-ci.org/coderwin/Fluder.svg?branch=master)](https://travis-ci.org/coderwin/Fluder) 8 | [![Coverage Status](https://coveralls.io/repos/github/coderwin/Fluder/badge.svg?branch=master)](https://coveralls.io/github/coderwin/Fluder?branch=master) 9 | [![npm ver](https://img.shields.io/npm/v/fluder.svg?style=flat)](https://www.npmjs.com/package/fluder) 10 | [![npm dm](https://img.shields.io/npm/dm/Fluder.svg?style=flat-square)](https://www.npmjs.com/package/fluder) 11 | [![LICENSE](https://img.shields.io/npm/l/fluder.svg)](https://www.npmjs.com/package/fluder) 12 | 13 | ## 10秒了解Fluder 14 | 15 | 16 | 17 | 28 | 47 | 65 | 66 | 84 | 85 | 86 |
18 | todoAction 19 |
 20 | export default actionCreate({
 21 |     addTodo:(item)=>({
 22 |       type: constants.ADD_TODO,
 23 |       value: item
 24 |     })
 25 | })
 26 | 
27 |
29 | todoStore 30 |
 31 | let items = [];
 32 | export default storeCreate(todoAction, {
 33 |   getAll: function(){
 34 |     return items
 35 |   }
 36 | },{
 37 |   \[constants.ADD_TODO\]: function(payload){
 38 |     push(payload.value)
 39 |     return items
 40 |   }
 41 | })
 42 | function push(item){
 43 |   items.push(item)
 44 | }
 45 | 
46 |
48 | React Component 49 |
 50 | componentDidMount(){
 51 |   todoStore.addChangeListener(()=>{
 52 |     this.setState({
 53 |       items: todoStore.getAll()
 54 |     })
 55 |   })
 56 | }
 57 | addTodo(e){
 58 |   todoAction.addTodo({
 59 |     text: e.target.value,
 60 |     done: false
 61 |   });
 62 | }
 63 | 
64 |
67 | Vue Component 68 |
 69 | methods:{
 70 |   addTodo(e){
 71 |     todoAction.addTodo({
 72 |       text: e.target.value,
 73 |       done: false
 74 |     });
 75 |   }
 76 | },
 77 | created (){
 78 |   todoStore.addChangeListener(()=>{
 79 |     this.items = todoStore.getAll()
 80 |   })
 81 | }
 82 | 
83 |
87 | 88 | 主要解决的痛点如下: 89 | 90 | * 1、Redux对没有函数式编程经验的人来说不好理解,很难用好, 91 | * 2、Redux的树形Store需要做太多的shouldComponentUpdate, 92 | * 3、Redux推崇state不可变 93 | 94 | ```javascript 95 | state = Object.assign({}, state) 96 | state.count++ 97 | return state 98 | ``` 99 | 使得Redux在Vue上使用很尴尬(vm对state的监听失效), 100 | * 4、其他线性Store的Flux实现中 Action => Store触发change更新view的成本高,Fluder用id把Action-Store关联起来提高Action到更新View的成本。 101 | 102 | ## 安装 103 | 104 | npm: https://www.npmjs.com/package/fluder 105 | 106 | 使用 npm 来安装 Fluder 107 | 108 | ```javascript 109 | npm install fluder 110 | ``` 111 | 112 | 运行Example 113 | 114 | ```javascript 115 | //Vue example 116 | npm run exampleVue 117 | 118 | //React example 119 | npm run exampleReact 120 | ``` 121 | 122 | 构建 123 | 124 | ```javascript 125 | npm run build 126 | ``` 127 | 测试 128 | 129 | ```javascript 130 | npm run test 131 | ``` 132 | 133 | 调用 134 | 135 | ```javascript 136 | import { 137 | storeCreate, 138 | actionCreate, 139 | applyMiddleware, 140 | actionStoreCreate 141 | } from 'fluder' 142 | ``` 143 | 144 | API 145 | 146 | ``` 147 | Fluder/storeCreate 148 | Fluder/actionCreate 149 | Fluder/applyMiddleware 150 | Fluder/actionStoreCreate 151 | ``` 152 | 153 | ## 介绍 154 | 155 | ![fluder-design](./design/fluder-design.png) 156 | 157 | ### Fluder Store 158 | 159 | **Store** => 数据存储和Handlers管理中心,**Store** 仅仅提供了 `读取` 数据的接口,杜绝Store数据被篡改的风险。 160 | 161 | > 在 **Views** (也可以说是Controller-Views及React组件)中,只能从 **Store** 中 `读取` 数据,在 **Store Handlers** 中(通过send **Action**),才能 `写入` (这里的写入不是store提供的API进行写入,而是只有在handlers里面才能读取到store构建的闭包中的数据)和 `读取` 数据。 162 | 163 | 当数据变化的时候,**Store** 会发送一个数据变化的事件(这个事件会把变化后的 **Store** 和引起变化的 `action payload` 传入,通过这个 `payload` 我们可以优化 **Store** 变化的回调函数的执行)。 164 | 165 | ### Fluder Actions 166 | 167 | 和Flux的 **Action** 概念一致,所有引起数据变化的操作都只能通过 **Action** 操作(比如更新数据/修改数据/删除数据)。前面 **Store** 中提到,只有在 **Store Handlers** 中才能 `写入` 数据,而能让 **Store Handlers** 执行的就是 **Action** 的发送 168 | 169 | ### Fluder Dispatcher 170 | 171 | Fluder里面隐藏了 **Dispatcher**,Action send Map到Store对应的handler后直接执行handler,存储和Map以及_invoke的操作都是 **Dispatcher** 进行,只是在Fluder里面进行了隐藏 172 | 173 | ### Fluder Middleware 174 | 175 | 提供一个统一操作 **Action** 的API,所有action都需要依次执行中间件队列里面的函数(参数为action的payload和storeId,这里的参数需要shallow Freeze),类似于express框架的中间件统一处理客户端请求 176 | 177 | ### Fluder Handlers 178 | 179 | 当 **Action** 触发的时候,**Store** 需要一个与该 **Action** 对应的回调函数来处理 payload 数据,这时可以将数据写入到 **Store** 中。**ActionType** 需要与 **Store** 的回调函数名相对应。 180 | 181 | ### Fluder Action-Store 182 | 183 | **Action** 和 **Store**在创建的时候必须匹配ID,一个Action对应一个Store,当 **Action** 触发的时候可以通过Action的ID Map到需要操作的Store,这样避免了循环查找所有Store,然后再通过Action的ActionType Map到Store对应的的Handler(Action=>Store Change时间复杂度为1),同时也避免了Flux里面的Switch case。 184 | 185 | ## API 186 | 187 | applyMiddleware-中间件 188 | 189 | storeCreate-创建Store 190 | 191 | actionCreate-创建Action 192 | 193 | actionStoreCreate-Action和Store一起创建 194 | 195 | ## use 196 | 197 | [API文档](https://coderwin.github.io/Fluder/) 198 | 199 | > Vue and React Action/Store Create with the same code 200 | 201 | ## Thanks 202 | 203 | [Flux](https://github.com/facebook/flux) 204 | 205 | [Redux](https://github.com/reactjs/redux) 206 | 207 | [Webpack](https://github.com/webpack/webpack) 208 | 209 | [GitBook](https://github.com/GitbookIO/gitbook) 210 | 211 | ## License 212 | 213 | [MIT](./LICENSE) 214 | -------------------------------------------------------------------------------- /build/lib/fluder.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define("Fluder", [], factory); 6 | else if(typeof exports === 'object') 7 | exports["Fluder"] = factory(); 8 | else 9 | root["Fluder"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | /******/ 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | /******/ 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | /******/ 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | /******/ 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | /******/ 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | /******/ 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | /******/ 39 | /******/ 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | /******/ 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | /******/ 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | /******/ 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | var storeCreate = __webpack_require__(1); 60 | var actionCreate = __webpack_require__(6); 61 | var applyMiddleware = __webpack_require__(7); 62 | var actionStoreCreate = __webpack_require__(8); 63 | 64 | module.exports = { 65 | storeCreate: storeCreate, 66 | actionCreate: actionCreate, 67 | applyMiddleware: applyMiddleware, 68 | actionStoreCreate: actionStoreCreate 69 | }; 70 | 71 | /***/ }, 72 | /* 1 */ 73 | /***/ function(module, exports, __webpack_require__) { 74 | 75 | 'use strict'; 76 | 77 | var Fluder = __webpack_require__(2); 78 | var EventEmitter = __webpack_require__(5); 79 | /** 80 | * 创建store[对外API] 81 | * @param {string} storeId 该store的唯一标识,和action里的storeId一一对应 82 | * @param {object} method 操作store的api(一般提供get set del update等) 83 | * @param {object} handlers action的处理回调对象,handler的key需要和actionType一致 84 | * @return {object} 返回store对象 85 | */ 86 | function storeCreate(storeId, method, handlers) { 87 | /** 88 | * 不存在storeId 89 | */ 90 | if (typeof storeId === 'undefined') { 91 | throw Error('id is reauired as create a store, and the id is the same of store!'); 92 | } 93 | var CHANGE_EVENT = 'change'; 94 | /** 95 | * 创建store,继承EventEmitter 96 | */ 97 | var store = Object.assign(method, EventEmitter.prototype, { 98 | /** 99 | * 统一Store的EventEmitter调用方式,避免和全局EventEmitter混淆 100 | * 这里把payload传给change的Store,可以做相应的渲染优化[局部渲染] 101 | * 这里的局部优化是指全局Stores更新,触发的Store Handler较多,可以通过payload的数据过滤 102 | */ 103 | emitChange: function emitChange(payload, result) { 104 | this.emit(CHANGE_EVENT, payload, result); 105 | }, 106 | addChangeListener: function addChangeListener(callback) { 107 | this.on(CHANGE_EVENT, callback); 108 | }, 109 | removeChangeListener: function removeChangeListener(callback) { 110 | this.removeListener(CHANGE_EVENT, callback); 111 | }, 112 | removeAllChangeListener: function removeAllChangeListener() { 113 | this.removeAllListeners(); 114 | } 115 | }); 116 | 117 | Fluder.register(storeId, { 118 | store: store, 119 | handlers: handlers 120 | }); 121 | 122 | return store; 123 | } 124 | 125 | module.exports = storeCreate; 126 | 127 | /***/ }, 128 | /* 2 */ 129 | /***/ function(module, exports, __webpack_require__) { 130 | 131 | 'use strict'; 132 | 133 | /** 134 | * Fluder 0.1.0 135 | * A unidirectional data flow tool based on flux. 136 | * 137 | * url: https://github.com/coderwin/Fluder 138 | * author: chenjiancj2011@outlook.com 139 | * weibo: imChenJian 140 | * date: 2016-08-10 141 | */ 142 | 143 | /** 144 | * workflow Queue 145 | */ 146 | var Queue = __webpack_require__(3); 147 | 148 | /** 149 | * catchError 150 | */ 151 | var Tool = __webpack_require__(4); 152 | var catchError = Tool.catchError; 153 | 154 | /** 155 | * 构造函数 156 | * @return {object} 返回Fluder实例对象 157 | */ 158 | function Fluder() { 159 | /** 160 | * store handlers 注册Map 161 | * @type {Object} 162 | */ 163 | this._registers = {}; 164 | 165 | /** 166 | * dispatch栈 167 | * @type {Array} 168 | */ 169 | this._dispatchStack = []; 170 | 171 | /** 172 | * 初始化 173 | */ 174 | this._init(); 175 | } 176 | 177 | Fluder.prototype._init = function () { 178 | /** 179 | * 中间件,集中处理action payload和storeId 180 | */ 181 | this._middleware = new Queue(true).after(function (payload) { 182 | /** 183 | * 中间件队列执行完后触发Store handler的调用 184 | */ 185 | this._invoke(payload); 186 | }.bind(this)); 187 | }; 188 | 189 | /** 190 | * action对handler的调用(内部调用) 191 | * @param {object} payload 此时的payload包含action和action对应的storeId 192 | * @return {void} 无返回值 193 | */ 194 | Fluder.prototype._invoke = function (payload) { 195 | /** 196 | * storeId: 用于map到register里面注册的handler 197 | * @type {string} 198 | */ 199 | var storeId = payload.storeId; 200 | 201 | /** 202 | * store和它对应的handler 203 | * @type {object} 204 | */ 205 | var store = this._registers[storeId]['store']; 206 | var handlers = this._registers[storeId]['handlers']; 207 | 208 | /** 209 | * action payload 210 | * @type {object} 211 | */ 212 | payload = payload.payload; 213 | 214 | /** 215 | * 在当前storeId的store Map到对应的handler 216 | * @type {function} 217 | */ 218 | var handler = handlers[payload.type]; 219 | 220 | if (typeof handler === 'function') { 221 | /** 222 | * TODO 223 | * result应该为store数据的copy,暂时没做深度copy,后续把Store改写成Immutable数据结构 224 | * view-controller里面对result的修改不会影响到store里的数据 225 | */ 226 | var result; 227 | // var _result = handler.call(store, payload) 228 | handler.call(store, payload); 229 | /** 230 | * 可以没有返回值,只是set Store里面的值 231 | * 这里把payload传给change的Store,可以做相应的渲染优化[局部渲染] 232 | */ 233 | // if (result !== undefined) { 234 | store.emitChange(payload, result); 235 | // } 236 | } 237 | }; 238 | 239 | /** 240 | * 更新Action栈以及记录当前ActionID 241 | */ 242 | Fluder.prototype._startDispatch = function (storeId) { 243 | this._dispatchStack || (this._dispatchStack = []); 244 | this._dispatchStack.push(storeId); 245 | this._currentDispatch = storeId; 246 | }; 247 | 248 | /** 249 | * Action执行完更新Action栈以及删除当前ActionID 250 | */ 251 | Fluder.prototype._endDispatch = function () { 252 | this._dispatchStack.pop(); 253 | this._currentDispatch = null; 254 | }; 255 | 256 | /** 257 | * Store和handler注册 258 | * @param {string} storeId store/action唯一标示 259 | * @param {object} storeHandler store和handler 260 | * @return {void} 无返回值 261 | */ 262 | Fluder.prototype.register = function (storeId, storeHandler) { 263 | this._registers[storeId] = storeHandler; 264 | }; 265 | 266 | /** 267 | * 中间件入队 268 | * @param {function} middleware 中间件处理函数 269 | * @return {void} 无返回值 270 | */ 271 | Fluder.prototype.enqueue = function (middleware) { 272 | this._middleware.enqueue(middleware); 273 | }; 274 | 275 | /** 276 | * 触发action(内部调用) 277 | * @param {string} storeId store/action唯一标示 278 | * @param {object} action action数据 279 | * @return {void} 无返回值 280 | */ 281 | Fluder.prototype.dispatch = function (storeId, payload) { 282 | /** 283 | * 在当前Action触发的Store handler回调函数中再次发起了当前Action,这样会造成A-A循环调用,出现栈溢出 284 | */ 285 | if (this._currentDispatch === storeId) { 286 | throw Error('action ' + (payload.value && payload.value.actionType) + ' __invoke__ myself!'); 287 | } 288 | 289 | /** 290 | * 在当前Action触发的Store handler回调函数中再次触发了当前Action栈中的Action,出现A-B-C-A式循环调用,也会出现栈溢出 291 | */ 292 | if (this._dispatchStack.indexOf(storeId) !== -1) { 293 | throw Error(this._dispatchStack.join(' -> ') + storeId + ' : action __invoke__ to a circle!'); 294 | } 295 | 296 | /** 297 | * 更新Action栈以及记录当前ActionID 298 | */ 299 | this._startDispatch(storeId); 300 | 301 | /** 302 | * Action的触发必须有ActionType,原因是ActionType和Store handlers Map的key一一对应 303 | */ 304 | if (!payload.type) { 305 | throw new Error('action type does not exist in \n' + JSON.stringify(payload, null, 2)); 306 | } 307 | 308 | try { 309 | /** 310 | * 发出action的时候 统一走一遍中间件 311 | * 312 | * { 313 | * storeId, 314 | * payload 315 | * } 316 | * 317 | * shallow Immutable 318 | */ 319 | this._middleware.execute(Object.freeze({ 320 | storeId: storeId, 321 | payload: payload, 322 | store: this._registers[storeId]['store'] 323 | })); 324 | } catch (e) { 325 | /** 326 | * 执行handler的时候出错end掉当前dispatch 327 | */ 328 | this._endDispatch(); 329 | 330 | /** 331 | * 抛出错误信息 332 | */ 333 | catchError(e); 334 | } 335 | 336 | /** 337 | * Action执行完更新Action栈以及删除当前ActionID 338 | */ 339 | this._endDispatch(); 340 | }; 341 | 342 | module.exports = new Fluder(); 343 | 344 | /***/ }, 345 | /* 3 */ 346 | /***/ function(module, exports) { 347 | 348 | 'use strict'; 349 | 350 | /** 351 | * 队列 352 | * @type {Array} 353 | */ 354 | var queue = []; 355 | 356 | /** 357 | * 队列备份 358 | * @type {Array} 359 | */ 360 | var _queue = []; 361 | 362 | /** 363 | * 队列类 364 | */ 365 | function Queue(loop) { 366 | this.loop = typeof loop === 'undefined' ? true : loop; 367 | } 368 | 369 | /** 370 | * 入队 371 | * @param {Function} 排队函数 372 | */ 373 | Queue.prototype.enqueue = function (task) { 374 | /** 375 | * 入队 376 | */ 377 | queue.push(task); 378 | // Backup 379 | this.loop && _queue.push(task); 380 | }; 381 | 382 | /** 383 | * 执行队列函数 384 | * @param {Object} 可为空,在排队函数中流通的data 385 | * @param {Array} 可为空,替换队列中的排队函数 386 | */ 387 | Queue.prototype.execute = function (data, tasks) { 388 | /** 389 | * 如果tasks存在则忽略排队函数 390 | */ 391 | tasks = tasks || queue; 392 | var task; 393 | 394 | /** 395 | * 队列不为空 396 | */ 397 | if (tasks.length) { 398 | /** 399 | * 出队 400 | */ 401 | task = tasks.shift(); 402 | task(data, this.execute.bind(this, data, tasks)); 403 | } else { 404 | /** 405 | * 队列为空,执行完成 406 | */ 407 | task = null; 408 | this.tasksAchieved(data); 409 | 410 | // Get backup 411 | this.loop && (queue = _queue.concat()); 412 | } 413 | }; 414 | 415 | /** 416 | * 队列中排队函数执行完成后的回调函数 417 | * @param {Function} fn 418 | * @return {object} 返回队列实例,mock Promise 419 | */ 420 | Queue.prototype.after = function (fn) { 421 | this.tasksAchieved = fn; 422 | return this; 423 | }; 424 | 425 | module.exports = Queue; 426 | 427 | /***/ }, 428 | /* 4 */ 429 | /***/ function(module, exports) { 430 | 431 | 'use strict'; 432 | 433 | function unique() { 434 | /** 435 | * Fluder Store唯一ID 436 | */ 437 | return '@@Fluder/StoreId/' + Math.random().toString(36).substring(7).split('').join('.'); 438 | } 439 | 440 | /** 441 | * 可以有中间件实现 442 | * @param {error} e 错误对象 443 | */ 444 | function catchError(e) { 445 | var start = '\n\n@@Fluder/Start\n'; 446 | var end = '\n@@Fluder/End\n\n'; 447 | 448 | throw Error(start + 'Error: ' + (e.line ? e.line + '行' : '') + (e.column ? e.column + '列' : '') + e.message + end); 449 | } 450 | 451 | module.exports = { 452 | unique: unique, 453 | catchError: catchError 454 | }; 455 | 456 | /***/ }, 457 | /* 5 */ 458 | /***/ function(module, exports) { 459 | 460 | 'use strict'; 461 | 462 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 463 | 464 | // Copyright Joyent, Inc. and other Node contributors. 465 | // 466 | // Permission is hereby granted, free of charge, to any person obtaining a 467 | // copy of this software and associated documentation files (the 468 | // "Software"), to deal in the Software without restriction, including 469 | // without limitation the rights to use, copy, modify, merge, publish, 470 | // distribute, sublicense, and/or sell copies of the Software, and to permit 471 | // persons to whom the Software is furnished to do so, subject to the 472 | // following conditions: 473 | // 474 | // The above copyright notice and this permission notice shall be included 475 | // in all copies or substantial portions of the Software. 476 | // 477 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 478 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 479 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 480 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 481 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 482 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 483 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 484 | 485 | function EventEmitter() { 486 | this._events = this._events || {}; 487 | this._maxListeners = this._maxListeners || undefined; 488 | } 489 | module.exports = EventEmitter; 490 | 491 | // Backwards-compat with node 0.10.x 492 | EventEmitter.EventEmitter = EventEmitter; 493 | 494 | EventEmitter.prototype._events = undefined; 495 | EventEmitter.prototype._maxListeners = undefined; 496 | 497 | // By default EventEmitters will print a warning if more than 10 listeners are 498 | // added to it. This is a useful default which helps finding memory leaks. 499 | EventEmitter.defaultMaxListeners = 10; 500 | 501 | // Obviously not all Emitters should be limited to 10. This function allows 502 | // that to be increased. Set to zero for unlimited. 503 | EventEmitter.prototype.setMaxListeners = function (n) { 504 | if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError('n must be a positive number'); 505 | this._maxListeners = n; 506 | return this; 507 | }; 508 | 509 | EventEmitter.prototype.emit = function (type) { 510 | var er, handler, len, args, i, listeners; 511 | 512 | if (!this._events) this._events = {}; 513 | 514 | // If there is no 'error' event listener then throw. 515 | if (type === 'error') { 516 | if (!this._events.error || isObject(this._events.error) && !this._events.error.length) { 517 | er = arguments[1]; 518 | if (er instanceof Error) { 519 | throw er; // Unhandled 'error' event 520 | } else { 521 | // At least give some kind of context to the user 522 | var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); 523 | err.context = er; 524 | throw err; 525 | } 526 | } 527 | } 528 | 529 | handler = this._events[type]; 530 | 531 | if (isUndefined(handler)) return false; 532 | 533 | if (isFunction(handler)) { 534 | switch (arguments.length) { 535 | // fast cases 536 | case 1: 537 | handler.call(this); 538 | break; 539 | case 2: 540 | handler.call(this, arguments[1]); 541 | break; 542 | case 3: 543 | handler.call(this, arguments[1], arguments[2]); 544 | break; 545 | // slower 546 | default: 547 | args = Array.prototype.slice.call(arguments, 1); 548 | handler.apply(this, args); 549 | } 550 | } else if (isObject(handler)) { 551 | args = Array.prototype.slice.call(arguments, 1); 552 | listeners = handler.slice(); 553 | len = listeners.length; 554 | for (i = 0; i < len; i++) { 555 | listeners[i].apply(this, args); 556 | } 557 | } 558 | 559 | return true; 560 | }; 561 | 562 | EventEmitter.prototype.addListener = function (type, listener) { 563 | var m; 564 | 565 | if (!isFunction(listener)) throw TypeError('listener must be a function'); 566 | 567 | if (!this._events) this._events = {}; 568 | 569 | // To avoid recursion in the case that type === "newListener"! Before 570 | // adding it to the listeners, first emit "newListener". 571 | if (this._events.newListener) this.emit('newListener', type, isFunction(listener.listener) ? listener.listener : listener); 572 | 573 | if (!this._events[type]) 574 | // Optimize the case of one listener. Don't need the extra array object. 575 | this._events[type] = listener;else if (isObject(this._events[type])) 576 | // If we've already got an array, just append. 577 | this._events[type].push(listener);else 578 | // Adding the second element, need to change to array. 579 | this._events[type] = [this._events[type], listener]; 580 | 581 | // Check for listener leak 582 | if (isObject(this._events[type]) && !this._events[type].warned) { 583 | if (!isUndefined(this._maxListeners)) { 584 | m = this._maxListeners; 585 | } else { 586 | m = EventEmitter.defaultMaxListeners; 587 | } 588 | 589 | if (m && m > 0 && this._events[type].length > m) { 590 | this._events[type].warned = true; 591 | console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); 592 | if (typeof console.trace === 'function') { 593 | // not supported in IE 10 594 | console.trace(); 595 | } 596 | } 597 | } 598 | 599 | return this; 600 | }; 601 | 602 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 603 | 604 | EventEmitter.prototype.once = function (type, listener) { 605 | if (!isFunction(listener)) throw TypeError('listener must be a function'); 606 | 607 | var fired = false; 608 | 609 | function g() { 610 | this.removeListener(type, g); 611 | 612 | if (!fired) { 613 | fired = true; 614 | listener.apply(this, arguments); 615 | } 616 | } 617 | 618 | g.listener = listener; 619 | this.on(type, g); 620 | 621 | return this; 622 | }; 623 | 624 | // emits a 'removeListener' event iff the listener was removed 625 | EventEmitter.prototype.removeListener = function (type, listener) { 626 | var list, position, length, i; 627 | 628 | if (!isFunction(listener)) throw TypeError('listener must be a function'); 629 | 630 | if (!this._events || !this._events[type]) return this; 631 | 632 | list = this._events[type]; 633 | length = list.length; 634 | position = -1; 635 | 636 | if (list === listener || isFunction(list.listener) && list.listener === listener) { 637 | delete this._events[type]; 638 | if (this._events.removeListener) this.emit('removeListener', type, listener); 639 | } else if (isObject(list)) { 640 | for (i = length; i-- > 0;) { 641 | if (list[i] === listener || list[i].listener && list[i].listener === listener) { 642 | position = i; 643 | break; 644 | } 645 | } 646 | 647 | if (position < 0) return this; 648 | 649 | if (list.length === 1) { 650 | list.length = 0; 651 | delete this._events[type]; 652 | } else { 653 | list.splice(position, 1); 654 | } 655 | 656 | if (this._events.removeListener) this.emit('removeListener', type, listener); 657 | } 658 | 659 | return this; 660 | }; 661 | 662 | EventEmitter.prototype.removeAllListeners = function (type) { 663 | var key, listeners; 664 | 665 | if (!this._events) return this; 666 | 667 | // not listening for removeListener, no need to emit 668 | if (!this._events.removeListener) { 669 | if (arguments.length === 0) this._events = {};else if (this._events[type]) delete this._events[type]; 670 | return this; 671 | } 672 | 673 | // emit removeListener for all listeners on all events 674 | if (arguments.length === 0) { 675 | for (key in this._events) { 676 | if (key === 'removeListener') continue; 677 | this.removeAllListeners(key); 678 | } 679 | this.removeAllListeners('removeListener'); 680 | this._events = {}; 681 | return this; 682 | } 683 | 684 | listeners = this._events[type]; 685 | 686 | if (isFunction(listeners)) { 687 | this.removeListener(type, listeners); 688 | } else if (listeners) { 689 | // LIFO order 690 | while (listeners.length) { 691 | this.removeListener(type, listeners[listeners.length - 1]); 692 | } 693 | } 694 | delete this._events[type]; 695 | 696 | return this; 697 | }; 698 | 699 | EventEmitter.prototype.listeners = function (type) { 700 | var ret; 701 | if (!this._events || !this._events[type]) ret = [];else if (isFunction(this._events[type])) ret = [this._events[type]];else ret = this._events[type].slice(); 702 | return ret; 703 | }; 704 | 705 | EventEmitter.prototype.listenerCount = function (type) { 706 | if (this._events) { 707 | var evlistener = this._events[type]; 708 | 709 | if (isFunction(evlistener)) return 1;else if (evlistener) return evlistener.length; 710 | } 711 | return 0; 712 | }; 713 | 714 | EventEmitter.listenerCount = function (emitter, type) { 715 | return emitter.listenerCount(type); 716 | }; 717 | 718 | function isFunction(arg) { 719 | return typeof arg === 'function'; 720 | } 721 | 722 | function isNumber(arg) { 723 | return typeof arg === 'number'; 724 | } 725 | 726 | function isObject(arg) { 727 | return (typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === 'object' && arg !== null; 728 | } 729 | 730 | function isUndefined(arg) { 731 | return arg === void 0; 732 | } 733 | 734 | /***/ }, 735 | /* 6 */ 736 | /***/ function(module, exports, __webpack_require__) { 737 | 738 | 'use strict'; 739 | 740 | var Fluder = __webpack_require__(2); 741 | /** 742 | * 创建action[对外API] 743 | * @param {string} storeId 该action作用于那个store,和store的storeId一一对应 744 | * @param {object} actionCreators 需要创建的action对象 745 | * @return {object} 返回一个actions对象,具有调用action触发store change的能力 746 | */ 747 | function actionCreate(storeId, actionCreators) { 748 | /** 749 | * 不存在storeId 750 | */ 751 | if (typeof storeId === 'undefined') { 752 | throw Error('id is reauired as creating a action!'); 753 | } 754 | /** 755 | * action handler为空,相当于没有action 756 | */ 757 | if (!actionCreators || Object.keys(actionCreators).length === 0) { 758 | console.warn('action handler\'s length is 0, need you have a action handler?'); 759 | } 760 | 761 | var creator; 762 | var actions = {}; 763 | /** 764 | * 遍历创建Action 765 | */ 766 | for (var name in actionCreators) { 767 | creator = actionCreators[name]; 768 | /** 769 | * 创建闭包,让creator不被回收 770 | */ 771 | actions[name] = function (storeId, creator) { 772 | return function () { 773 | /** 774 | * action里面发出改变store消息 775 | */ 776 | return this.dispatch(storeId, creator.apply(null, arguments)); 777 | }.bind(this); 778 | }.call(Fluder, storeId, creator); 779 | } 780 | return actions; 781 | } 782 | 783 | module.exports = actionCreate; 784 | 785 | /***/ }, 786 | /* 7 */ 787 | /***/ function(module, exports, __webpack_require__) { 788 | 789 | 'use strict'; 790 | 791 | var Fluder = __webpack_require__(2); 792 | 793 | /** 794 | * 中间件[对外API] 795 | * @param {function} middleware action统一流入中间件 796 | * 这里和redux类似,和express等框架对请求的处理一样 797 | */ 798 | function applyMiddleware(middleware) { 799 | if (typeof middleware === 'function') { 800 | /** 801 | * 中间件是一个队列,一个action发出时 802 | * 需要排队等到所有的中间件 完成才会触发对应的handler 803 | * @param {function} middleware 804 | */ 805 | Fluder.enqueue(middleware); 806 | } 807 | if ({}.toString.call(middleware) === '[object Array]') { 808 | for (var i = 0; i < middleware.length; i++) { 809 | if (typeof middleware[i] === 'function') { 810 | applyMiddleware(middleware[i]); 811 | } 812 | } 813 | } 814 | /** 815 | * 支持链式中间件 816 | */ 817 | return { 818 | applyMiddleware: applyMiddleware 819 | }; 820 | } 821 | module.exports = applyMiddleware; 822 | 823 | /***/ }, 824 | /* 8 */ 825 | /***/ function(module, exports, __webpack_require__) { 826 | 827 | 'use strict'; 828 | 829 | var Tool = __webpack_require__(4); 830 | var unique = Tool.unique; 831 | var storeCreate = __webpack_require__(1); 832 | var actionCreate = __webpack_require__(6); 833 | 834 | function actionStoreCreate(actionCreators, method, handlers, storeId) { 835 | storeId = storeId || unique(); 836 | return { 837 | actionor: actionCreate(storeId, actionCreators), 838 | storeor: storeCreate(storeId, method, handlers) 839 | }; 840 | } 841 | 842 | module.exports = actionStoreCreate; 843 | 844 | /***/ } 845 | /******/ ]) 846 | }); 847 | ; 848 | //# sourceMappingURL=fluder.js.map -------------------------------------------------------------------------------- /build/lib/fluder.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///webpack/bootstrap 0f62d20a3e3d354b0304","webpack:///./src/index.js","webpack:///./src/storeCreator.js","webpack:///./src/fluder.js","webpack:///./src/queue.js","webpack:///./src/tools.js","webpack:///./~/events/events.js","webpack:///./src/actionCreator.js","webpack:///./src/applyMiddleware.js","webpack:///./src/actionStoreCreator.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;ACVA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;;;;;;;;ACtCA,KAAI,cAAc,oBAAQ,CAAR,CAAlB;AACA,KAAI,eAAe,oBAAQ,CAAR,CAAnB;AACA,KAAI,kBAAkB,oBAAQ,CAAR,CAAtB;AACA,KAAI,oBAAoB,oBAAQ,CAAR,CAAxB;;AAEA,QAAO,OAAP,GAAiB;AACf,gBAAa,WADE;AAEf,iBAAc,YAFC;AAGf,oBAAiB,eAHF;AAIf,sBAAmB;AAJJ,EAAjB,C;;;;;;;;ACLA,KAAI,SAAS,oBAAQ,CAAR,CAAb;AACA,KAAI,eAAe,oBAAQ,CAAR,CAAnB;AACE;;;;;;;AAOF,UAAS,WAAT,CAAsB,OAAtB,EAA+B,MAA/B,EAAuC,QAAvC,EAAiD;AAC/C;;;AAGA,OAAI,OAAO,OAAP,KAAmB,WAAvB,EAAoC;AAClC,WAAM,MAAM,oEAAN,CAAN;AACD;AACD,OAAI,eAAe,QAAnB;AACE;;;AAGF,OAAI,QAAQ,OAAO,MAAP,CAAc,MAAd,EAAsB,aAAa,SAAnC,EAA8C;AACxD;;;;;AAKA,iBAAY,oBAAU,OAAV,EAAmB,MAAnB,EAA2B;AACrC,YAAK,IAAL,CAAU,YAAV,EAAwB,OAAxB,EAAiC,MAAjC;AACD,MARuD;AASxD,wBAAmB,2BAAU,QAAV,EAAoB;AACrC,YAAK,EAAL,CAAQ,YAAR,EAAsB,QAAtB;AACD,MAXuD;AAYxD,2BAAsB,8BAAU,QAAV,EAAoB;AACxC,YAAK,cAAL,CAAoB,YAApB,EAAkC,QAAlC;AACD,MAduD;AAexD,8BAAyB,mCAAY;AACnC,YAAK,kBAAL;AACD;AAjBuD,IAA9C,CAAZ;;AAoBA,UAAO,QAAP,CAAgB,OAAhB,EAAyB;AACvB,YAAO,KADgB;AAEvB,eAAU;AAFa,IAAzB;;AAKA,UAAO,KAAP;AACD;;AAED,QAAO,OAAP,GAAiB,WAAjB,C;;;;;;;;AChDA;;;;;;;;;;AAUA;;;AAGA,KAAI,QAAQ,oBAAQ,CAAR,CAAZ;;AAEA;;;AAGA,KAAI,OAAO,oBAAQ,CAAR,CAAX;AACA,KAAI,aAAa,KAAK,UAAtB;;AAEA;;;;AAIA,UAAS,MAAT,GAAmB;AACjB;;;;AAIA,QAAK,UAAL,GAAkB,EAAlB;;AAEA;;;;AAIA,QAAK,cAAL,GAAsB,EAAtB;;AAEA;;;AAGA,QAAK,KAAL;AACD;;AAED,QAAO,SAAP,CAAiB,KAAjB,GAAyB,YAAY;AACnC;;;AAGA,QAAK,WAAL,GAAmB,IAAI,KAAJ,CAAU,IAAV,EAAgB,KAAhB,CAAsB,UAAU,OAAV,EAAmB;AAC1D;;;AAGA,UAAK,OAAL,CAAa,OAAb;AACD,IALwC,CAKvC,IALuC,CAKlC,IALkC,CAAtB,CAAnB;AAMD,EAVD;;AAYA;;;;;AAKA,QAAO,SAAP,CAAiB,OAAjB,GAA2B,UAAU,OAAV,EAAmB;AAC5C;;;;AAIA,OAAI,UAAU,QAAQ,OAAtB;;AAEA;;;;AAIA,OAAI,QAAQ,KAAK,UAAL,CAAgB,OAAhB,EAAyB,OAAzB,CAAZ;AACA,OAAI,WAAW,KAAK,UAAL,CAAgB,OAAhB,EAAyB,UAAzB,CAAf;;AAEA;;;;AAIA,aAAU,QAAQ,OAAlB;;AAEA;;;;AAIA,OAAI,UAAU,SAAS,QAAQ,IAAjB,CAAd;;AAEA,OAAI,OAAO,OAAP,KAAmB,UAAvB,EAAmC;AACjC;;;;;AAKA,SAAI,MAAJ;AACE;AACF,aAAQ,IAAR,CAAa,KAAb,EAAoB,OAApB;AACE;;;;AAIA;AACF,WAAM,UAAN,CAAiB,OAAjB,EAA0B,MAA1B;AACE;AACH;AACF,EA3CD;;AA6CA;;;AAGA,QAAO,SAAP,CAAiB,cAAjB,GAAkC,UAAU,OAAV,EAAmB;AACnD,QAAK,cAAL,KAAwB,KAAK,cAAL,GAAsB,EAA9C;AACA,QAAK,cAAL,CAAoB,IAApB,CAAyB,OAAzB;AACA,QAAK,gBAAL,GAAwB,OAAxB;AACD,EAJD;;AAMA;;;AAGA,QAAO,SAAP,CAAiB,YAAjB,GAAgC,YAAY;AAC1C,QAAK,cAAL,CAAoB,GAApB;AACA,QAAK,gBAAL,GAAwB,IAAxB;AACD,EAHD;;AAKA;;;;;;AAMA,QAAO,SAAP,CAAiB,QAAjB,GAA4B,UAAU,OAAV,EAAmB,YAAnB,EAAiC;AAC3D,QAAK,UAAL,CAAgB,OAAhB,IAA2B,YAA3B;AACD,EAFD;;AAIA;;;;;AAKA,QAAO,SAAP,CAAiB,OAAjB,GAA2B,UAAU,UAAV,EAAsB;AAC/C,QAAK,WAAL,CAAiB,OAAjB,CAAyB,UAAzB;AACD,EAFD;;AAIA;;;;;;AAMA,QAAO,SAAP,CAAiB,QAAjB,GAA4B,UAAU,OAAV,EAAmB,OAAnB,EAA4B;AACtD;;;AAGA,OAAI,KAAK,gBAAL,KAA0B,OAA9B,EAAuC;AACrC,WAAM,MAAM,aAAa,QAAQ,KAAR,IAAiB,QAAQ,KAAR,CAAc,UAA5C,IAA0D,qBAAhE,CAAN;AACD;;AAED;;;AAGA,OAAI,KAAK,cAAL,CAAoB,OAApB,CAA4B,OAA5B,MAAyC,CAAC,CAA9C,EAAiD;AAC/C,WAAM,MAAM,KAAK,cAAL,CAAoB,IAApB,CAAyB,MAAzB,IAAmC,OAAnC,GAA6C,mCAAnD,CAAN;AACD;;AAED;;;AAGA,QAAK,cAAL,CAAoB,OAApB;;AAEA;;;AAGA,OAAI,CAAC,QAAQ,IAAb,EAAmB;AACjB,WAAM,IAAI,KAAJ,CAAU,qCAAqC,KAAK,SAAL,CAAe,OAAf,EAAwB,IAAxB,EAA8B,CAA9B,CAA/C,CAAN;AACD;;AAED,OAAI;AACF;;;;;;;;;;AAUA,UAAK,WAAL,CAAiB,OAAjB,CAAyB,OAAO,MAAP,CAAc;AACrC,gBAAS,OAD4B;AAErC,gBAAS,OAF4B;AAGrC,cAAO,KAAK,UAAL,CAAgB,OAAhB,EAAyB,OAAzB;AAH8B,MAAd,CAAzB;AAKD,IAhBD,CAgBE,OAAO,CAAP,EAAU;AACV;;;AAGA,UAAK,YAAL;;AAEA;;;AAGA,gBAAW,CAAX;AACD;;AAED;;;AAGA,QAAK,YAAL;AACD,EA3DD;;AA6DA,QAAO,OAAP,GAAiB,IAAI,MAAJ,EAAjB,C;;;;;;;;ACjNA;;;;AAIA,KAAI,QAAQ,EAAZ;;AAEA;;;;AAIA,KAAI,SAAS,EAAb;;AAEA;;;AAGA,UAAS,KAAT,CAAgB,IAAhB,EAAsB;AACpB,QAAK,IAAL,GAAa,OAAO,IAAP,KAAgB,WAAjB,GAAgC,IAAhC,GAAuC,IAAnD;AACD;;AAED;;;;AAIA,OAAM,SAAN,CAAgB,OAAhB,GAA0B,UAAU,IAAV,EAAgB;AACxC;;;AAGA,SAAM,IAAN,CAAW,IAAX;AACE;AACF,QAAK,IAAL,IAAa,OAAO,IAAP,CAAY,IAAZ,CAAb;AACD,EAPD;;AASA;;;;;AAKA,OAAM,SAAN,CAAgB,OAAhB,GAA0B,UAAU,IAAV,EAAgB,KAAhB,EAAuB;AAC/C;;;AAGA,WAAQ,SAAS,KAAjB;AACA,OAAI,IAAJ;;AAEA;;;AAGA,OAAI,MAAM,MAAV,EAAkB;AAChB;;;AAGA,YAAO,MAAM,KAAN,EAAP;AACA,UAAK,IAAL,EAAW,KAAK,OAAL,CAAa,IAAb,CAAkB,IAAlB,EAAwB,IAAxB,EAA8B,KAA9B,CAAX;AACD,IAND,MAMO;AACL;;;AAGA,YAAO,IAAP;AACA,UAAK,aAAL,CAAmB,IAAnB;;AAEA;AACA,UAAK,IAAL,KAAc,QAAQ,OAAO,MAAP,EAAtB;AACD;AACF,EA1BD;;AA4BA;;;;;AAKA,OAAM,SAAN,CAAgB,KAAhB,GAAwB,UAAU,EAAV,EAAc;AACpC,QAAK,aAAL,GAAqB,EAArB;AACA,UAAO,IAAP;AACD,EAHD;;AAKA,QAAO,OAAP,GAAiB,KAAjB,C;;;;;;;;AC3EA,UAAS,MAAT,GAAmB;AACjB;;;AAGA,UAAO,sBACL,KAAK,MAAL,GACC,QADD,CACU,EADV,EAEC,SAFD,CAEW,CAFX,EAGC,KAHD,CAGO,EAHP,EAIC,IAJD,CAIM,GAJN,CADF;AAMD;;AAED;;;;AAIA,UAAS,UAAT,CAAqB,CAArB,EAAwB;AACtB,OAAI,QAAQ,sBAAZ;AACA,OAAI,MAAM,oBAAV;;AAEA,SAAM,MAAM,QACV,SADU,IAET,EAAE,IAAF,GAAU,EAAE,IAAF,GAAS,GAAnB,GAA0B,EAFjB,KAGT,EAAE,MAAF,GAAY,EAAE,MAAF,GAAW,GAAvB,GAA8B,EAHrB,IAIV,EAAE,OAJQ,GAKV,GALI,CAAN;AAMD;;AAED,QAAO,OAAP,GAAiB;AACf,WAAQ,MADO;AAEf,eAAY;AAFG,EAAjB,C;;;;;;;;;;AC5BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,UAAS,YAAT,GAAwB;AACtB,QAAK,OAAL,GAAe,KAAK,OAAL,IAAgB,EAA/B;AACA,QAAK,aAAL,GAAqB,KAAK,aAAL,IAAsB,SAA3C;AACD;AACD,QAAO,OAAP,GAAiB,YAAjB;;AAEA;AACA,cAAa,YAAb,GAA4B,YAA5B;;AAEA,cAAa,SAAb,CAAuB,OAAvB,GAAiC,SAAjC;AACA,cAAa,SAAb,CAAuB,aAAvB,GAAuC,SAAvC;;AAEA;AACA;AACA,cAAa,mBAAb,GAAmC,EAAnC;;AAEA;AACA;AACA,cAAa,SAAb,CAAuB,eAAvB,GAAyC,UAAS,CAAT,EAAY;AACnD,OAAI,CAAC,SAAS,CAAT,CAAD,IAAgB,IAAI,CAApB,IAAyB,MAAM,CAAN,CAA7B,EACE,MAAM,UAAU,6BAAV,CAAN;AACF,QAAK,aAAL,GAAqB,CAArB;AACA,UAAO,IAAP;AACD,EALD;;AAOA,cAAa,SAAb,CAAuB,IAAvB,GAA8B,UAAS,IAAT,EAAe;AAC3C,OAAI,EAAJ,EAAQ,OAAR,EAAiB,GAAjB,EAAsB,IAAtB,EAA4B,CAA5B,EAA+B,SAA/B;;AAEA,OAAI,CAAC,KAAK,OAAV,EACE,KAAK,OAAL,GAAe,EAAf;;AAEF;AACA,OAAI,SAAS,OAAb,EAAsB;AACpB,SAAI,CAAC,KAAK,OAAL,CAAa,KAAd,IACC,SAAS,KAAK,OAAL,CAAa,KAAtB,KAAgC,CAAC,KAAK,OAAL,CAAa,KAAb,CAAmB,MADzD,EACkE;AAChE,YAAK,UAAU,CAAV,CAAL;AACA,WAAI,cAAc,KAAlB,EAAyB;AACvB,eAAM,EAAN,CADuB,CACb;AACX,QAFD,MAEO;AACL;AACA,aAAI,MAAM,IAAI,KAAJ,CAAU,2CAA2C,EAA3C,GAAgD,GAA1D,CAAV;AACA,aAAI,OAAJ,GAAc,EAAd;AACA,eAAM,GAAN;AACD;AACF;AACF;;AAED,aAAU,KAAK,OAAL,CAAa,IAAb,CAAV;;AAEA,OAAI,YAAY,OAAZ,CAAJ,EACE,OAAO,KAAP;;AAEF,OAAI,WAAW,OAAX,CAAJ,EAAyB;AACvB,aAAQ,UAAU,MAAlB;AACE;AACA,YAAK,CAAL;AACE,iBAAQ,IAAR,CAAa,IAAb;AACA;AACF,YAAK,CAAL;AACE,iBAAQ,IAAR,CAAa,IAAb,EAAmB,UAAU,CAAV,CAAnB;AACA;AACF,YAAK,CAAL;AACE,iBAAQ,IAAR,CAAa,IAAb,EAAmB,UAAU,CAAV,CAAnB,EAAiC,UAAU,CAAV,CAAjC;AACA;AACF;AACA;AACE,gBAAO,MAAM,SAAN,CAAgB,KAAhB,CAAsB,IAAtB,CAA2B,SAA3B,EAAsC,CAAtC,CAAP;AACA,iBAAQ,KAAR,CAAc,IAAd,EAAoB,IAApB;AAdJ;AAgBD,IAjBD,MAiBO,IAAI,SAAS,OAAT,CAAJ,EAAuB;AAC5B,YAAO,MAAM,SAAN,CAAgB,KAAhB,CAAsB,IAAtB,CAA2B,SAA3B,EAAsC,CAAtC,CAAP;AACA,iBAAY,QAAQ,KAAR,EAAZ;AACA,WAAM,UAAU,MAAhB;AACA,UAAK,IAAI,CAAT,EAAY,IAAI,GAAhB,EAAqB,GAArB;AACE,iBAAU,CAAV,EAAa,KAAb,CAAmB,IAAnB,EAAyB,IAAzB;AADF;AAED;;AAED,UAAO,IAAP;AACD,EArDD;;AAuDA,cAAa,SAAb,CAAuB,WAAvB,GAAqC,UAAS,IAAT,EAAe,QAAf,EAAyB;AAC5D,OAAI,CAAJ;;AAEA,OAAI,CAAC,WAAW,QAAX,CAAL,EACE,MAAM,UAAU,6BAAV,CAAN;;AAEF,OAAI,CAAC,KAAK,OAAV,EACE,KAAK,OAAL,GAAe,EAAf;;AAEF;AACA;AACA,OAAI,KAAK,OAAL,CAAa,WAAjB,EACE,KAAK,IAAL,CAAU,aAAV,EAAyB,IAAzB,EACU,WAAW,SAAS,QAApB,IACA,SAAS,QADT,GACoB,QAF9B;;AAIF,OAAI,CAAC,KAAK,OAAL,CAAa,IAAb,CAAL;AACE;AACA,UAAK,OAAL,CAAa,IAAb,IAAqB,QAArB,CAFF,KAGK,IAAI,SAAS,KAAK,OAAL,CAAa,IAAb,CAAT,CAAJ;AACH;AACA,UAAK,OAAL,CAAa,IAAb,EAAmB,IAAnB,CAAwB,QAAxB,EAFG;AAIH;AACA,UAAK,OAAL,CAAa,IAAb,IAAqB,CAAC,KAAK,OAAL,CAAa,IAAb,CAAD,EAAqB,QAArB,CAArB;;AAEF;AACA,OAAI,SAAS,KAAK,OAAL,CAAa,IAAb,CAAT,KAAgC,CAAC,KAAK,OAAL,CAAa,IAAb,EAAmB,MAAxD,EAAgE;AAC9D,SAAI,CAAC,YAAY,KAAK,aAAjB,CAAL,EAAsC;AACpC,WAAI,KAAK,aAAT;AACD,MAFD,MAEO;AACL,WAAI,aAAa,mBAAjB;AACD;;AAED,SAAI,KAAK,IAAI,CAAT,IAAc,KAAK,OAAL,CAAa,IAAb,EAAmB,MAAnB,GAA4B,CAA9C,EAAiD;AAC/C,YAAK,OAAL,CAAa,IAAb,EAAmB,MAAnB,GAA4B,IAA5B;AACA,eAAQ,KAAR,CAAc,kDACA,qCADA,GAEA,kDAFd,EAGc,KAAK,OAAL,CAAa,IAAb,EAAmB,MAHjC;AAIA,WAAI,OAAO,QAAQ,KAAf,KAAyB,UAA7B,EAAyC;AACvC;AACA,iBAAQ,KAAR;AACD;AACF;AACF;;AAED,UAAO,IAAP;AACD,EAhDD;;AAkDA,cAAa,SAAb,CAAuB,EAAvB,GAA4B,aAAa,SAAb,CAAuB,WAAnD;;AAEA,cAAa,SAAb,CAAuB,IAAvB,GAA8B,UAAS,IAAT,EAAe,QAAf,EAAyB;AACrD,OAAI,CAAC,WAAW,QAAX,CAAL,EACE,MAAM,UAAU,6BAAV,CAAN;;AAEF,OAAI,QAAQ,KAAZ;;AAEA,YAAS,CAAT,GAAa;AACX,UAAK,cAAL,CAAoB,IAApB,EAA0B,CAA1B;;AAEA,SAAI,CAAC,KAAL,EAAY;AACV,eAAQ,IAAR;AACA,gBAAS,KAAT,CAAe,IAAf,EAAqB,SAArB;AACD;AACF;;AAED,KAAE,QAAF,GAAa,QAAb;AACA,QAAK,EAAL,CAAQ,IAAR,EAAc,CAAd;;AAEA,UAAO,IAAP;AACD,EAnBD;;AAqBA;AACA,cAAa,SAAb,CAAuB,cAAvB,GAAwC,UAAS,IAAT,EAAe,QAAf,EAAyB;AAC/D,OAAI,IAAJ,EAAU,QAAV,EAAoB,MAApB,EAA4B,CAA5B;;AAEA,OAAI,CAAC,WAAW,QAAX,CAAL,EACE,MAAM,UAAU,6BAAV,CAAN;;AAEF,OAAI,CAAC,KAAK,OAAN,IAAiB,CAAC,KAAK,OAAL,CAAa,IAAb,CAAtB,EACE,OAAO,IAAP;;AAEF,UAAO,KAAK,OAAL,CAAa,IAAb,CAAP;AACA,YAAS,KAAK,MAAd;AACA,cAAW,CAAC,CAAZ;;AAEA,OAAI,SAAS,QAAT,IACC,WAAW,KAAK,QAAhB,KAA6B,KAAK,QAAL,KAAkB,QADpD,EAC+D;AAC7D,YAAO,KAAK,OAAL,CAAa,IAAb,CAAP;AACA,SAAI,KAAK,OAAL,CAAa,cAAjB,EACE,KAAK,IAAL,CAAU,gBAAV,EAA4B,IAA5B,EAAkC,QAAlC;AAEH,IAND,MAMO,IAAI,SAAS,IAAT,CAAJ,EAAoB;AACzB,UAAK,IAAI,MAAT,EAAiB,MAAM,CAAvB,GAA2B;AACzB,WAAI,KAAK,CAAL,MAAY,QAAZ,IACC,KAAK,CAAL,EAAQ,QAAR,IAAoB,KAAK,CAAL,EAAQ,QAAR,KAAqB,QAD9C,EACyD;AACvD,oBAAW,CAAX;AACA;AACD;AACF;;AAED,SAAI,WAAW,CAAf,EACE,OAAO,IAAP;;AAEF,SAAI,KAAK,MAAL,KAAgB,CAApB,EAAuB;AACrB,YAAK,MAAL,GAAc,CAAd;AACA,cAAO,KAAK,OAAL,CAAa,IAAb,CAAP;AACD,MAHD,MAGO;AACL,YAAK,MAAL,CAAY,QAAZ,EAAsB,CAAtB;AACD;;AAED,SAAI,KAAK,OAAL,CAAa,cAAjB,EACE,KAAK,IAAL,CAAU,gBAAV,EAA4B,IAA5B,EAAkC,QAAlC;AACH;;AAED,UAAO,IAAP;AACD,EA3CD;;AA6CA,cAAa,SAAb,CAAuB,kBAAvB,GAA4C,UAAS,IAAT,EAAe;AACzD,OAAI,GAAJ,EAAS,SAAT;;AAEA,OAAI,CAAC,KAAK,OAAV,EACE,OAAO,IAAP;;AAEF;AACA,OAAI,CAAC,KAAK,OAAL,CAAa,cAAlB,EAAkC;AAChC,SAAI,UAAU,MAAV,KAAqB,CAAzB,EACE,KAAK,OAAL,GAAe,EAAf,CADF,KAEK,IAAI,KAAK,OAAL,CAAa,IAAb,CAAJ,EACH,OAAO,KAAK,OAAL,CAAa,IAAb,CAAP;AACF,YAAO,IAAP;AACD;;AAED;AACA,OAAI,UAAU,MAAV,KAAqB,CAAzB,EAA4B;AAC1B,UAAK,GAAL,IAAY,KAAK,OAAjB,EAA0B;AACxB,WAAI,QAAQ,gBAAZ,EAA8B;AAC9B,YAAK,kBAAL,CAAwB,GAAxB;AACD;AACD,UAAK,kBAAL,CAAwB,gBAAxB;AACA,UAAK,OAAL,GAAe,EAAf;AACA,YAAO,IAAP;AACD;;AAED,eAAY,KAAK,OAAL,CAAa,IAAb,CAAZ;;AAEA,OAAI,WAAW,SAAX,CAAJ,EAA2B;AACzB,UAAK,cAAL,CAAoB,IAApB,EAA0B,SAA1B;AACD,IAFD,MAEO,IAAI,SAAJ,EAAe;AACpB;AACA,YAAO,UAAU,MAAjB;AACE,YAAK,cAAL,CAAoB,IAApB,EAA0B,UAAU,UAAU,MAAV,GAAmB,CAA7B,CAA1B;AADF;AAED;AACD,UAAO,KAAK,OAAL,CAAa,IAAb,CAAP;;AAEA,UAAO,IAAP;AACD,EAtCD;;AAwCA,cAAa,SAAb,CAAuB,SAAvB,GAAmC,UAAS,IAAT,EAAe;AAChD,OAAI,GAAJ;AACA,OAAI,CAAC,KAAK,OAAN,IAAiB,CAAC,KAAK,OAAL,CAAa,IAAb,CAAtB,EACE,MAAM,EAAN,CADF,KAEK,IAAI,WAAW,KAAK,OAAL,CAAa,IAAb,CAAX,CAAJ,EACH,MAAM,CAAC,KAAK,OAAL,CAAa,IAAb,CAAD,CAAN,CADG,KAGH,MAAM,KAAK,OAAL,CAAa,IAAb,EAAmB,KAAnB,EAAN;AACF,UAAO,GAAP;AACD,EATD;;AAWA,cAAa,SAAb,CAAuB,aAAvB,GAAuC,UAAS,IAAT,EAAe;AACpD,OAAI,KAAK,OAAT,EAAkB;AAChB,SAAI,aAAa,KAAK,OAAL,CAAa,IAAb,CAAjB;;AAEA,SAAI,WAAW,UAAX,CAAJ,EACE,OAAO,CAAP,CADF,KAEK,IAAI,UAAJ,EACH,OAAO,WAAW,MAAlB;AACH;AACD,UAAO,CAAP;AACD,EAVD;;AAYA,cAAa,aAAb,GAA6B,UAAS,OAAT,EAAkB,IAAlB,EAAwB;AACnD,UAAO,QAAQ,aAAR,CAAsB,IAAtB,CAAP;AACD,EAFD;;AAIA,UAAS,UAAT,CAAoB,GAApB,EAAyB;AACvB,UAAO,OAAO,GAAP,KAAe,UAAtB;AACD;;AAED,UAAS,QAAT,CAAkB,GAAlB,EAAuB;AACrB,UAAO,OAAO,GAAP,KAAe,QAAtB;AACD;;AAED,UAAS,QAAT,CAAkB,GAAlB,EAAuB;AACrB,UAAO,QAAO,GAAP,yCAAO,GAAP,OAAe,QAAf,IAA2B,QAAQ,IAA1C;AACD;;AAED,UAAS,WAAT,CAAqB,GAArB,EAA0B;AACxB,UAAO,QAAQ,KAAK,CAApB;AACD,E;;;;;;;;AC7SD,KAAI,SAAS,oBAAQ,CAAR,CAAb;AACA;;;;;;AAMA,UAAS,YAAT,CAAuB,OAAvB,EAAgC,cAAhC,EAAgD;AAC9C;;;AAGA,OAAI,OAAO,OAAP,KAAmB,WAAvB,EAAoC;AAClC,WAAM,MAAM,sCAAN,CAAN;AACD;AACD;;;AAGA,OAAI,CAAC,cAAD,IAAmB,OAAO,IAAP,CAAY,cAAZ,EAA4B,MAA5B,KAAuC,CAA9D,EAAiE;AAC/D,aAAQ,IAAR,CAAa,gEAAb;AACD;;AAED,OAAI,OAAJ;AACA,OAAI,UAAU,EAAd;AACA;;;AAGA,QAAK,IAAI,IAAT,IAAiB,cAAjB,EAAiC;AAC/B,eAAU,eAAe,IAAf,CAAV;AACA;;;AAGA,aAAQ,IAAR,IAAgB,UAAU,OAAV,EAAmB,OAAnB,EAA4B;AAC1C,cAAO,YAAY;AACjB;;;AAGA,gBAAO,KAAK,QAAL,CAAc,OAAd,EAAuB,QAAQ,KAAR,CAAc,IAAd,EAAoB,SAApB,CAAvB,CAAP;AACD,QALM,CAKL,IALK,CAKA,IALA,CAAP;AAMD,MAPe,CAOd,IAPc,CAOT,MAPS,EAOD,OAPC,EAOQ,OAPR,CAAhB;AAQD;AACD,UAAO,OAAP;AACD;;AAED,QAAO,OAAP,GAAiB,YAAjB,C;;;;;;;;AC3CA,KAAI,SAAS,oBAAQ,CAAR,CAAb;;AAEA;;;;;AAKA,UAAS,eAAT,CAA0B,UAA1B,EAAsC;AACpC,OAAI,OAAO,UAAP,KAAsB,UAA1B,EAAsC;AACpC;;;;;AAKA,YAAO,OAAP,CAAe,UAAf;AACD;AACD,OAAK,EAAD,CAAK,QAAL,CAAc,IAAd,CAAmB,UAAnB,MAAmC,gBAAvC,EAAyD;AACvD,UAAK,IAAI,IAAI,CAAb,EAAgB,IAAI,WAAW,MAA/B,EAAuC,GAAvC,EAA4C;AAC1C,WAAI,OAAO,WAAW,CAAX,CAAP,KAAyB,UAA7B,EAAyC;AACvC,yBAAgB,WAAW,CAAX,CAAhB;AACD;AACF;AACF;AACD;;;AAGA,UAAO;AACL,sBAAiB;AADZ,IAAP;AAGD;AACD,QAAO,OAAP,GAAiB,eAAjB,C;;;;;;;;AC9BA,KAAI,OAAO,oBAAQ,CAAR,CAAX;AACA,KAAI,SAAS,KAAK,MAAlB;AACA,KAAI,cAAc,oBAAQ,CAAR,CAAlB;AACA,KAAI,eAAe,oBAAQ,CAAR,CAAnB;;AAEA,UAAS,iBAAT,CAA4B,cAA5B,EAA4C,MAA5C,EAAoD,QAApD,EAA8D,OAA9D,EAAuE;AACrE,aAAW,WAAW,QAAtB;AACA,UAAO;AACL,eAAU,aAAa,OAAb,EAAsB,cAAtB,CADL;AAEL,cAAS,YAAY,OAAZ,EAAqB,MAArB,EAA6B,QAA7B;AAFJ,IAAP;AAID;;AAED,QAAO,OAAP,GAAiB,iBAAjB,C","file":"fluder.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"Fluder\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"Fluder\"] = factory();\n\telse\n\t\troot[\"Fluder\"] = factory();\n})(this, function() {\nreturn \n\n\n/** WEBPACK FOOTER **\n ** webpack/universalModuleDefinition\n **/"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap 0f62d20a3e3d354b0304\n **/","var storeCreate = require('./storeCreator')\nvar actionCreate = require('./actionCreator')\nvar applyMiddleware = require('./applyMiddleware')\nvar actionStoreCreate = require('./actionStoreCreator')\n\nmodule.exports = {\n storeCreate: storeCreate,\n actionCreate: actionCreate,\n applyMiddleware: applyMiddleware,\n actionStoreCreate: actionStoreCreate\n}\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/index.js\n **/","var Fluder = require('./fluder')\nvar EventEmitter = require('events')\n /**\n * 创建store[对外API]\n * @param {string} storeId 该store的唯一标识,和action里的storeId一一对应\n * @param {object} method 操作store的api(一般提供get set del update等)\n * @param {object} handlers action的处理回调对象,handler的key需要和actionType一致\n * @return {object} 返回store对象\n */\nfunction storeCreate (storeId, method, handlers) {\n /**\n * 不存在storeId\n */\n if (typeof storeId === 'undefined') {\n throw Error('id is reauired as create a store, and the id is the same of store!')\n }\n var CHANGE_EVENT = 'change'\n /**\n * 创建store,继承EventEmitter\n */\n var store = Object.assign(method, EventEmitter.prototype, {\n /**\n * 统一Store的EventEmitter调用方式,避免和全局EventEmitter混淆\n * 这里把payload传给change的Store,可以做相应的渲染优化[局部渲染]\n * 这里的局部优化是指全局Stores更新,触发的Store Handler较多,可以通过payload的数据过滤\n */\n emitChange: function (payload, result) {\n this.emit(CHANGE_EVENT, payload, result)\n },\n addChangeListener: function (callback) {\n this.on(CHANGE_EVENT, callback)\n },\n removeChangeListener: function (callback) {\n this.removeListener(CHANGE_EVENT, callback)\n },\n removeAllChangeListener: function () {\n this.removeAllListeners()\n }\n })\n\n Fluder.register(storeId, {\n store: store,\n handlers: handlers\n })\n\n return store\n}\n\nmodule.exports = storeCreate\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/storeCreator.js\n **/","/**\n * Fluder 0.1.0\n * A unidirectional data flow tool based on flux.\n *\n * url: https://github.com/coderwin/Fluder\n * author: chenjiancj2011@outlook.com\n * weibo: imChenJian\n * date: 2016-08-10\n */\n\n/**\n * workflow Queue\n */\nvar Queue = require('./queue')\n\n/**\n * catchError\n */\nvar Tool = require('./tools')\nvar catchError = Tool.catchError\n\n/**\n * 构造函数\n * @return {object} 返回Fluder实例对象\n */\nfunction Fluder () {\n /**\n * store handlers 注册Map\n * @type {Object}\n */\n this._registers = {}\n\n /**\n * dispatch栈\n * @type {Array}\n */\n this._dispatchStack = []\n\n /**\n * 初始化\n */\n this._init()\n}\n\nFluder.prototype._init = function () {\n /**\n * 中间件,集中处理action payload和storeId\n */\n this._middleware = new Queue(true).after(function (payload) {\n /**\n * 中间件队列执行完后触发Store handler的调用\n */\n this._invoke(payload)\n }.bind(this))\n}\n\n/**\n * action对handler的调用(内部调用)\n * @param {object} payload 此时的payload包含action和action对应的storeId\n * @return {void} 无返回值\n */\nFluder.prototype._invoke = function (payload) {\n /**\n * storeId: 用于map到register里面注册的handler\n * @type {string}\n */\n var storeId = payload.storeId\n\n /**\n * store和它对应的handler\n * @type {object}\n */\n var store = this._registers[storeId]['store']\n var handlers = this._registers[storeId]['handlers']\n\n /**\n * action payload\n * @type {object}\n */\n payload = payload.payload\n\n /**\n * 在当前storeId的store Map到对应的handler\n * @type {function}\n */\n var handler = handlers[payload.type]\n\n if (typeof handler === 'function') {\n /**\n * TODO\n * result应该为store数据的copy,暂时没做深度copy,后续把Store改写成Immutable数据结构\n * view-controller里面对result的修改不会影响到store里的数据\n */\n var result\n // var _result = handler.call(store, payload)\n handler.call(store, payload)\n /**\n * 可以没有返回值,只是set Store里面的值\n * 这里把payload传给change的Store,可以做相应的渲染优化[局部渲染]\n */\n // if (result !== undefined) {\n store.emitChange(payload, result)\n // }\n }\n}\n\n/**\n * 更新Action栈以及记录当前ActionID\n */\nFluder.prototype._startDispatch = function (storeId) {\n this._dispatchStack || (this._dispatchStack = [])\n this._dispatchStack.push(storeId)\n this._currentDispatch = storeId\n}\n\n/**\n * Action执行完更新Action栈以及删除当前ActionID\n */\nFluder.prototype._endDispatch = function () {\n this._dispatchStack.pop()\n this._currentDispatch = null\n}\n\n/**\n * Store和handler注册\n * @param {string} storeId store/action唯一标示\n * @param {object} storeHandler store和handler\n * @return {void} 无返回值\n */\nFluder.prototype.register = function (storeId, storeHandler) {\n this._registers[storeId] = storeHandler\n}\n\n/**\n * 中间件入队\n * @param {function} middleware 中间件处理函数\n * @return {void} 无返回值\n */\nFluder.prototype.enqueue = function (middleware) {\n this._middleware.enqueue(middleware)\n}\n\n/**\n * 触发action(内部调用)\n * @param {string} storeId store/action唯一标示\n * @param {object} action action数据\n * @return {void} 无返回值\n */\nFluder.prototype.dispatch = function (storeId, payload) {\n /**\n * 在当前Action触发的Store handler回调函数中再次发起了当前Action,这样会造成A-A循环调用,出现栈溢出\n */\n if (this._currentDispatch === storeId) {\n throw Error('action ' + (payload.value && payload.value.actionType) + ' __invoke__ myself!')\n }\n\n /**\n * 在当前Action触发的Store handler回调函数中再次触发了当前Action栈中的Action,出现A-B-C-A式循环调用,也会出现栈溢出\n */\n if (this._dispatchStack.indexOf(storeId) !== -1) {\n throw Error(this._dispatchStack.join(' -> ') + storeId + ' : action __invoke__ to a circle!')\n }\n\n /**\n * 更新Action栈以及记录当前ActionID\n */\n this._startDispatch(storeId)\n\n /**\n * Action的触发必须有ActionType,原因是ActionType和Store handlers Map的key一一对应\n */\n if (!payload.type) {\n throw new Error('action type does not exist in \\n' + JSON.stringify(payload, null, 2))\n }\n\n try {\n /**\n * 发出action的时候 统一走一遍中间件\n *\n * {\n * storeId,\n * payload\n * }\n *\n * shallow Immutable\n */\n this._middleware.execute(Object.freeze({\n storeId: storeId,\n payload: payload,\n store: this._registers[storeId]['store']\n }))\n } catch (e) {\n /**\n * 执行handler的时候出错end掉当前dispatch\n */\n this._endDispatch()\n\n /**\n * 抛出错误信息\n */\n catchError(e)\n }\n\n /**\n * Action执行完更新Action栈以及删除当前ActionID\n */\n this._endDispatch()\n}\n\nmodule.exports = new Fluder()\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/fluder.js\n **/","/**\n * 队列\n * @type {Array}\n */\nvar queue = []\n\n/**\n * 队列备份\n * @type {Array}\n */\nvar _queue = []\n\n/**\n * 队列类\n */\nfunction Queue (loop) {\n this.loop = (typeof loop === 'undefined') ? true : loop\n}\n\n/**\n * 入队\n * @param {Function} 排队函数\n */\nQueue.prototype.enqueue = function (task) {\n /**\n * 入队\n */\n queue.push(task)\n // Backup\n this.loop && _queue.push(task)\n}\n\n/**\n * 执行队列函数\n * @param {Object} 可为空,在排队函数中流通的data\n * @param {Array} 可为空,替换队列中的排队函数\n */\nQueue.prototype.execute = function (data, tasks) {\n /**\n * 如果tasks存在则忽略排队函数\n */\n tasks = tasks || queue\n var task\n\n /**\n * 队列不为空\n */\n if (tasks.length) {\n /**\n * 出队\n */\n task = tasks.shift()\n task(data, this.execute.bind(this, data, tasks))\n } else {\n /**\n * 队列为空,执行完成\n */\n task = null\n this.tasksAchieved(data)\n\n // Get backup\n this.loop && (queue = _queue.concat())\n }\n}\n\n/**\n * 队列中排队函数执行完成后的回调函数\n * @param {Function} fn\n * @return {object} 返回队列实例,mock Promise\n */\nQueue.prototype.after = function (fn) {\n this.tasksAchieved = fn\n return this\n}\n\nmodule.exports = Queue\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/queue.js\n **/","function unique () {\n /**\n * Fluder Store唯一ID\n */\n return '@@Fluder/StoreId/' +\n Math.random()\n .toString(36)\n .substring(7)\n .split('')\n .join('.')\n}\n\n/**\n * 可以有中间件实现\n * @param {error} e 错误对象\n */\nfunction catchError (e) {\n var start = '\\n\\n@@Fluder/Start\\n'\n var end = '\\n@@Fluder/End\\n\\n'\n\n throw Error(start +\n 'Error: ' +\n (e.line ? (e.line + '行') : '') +\n (e.column ? (e.column + '列') : '') +\n e.message +\n end)\n}\n\nmodule.exports = {\n unique: unique,\n catchError: catchError\n}\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/tools.js\n **/","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nfunction EventEmitter() {\n this._events = this._events || {};\n this._maxListeners = this._maxListeners || undefined;\n}\nmodule.exports = EventEmitter;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nEventEmitter.defaultMaxListeners = 10;\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function(n) {\n if (!isNumber(n) || n < 0 || isNaN(n))\n throw TypeError('n must be a positive number');\n this._maxListeners = n;\n return this;\n};\n\nEventEmitter.prototype.emit = function(type) {\n var er, handler, len, args, i, listeners;\n\n if (!this._events)\n this._events = {};\n\n // If there is no 'error' event listener then throw.\n if (type === 'error') {\n if (!this._events.error ||\n (isObject(this._events.error) && !this._events.error.length)) {\n er = arguments[1];\n if (er instanceof Error) {\n throw er; // Unhandled 'error' event\n } else {\n // At least give some kind of context to the user\n var err = new Error('Uncaught, unspecified \"error\" event. (' + er + ')');\n err.context = er;\n throw err;\n }\n }\n }\n\n handler = this._events[type];\n\n if (isUndefined(handler))\n return false;\n\n if (isFunction(handler)) {\n switch (arguments.length) {\n // fast cases\n case 1:\n handler.call(this);\n break;\n case 2:\n handler.call(this, arguments[1]);\n break;\n case 3:\n handler.call(this, arguments[1], arguments[2]);\n break;\n // slower\n default:\n args = Array.prototype.slice.call(arguments, 1);\n handler.apply(this, args);\n }\n } else if (isObject(handler)) {\n args = Array.prototype.slice.call(arguments, 1);\n listeners = handler.slice();\n len = listeners.length;\n for (i = 0; i < len; i++)\n listeners[i].apply(this, args);\n }\n\n return true;\n};\n\nEventEmitter.prototype.addListener = function(type, listener) {\n var m;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events)\n this._events = {};\n\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (this._events.newListener)\n this.emit('newListener', type,\n isFunction(listener.listener) ?\n listener.listener : listener);\n\n if (!this._events[type])\n // Optimize the case of one listener. Don't need the extra array object.\n this._events[type] = listener;\n else if (isObject(this._events[type]))\n // If we've already got an array, just append.\n this._events[type].push(listener);\n else\n // Adding the second element, need to change to array.\n this._events[type] = [this._events[type], listener];\n\n // Check for listener leak\n if (isObject(this._events[type]) && !this._events[type].warned) {\n if (!isUndefined(this._maxListeners)) {\n m = this._maxListeners;\n } else {\n m = EventEmitter.defaultMaxListeners;\n }\n\n if (m && m > 0 && this._events[type].length > m) {\n this._events[type].warned = true;\n console.error('(node) warning: possible EventEmitter memory ' +\n 'leak detected. %d listeners added. ' +\n 'Use emitter.setMaxListeners() to increase limit.',\n this._events[type].length);\n if (typeof console.trace === 'function') {\n // not supported in IE 10\n console.trace();\n }\n }\n }\n\n return this;\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.once = function(type, listener) {\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n var fired = false;\n\n function g() {\n this.removeListener(type, g);\n\n if (!fired) {\n fired = true;\n listener.apply(this, arguments);\n }\n }\n\n g.listener = listener;\n this.on(type, g);\n\n return this;\n};\n\n// emits a 'removeListener' event iff the listener was removed\nEventEmitter.prototype.removeListener = function(type, listener) {\n var list, position, length, i;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events || !this._events[type])\n return this;\n\n list = this._events[type];\n length = list.length;\n position = -1;\n\n if (list === listener ||\n (isFunction(list.listener) && list.listener === listener)) {\n delete this._events[type];\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n\n } else if (isObject(list)) {\n for (i = length; i-- > 0;) {\n if (list[i] === listener ||\n (list[i].listener && list[i].listener === listener)) {\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (list.length === 1) {\n list.length = 0;\n delete this._events[type];\n } else {\n list.splice(position, 1);\n }\n\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n }\n\n return this;\n};\n\nEventEmitter.prototype.removeAllListeners = function(type) {\n var key, listeners;\n\n if (!this._events)\n return this;\n\n // not listening for removeListener, no need to emit\n if (!this._events.removeListener) {\n if (arguments.length === 0)\n this._events = {};\n else if (this._events[type])\n delete this._events[type];\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n for (key in this._events) {\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = {};\n return this;\n }\n\n listeners = this._events[type];\n\n if (isFunction(listeners)) {\n this.removeListener(type, listeners);\n } else if (listeners) {\n // LIFO order\n while (listeners.length)\n this.removeListener(type, listeners[listeners.length - 1]);\n }\n delete this._events[type];\n\n return this;\n};\n\nEventEmitter.prototype.listeners = function(type) {\n var ret;\n if (!this._events || !this._events[type])\n ret = [];\n else if (isFunction(this._events[type]))\n ret = [this._events[type]];\n else\n ret = this._events[type].slice();\n return ret;\n};\n\nEventEmitter.prototype.listenerCount = function(type) {\n if (this._events) {\n var evlistener = this._events[type];\n\n if (isFunction(evlistener))\n return 1;\n else if (evlistener)\n return evlistener.length;\n }\n return 0;\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n return emitter.listenerCount(type);\n};\n\nfunction isFunction(arg) {\n return typeof arg === 'function';\n}\n\nfunction isNumber(arg) {\n return typeof arg === 'number';\n}\n\nfunction isObject(arg) {\n return typeof arg === 'object' && arg !== null;\n}\n\nfunction isUndefined(arg) {\n return arg === void 0;\n}\n\n\n\n/** WEBPACK FOOTER **\n ** ./~/events/events.js\n **/","var Fluder = require('./fluder')\n/**\n * 创建action[对外API]\n * @param {string} storeId 该action作用于那个store,和store的storeId一一对应\n * @param {object} actionCreators 需要创建的action对象\n * @return {object} 返回一个actions对象,具有调用action触发store change的能力\n */\nfunction actionCreate (storeId, actionCreators) {\n /**\n * 不存在storeId\n */\n if (typeof storeId === 'undefined') {\n throw Error('id is reauired as creating a action!')\n }\n /**\n * action handler为空,相当于没有action\n */\n if (!actionCreators || Object.keys(actionCreators).length === 0) {\n console.warn('action handler\\'s length is 0, need you have a action handler?')\n }\n\n var creator\n var actions = {}\n /**\n * 遍历创建Action\n */\n for (var name in actionCreators) {\n creator = actionCreators[name]\n /**\n * 创建闭包,让creator不被回收\n */\n actions[name] = function (storeId, creator) {\n return function () {\n /**\n * action里面发出改变store消息\n */\n return this.dispatch(storeId, creator.apply(null, arguments))\n }.bind(this)\n }.call(Fluder, storeId, creator)\n }\n return actions\n}\n\nmodule.exports = actionCreate\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/actionCreator.js\n **/","var Fluder = require('./fluder')\n\n/**\n * 中间件[对外API]\n * @param {function} middleware action统一流入中间件\n * 这里和redux类似,和express等框架对请求的处理一样\n */\nfunction applyMiddleware (middleware) {\n if (typeof middleware === 'function') {\n /**\n * 中间件是一个队列,一个action发出时\n * 需要排队等到所有的中间件 完成才会触发对应的handler\n * @param {function} middleware\n */\n Fluder.enqueue(middleware)\n }\n if (({}).toString.call(middleware) === '[object Array]') {\n for (var i = 0; i < middleware.length; i++) {\n if (typeof middleware[i] === 'function') {\n applyMiddleware(middleware[i])\n }\n }\n }\n /**\n * 支持链式中间件\n */\n return {\n applyMiddleware: applyMiddleware\n }\n}\nmodule.exports = applyMiddleware\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/applyMiddleware.js\n **/","var Tool = require('./tools')\nvar unique = Tool.unique\nvar storeCreate = require('./storeCreator')\nvar actionCreate = require('./actionCreator')\n\nfunction actionStoreCreate (actionCreators, method, handlers, storeId) {\n storeId = (storeId || unique())\n return {\n actionor: actionCreate(storeId, actionCreators),\n storeor: storeCreate(storeId, method, handlers)\n }\n}\n\nmodule.exports = actionStoreCreate\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/actionStoreCreator.js\n **/"],"sourceRoot":""} -------------------------------------------------------------------------------- /design/fluder-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noneven/Fluder/f82861d0f3f8c2e9e427ac4efd6fbd5198dd057b/design/fluder-design.png -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # 10秒了解Fluder 2 | 3 | 4 | 5 | 16 | 35 | 53 | 54 | 74 | 75 | 76 |
6 | actionCreator 7 |
 8 | export actionCreate({
 9 |     addTodo:(item)=>({
10 |       type: constants.ADD_TODO,
11 |       value: item
12 |     })
13 | })
14 | 
15 |
17 | storeCreator 18 |
19 | let items = [];
20 | export storeCreate(todoAction, {
21 |   getAll: function(){
22 |     return items
23 |   }
24 | },{
25 |   \[constants.ADD_TODO\]: function(payload){
26 |     push(payload.value)
27 |     return items
28 |   }
29 | })
30 | function push(item){
31 |   items.push(item)
32 | }
33 | 
34 |
36 | React Component 37 |
38 | componentDidMount(){
39 |   todoStore.addChangeListener(()=>{
40 |     this.setState({
41 |       items: todoStore.getAll()
42 |     })
43 |   })
44 | }
45 | addTodo(e){
46 |   todoAction.addTodo({
47 |     text: e.target.value,
48 |     done: false
49 |   });
50 | }
51 | 
52 |
55 | Vue Component 56 |
57 | methods:{
58 |   addTodo(e){
59 |     todoAction.addTodo({
60 |       text: e.target.value,
61 |       done: false
62 |     });
63 |   }
64 | },
65 | created (){
66 |   todoStore.addChangeListener(()=>{
67 |     this.setState({
68 |       items: todoStore.getAll()
69 |     })
70 |   })
71 | }
72 | 
73 |
77 | -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | # Fluder Design 2 | 3 | > TODO 4 | 5 | ![fluder-design](../design/fluder-design.png) 6 | -------------------------------------------------------------------------------- /example/todoReact/actions/todoAction.js: -------------------------------------------------------------------------------- 1 | import {actionCreate} from '../../../src'; 2 | 3 | import constants from '../constants/constants'; 4 | const TODOID = constants.TODO_STORE_ID; 5 | export default actionCreate({ 6 | getAll:()=>({ 7 | type: `${TODOID}/${constants.GET_ALL}`, 8 | }), 9 | delAll:()=>({ 10 | type: `${TODOID}/${constants.DEL_ALL}`, 11 | }), 12 | addTodo:(addTodoV)=>({ 13 | type: `${TODOID}/${constants.ADD_TODO}`, 14 | value: addTodoV, 15 | }), 16 | delTodo:(value)=>({ 17 | type: `${TODOID}/${constants.DEL_TODO}`, 18 | value 19 | }), 20 | updateTodo:(updateTodoV,i)=>({ 21 | type: `${TODOID}/${constants.UPDATE_TODO}`, 22 | value: updateTodoV, 23 | extParam: i, 24 | }), 25 | prevTodo:(i)=>({ 26 | type: `${TODOID}/${constants.PREV_TODO}`, 27 | value: i 28 | }) 29 | }); 30 | -------------------------------------------------------------------------------- /example/todoReact/constants/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CONSTANTS部分 3 | */ 4 | const CONSTANTS = { 5 | /** 6 | * store唯一标示管理 7 | */ 8 | TODO_STORE_ID : 'TODO_STORE_ID', 9 | 10 | GET_ALL : 'GET_ALL', 11 | ADD_TODO : 'ADD_TODO', 12 | DEL_TODO : 'DEL_TODO', 13 | DEL_ALL : 'DEL_ALL', 14 | UPDATE_TODO : 'UPDATE_TODO', 15 | PREV_TODO : 'PREV_TODO', 16 | GET_BY_INDEX : "GET_BY_INDEX", 17 | }; 18 | 19 | export default CONSTANTS -------------------------------------------------------------------------------- /example/todoReact/css/index.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: #f5f5f5 3 | } 4 | ul,li{ 5 | list-style: none; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | .app{ 10 | width: 600px; 11 | margin: 0 auto; 12 | } 13 | .app-header, .app-footer{ 14 | text-align: center; 15 | height: 150px; 16 | color: rgba(175, 47, 47, 0.15); 17 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 18 | font-size: 100px; 19 | font-weight: 100; 20 | } 21 | .app-footer{ 22 | font-size: 25px; 23 | height: auto; 24 | margin: 100px; 25 | } 26 | .app-footer p{ 27 | font-size: 14px; 28 | } 29 | .app-main{ 30 | background-color: #fff; 31 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); 32 | } 33 | .app-input{ 34 | padding: 0 20px; 35 | height: 60px; 36 | line-height: 60px; 37 | border-bottom: 1px solid #eff; 38 | } 39 | .app-input input{ 40 | border: none; 41 | width: 100%; 42 | height: 30px; 43 | outline: none; 44 | 45 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 46 | font-weight: 100; 47 | } 48 | 49 | .app-input button{ 50 | display: none; 51 | } 52 | .app-items .app-items-ul li{ 53 | min-height: 40px; 54 | line-height: 40px; 55 | padding: 0 30px 0 10px; 56 | color: #4d4d4d; 57 | border-bottom: 1px solid #eff; 58 | position: relative; 59 | } 60 | .app-label{ 61 | display: inline-block; 62 | line-height: 18px; 63 | padding: 10px 0; 64 | // outline: none; 65 | width:100%; 66 | word-wrap: break-word; 67 | } 68 | .app-items .app-items-ul li:hover .app-del-icon, .app-items .app-items-ul li:hover .app-prev-icon{ 69 | display: block; 70 | } 71 | /*.app-items .app-items-ul li:hover { 72 | padding: 0 45px 0 10px; 73 | }*/ 74 | .app-prev-icon{ 75 | width: 18px; 76 | height: 18px; 77 | font-size: 18px; 78 | color: #cc9a9a; 79 | cursor: move; 80 | position: absolute; 81 | right: 30px; 82 | bottom: 22px; 83 | display: none; 84 | transition: color 0.2s ease-out; 85 | 86 | } 87 | 88 | .app-del-icon{ 89 | width: 40px; 90 | height: 40px; 91 | font-size: 18px; 92 | color: #cc9a9a; 93 | cursor: pointer; 94 | position: absolute; 95 | right: 0px; 96 | bottom: 0px; 97 | 98 | display: none; 99 | transition: color 0.2s ease-out; 100 | 101 | 102 | width: 18px; 103 | height: 18px; 104 | font-size: 18px; 105 | color: #cc9a9a; 106 | cursor: pointer; 107 | position: absolute; 108 | right: 10px; 109 | bottom: 21px; 110 | display: none; 111 | transition: color 0.2s ease-out; 112 | } 113 | .app-counter{ 114 | color: rgba(175, 47, 47, 0.15); 115 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 116 | font-size: 18px; 117 | font-weight: 100; 118 | position: relative; 119 | padding: 0 10px; 120 | min-height: 30px; 121 | } 122 | 123 | .app-counter .counter{; 124 | z-index: 1; 125 | position: absolute; 126 | left: 10px; 127 | height: 30px; 128 | line-height: 30px; 129 | } 130 | .app-counter .counter:hover{ 131 | color: green; 132 | } 133 | .app-counter .clear:hover{ 134 | color: red; 135 | cursor: pointer; 136 | } 137 | .app-counter .clear{ 138 | z-index: 1; 139 | position: absolute; 140 | right: 10px; 141 | height: 30px; 142 | line-height: 30px; 143 | } 144 | .app-counter::before{ 145 | z-index: 0; 146 | content: ''; 147 | position: absolute; 148 | right: 0; 149 | bottom: 0; 150 | left: 0; 151 | height: 30px; 152 | overflow: hidden; 153 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2); 154 | } -------------------------------------------------------------------------------- /example/todoReact/dispatcher/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | applyMiddleware 3 | } from '../../../src'; 4 | 5 | export default applyMiddleware(function(data, next) { 6 | //日志log中间件 7 | let { 8 | storeId, 9 | payload, 10 | store 11 | } = data; 12 | console.info(`actionType: \"${payload.type}\"`); 13 | console.info(`storeId: \"${storeId}\"`); 14 | console.info(`payload: ${JSON.stringify(payload)}`); 15 | 16 | console.log('oldStore:'); 17 | console.log(store.getAll && JSON.stringify(store.getAll())); 18 | next(); 19 | console.log('newStore:'); 20 | console.log(store.getAll && JSON.stringify(store.getAll())); 21 | 22 | }).applyMiddleware(function(data, next) { 23 | //异步Action中间件 24 | /** 25 | * 把action里面的异步处理统一放在中间件 26 | */ 27 | let { 28 | storeId, 29 | payload 30 | } = data; 31 | if (payload.uri) { 32 | fetch(payload.uri).then(function(response) { 33 | return response.json(); 34 | }) 35 | .then(function(response) { 36 | payload.response = response; 37 | // 异步函数中的 next 回调 38 | next(payload); 39 | }); 40 | } else next(data); // 如果不包含 uri 字段,则不作任何处理 41 | }).applyMiddleware(function(data, next) { 42 | //catchError 中间件 43 | let { 44 | storeId, 45 | payload, 46 | store 47 | } = data; 48 | try { 49 | return next() 50 | } catch (err) { 51 | console.error('Caught an exception!', err) 52 | throw err 53 | } 54 | }) 55 | -------------------------------------------------------------------------------- /example/todoReact/index.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: #f5f5f5 3 | } 4 | ul,li{ 5 | list-style: none; 6 | padding: 0; 7 | margin: 0; 8 | } 9 | .app{ 10 | width: 600px; 11 | margin: 0 auto; 12 | } 13 | .app-header, .app-footer{ 14 | text-align: center; 15 | height: 150px; 16 | color: rgba(175, 47, 47, 0.15); 17 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 18 | font-size: 100px; 19 | font-weight: 100; 20 | } 21 | .app-footer{ 22 | font-size: 25px; 23 | height: auto; 24 | margin: 100px; 25 | } 26 | .app-main{ 27 | background-color: #fff; 28 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); 29 | } 30 | .app-input{ 31 | padding: 0 20px; 32 | height: 60px; 33 | line-height: 60px; 34 | border-bottom: 1px solid #eff; 35 | } 36 | .app-input input{ 37 | border: none; 38 | width: 100%; 39 | height: 30px; 40 | outline: none; 41 | 42 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 43 | font-weight: 100; 44 | } 45 | 46 | .app-input button{ 47 | display: none; 48 | } 49 | .app-items .app-items-ul li{ 50 | min-height: 40px; 51 | line-height: 40px; 52 | padding: 0 30px 0 10px; 53 | color: #4d4d4d; 54 | border-bottom: 1px solid #eff; 55 | position: relative; 56 | } 57 | .app-label{ 58 | display: inline-block; 59 | line-height: 18px; 60 | padding: 10px 0; 61 | } 62 | .app-items .app-items-ul li:hover .app-del-icon{ 63 | display: block; 64 | } 65 | .app-prev-icon{ 66 | width: 18px; 67 | height: 18px; 68 | font-size: 18px; 69 | color: #cc9a9a; 70 | cursor: default; 71 | position: absolute; 72 | right: 30px; 73 | bottom: 21px; 74 | } 75 | 76 | .app-del-icon{ 77 | width: 40px; 78 | height: 40px; 79 | font-size: 18px; 80 | color: #cc9a9a; 81 | cursor: pointer; 82 | position: absolute; 83 | right: 0px; 84 | bottom: 0px; 85 | 86 | display: none; 87 | transition: color 0.2s ease-out; 88 | 89 | 90 | width: 18px; 91 | height: 18px; 92 | font-size: 18px; 93 | color: #cc9a9a; 94 | cursor: pointer; 95 | position: absolute; 96 | right: 10px; 97 | bottom: 21px; 98 | display: none; 99 | transition: color 0.2s ease-out; 100 | } 101 | .app-counter{ 102 | color: rgba(175, 47, 47, 0.15); 103 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 104 | font-size: 18px; 105 | font-weight: 100; 106 | position: relative; 107 | padding: 0 10px; 108 | min-height: 30px; 109 | } 110 | 111 | .app-counter .counter{; 112 | z-index: 1; 113 | position: absolute; 114 | left: 10px; 115 | height: 30px; 116 | line-height: 30px; 117 | } 118 | .app-counter .counter:hover{ 119 | color: green; 120 | } 121 | .app-counter .clear:hover{ 122 | color: red; 123 | cursor: pointer; 124 | } 125 | .app-counter .clear{ 126 | z-index: 1; 127 | position: absolute; 128 | right: 10px; 129 | height: 30px; 130 | line-height: 30px; 131 | } 132 | .app-counter::before{ 133 | z-index: 0; 134 | content: ''; 135 | position: absolute; 136 | right: 0; 137 | bottom: 0; 138 | left: 0; 139 | height: 30px; 140 | overflow: hidden; 141 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2); 142 | } -------------------------------------------------------------------------------- /example/todoReact/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fluder demo 6 | 7 | 8 |
9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/todoReact/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Root from './views/root'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('container') 9 | ); -------------------------------------------------------------------------------- /example/todoReact/stores/todoStore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * STORE部分 3 | */ 4 | import {storeCreate,actionStoreCreate} from '../../../src' 5 | 6 | /** 7 | * 启用中间件 8 | */ 9 | import applyMiddleware from '../dispatcher' 10 | import constants from '../constants/constants'; 11 | import todoAction from '../actions/todoAction'; 12 | 13 | //命名空间,可以没有 14 | var TODOID = constants.TODO_STORE_ID; 15 | /** 16 | * store数据(states)存储 17 | */ 18 | let items = function(){ 19 | var TODOAPP = localStorage.getItem('TODOAPP'); 20 | return TODOAPP?JSON.parse(TODOAPP):[]; 21 | }(); 22 | /** 23 | * store不提供set操作,只能在handler里面做set 24 | */ 25 | let API = { 26 | set: function(item){ 27 | items.push({text:item}); 28 | this.storage(); 29 | }, 30 | del: function(i){ 31 | items.splice(i,1); 32 | this.storage(); 33 | }, 34 | delAll: function(){ 35 | items = []; 36 | this.storage(); 37 | }, 38 | update: function(value,i){ 39 | items[i] = {text:value}; 40 | this.storage(); 41 | }, 42 | storage: function(){ 43 | localStorage.setItem('TODOAPP',JSON.stringify(items)); 44 | } 45 | }; 46 | 47 | /** 48 | * 注意这里面的一些方法没有用arrow function 49 | * 原因是函数内部的this是动态绑定到store上面的 50 | */ 51 | var todoStore = storeCreate(todoAction, { 52 | /** 53 | * STORE APIs 54 | */ 55 | getAll: function(){ 56 | return items; 57 | } 58 | }, { 59 | /** 60 | * STORE handlers 61 | */ 62 | [`${TODOID}/${constants.GET_ALL}`]: function(){ 63 | return items; 64 | }, 65 | 66 | [`${TODOID}/${constants.ADD_TODO}`]: function(payload){ 67 | API.set(payload.value); 68 | return items; 69 | }, 70 | 71 | [`${TODOID}/${constants.DEL_TODO}`]: function(payload){ 72 | API.del(payload.value); 73 | return items; 74 | }, 75 | 76 | [`${TODOID}/${constants.DEL_ALL}`]: function(){ 77 | API.delAll(); 78 | return items; 79 | }, 80 | 81 | [`${TODOID}/${constants.UPDATE_TODO}`]: function(payload){ 82 | API.update(payload.value,payload.extParam); 83 | return items; 84 | }, 85 | 86 | [`${TODOID}/${constants.PREV_TODO}`]: function(payload){ 87 | let i = payload.value; 88 | let v1 = items[i].text; 89 | let v2 = items[i-1].text; 90 | API.update(v1, i-1); 91 | API.update(v2, i); 92 | return items; 93 | } 94 | }); 95 | 96 | export default todoStore; 97 | -------------------------------------------------------------------------------- /example/todoReact/views/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export default class Footer extends React.Component{ 3 | constructor(props){ 4 | super(props) 5 | } 6 | render(){ 7 | return( 8 |
@imChenJian Created by Fluder 9 |

right-bottom-line to clear one

10 |

right-bottom to clear all

11 |
12 | ) 13 | } 14 | } -------------------------------------------------------------------------------- /example/todoReact/views/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export default class Header extends React.Component{ 3 | constructor(props){ 4 | super(props) 5 | } 6 | render(){ 7 | return( 8 |
todos
9 | ) 10 | } 11 | } -------------------------------------------------------------------------------- /example/todoReact/views/lines.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import todoAction from '../actions/todoAction'; 3 | export default class Lines extends React.Component{ 4 | constructor(props){ 5 | super(props); 6 | } 7 | editItem(e){ 8 | this.currentText = e.target.textContent; 9 | e.target.contentEditable=true; 10 | } 11 | editDone(i,e,editDone){ 12 | if(e.keyCode=='13'||editDone){ 13 | let nextText = e.target.textContent; 14 | if(this.currentText != nextText){ 15 | this.props.linesUpdateTodo(i,e.target.textContent); 16 | } 17 | e.target.contentEditable=false; 18 | } 19 | } 20 | delTodo(i){ 21 | todoAction.delTodo(i) 22 | } 23 | prevTodo(i){ 24 | if(i>0) todoAction.prevTodo(i) 25 | } 26 | render(){ 27 | let items = this.props.items; 28 | let lines = []; 29 | for (let i = 0; i < items.length; i++) { 30 | lines.push(
  • 31 | { 35 | // e.persist(); 36 | // setTimeout(function(){ 37 | this.editDone.call(this,i,e,true) 38 | // e.destructor(); 39 | // }.bind(this),0) 40 | }} 41 | onKeyUp={this.editDone.bind(this,i)} 42 | onFocus={this.editItem.bind(this)} 43 | >{items[i].text} 44 | × 45 | { 46 | i>0&& 47 | 48 | } 49 |
  • ) 50 | } 51 | return( 52 | 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /example/todoReact/views/root.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | 4 | require('../css/index.css'); 5 | 6 | import Header from './header'; 7 | import Footer from './footer'; 8 | import TodoApp from './todoApp'; 9 | 10 | export default class Root extends React.Component{ 11 | render(){ 12 | return ( 13 |
    14 |
    15 | 16 |
    17 |
    18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /example/todoReact/views/todoapp.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import todoStore from '../stores/todoStore'; 4 | import todoAction from '../actions/todoAction'; 5 | 6 | import Lines from './lines'; 7 | 8 | export default class TodoApp extends React.Component{ 9 | constructor(props){ 10 | super(props); 11 | this.state = { 12 | items: todoStore.getAll()||[] 13 | }; 14 | } 15 | componentDidMount(){ 16 | todoStore.addChangeListener(this.onChange.bind(this)); 17 | } 18 | onChange(){ 19 | let items = todoStore.getAll(); 20 | this.setState({ 21 | items: items 22 | }); 23 | } 24 | handleKeyup(e){ 25 | if(e.keyCode == "13"){ 26 | this.addTodo() 27 | } 28 | } 29 | delAll(){ 30 | todoAction.delAll(); 31 | } 32 | addTodo(){ 33 | let addTodoV = this.refs['app-val'].value; 34 | todoAction.addTodo(addTodoV); 35 | this.refs['app-val'].value = ''; 36 | } 37 | linesUpdateTodo(i,updateTodoV){ 38 | todoAction.updateTodo(updateTodoV,i) 39 | } 40 | render(){ 41 | let items = this.state.items; 42 | return ( 43 |
    44 |
    45 | 46 | 47 |
    48 |
    49 | 50 |
    51 | { 52 | items.length>0&& 53 |
    54 | {items.length+' items added'} 55 | Clear All 56 |
    57 | } 58 |
    59 | ) 60 | } 61 | } -------------------------------------------------------------------------------- /example/todoVue/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /example/todoVue/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /example/todoVue/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /example/todoVue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | selenium-debug.log 6 | test/unit/coverage 7 | test/e2e/reports 8 | -------------------------------------------------------------------------------- /example/todoVue/README.md: -------------------------------------------------------------------------------- 1 | # vue-fluder 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # run unit tests 18 | npm run unit 19 | 20 | # run e2e tests 21 | npm run e2e 22 | 23 | # run all tests 24 | npm test 25 | ``` 26 | 27 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 28 | -------------------------------------------------------------------------------- /example/todoVue/build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('shelljs/global') 3 | env.NODE_ENV = 'production' 4 | 5 | var path = require('path') 6 | var config = require('../config') 7 | var ora = require('ora') 8 | var webpack = require('webpack') 9 | var webpackConfig = require('./webpack.prod.conf') 10 | 11 | console.log( 12 | ' Tip:\n' + 13 | ' Built files are meant to be served over an HTTP server.\n' + 14 | ' Opening index.html over file:// won\'t work.\n' 15 | ) 16 | 17 | var spinner = ora('building for production...') 18 | spinner.start() 19 | 20 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 21 | rm('-rf', assetsPath) 22 | mkdir('-p', assetsPath) 23 | cp('-R', 'static/', assetsPath) 24 | 25 | webpack(webpackConfig, function (err, stats) { 26 | spinner.stop() 27 | if (err) throw err 28 | process.stdout.write(stats.toString({ 29 | colors: true, 30 | modules: false, 31 | children: false, 32 | chunks: false, 33 | chunkModules: false 34 | }) + '\n') 35 | }) 36 | -------------------------------------------------------------------------------- /example/todoVue/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /example/todoVue/build/dev-server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var express = require('express') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var proxyMiddleware = require('http-proxy-middleware') 6 | var webpackConfig = process.env.NODE_ENV === 'testing' 7 | ? require('./webpack.prod.conf') 8 | : require('./webpack.dev.conf') 9 | 10 | // default port where dev server listens for incoming traffic 11 | var port = process.env.PORT || config.dev.port 12 | // Define HTTP proxies to your custom API backend 13 | // https://github.com/chimurai/http-proxy-middleware 14 | var proxyTable = config.dev.proxyTable 15 | 16 | var app = express() 17 | var compiler = webpack(webpackConfig) 18 | 19 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 20 | publicPath: webpackConfig.output.publicPath, 21 | stats: { 22 | colors: true, 23 | chunks: false 24 | } 25 | }) 26 | 27 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 28 | // force page reload when html-webpack-plugin template changes 29 | compiler.plugin('compilation', function (compilation) { 30 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 31 | hotMiddleware.publish({ action: 'reload' }) 32 | cb() 33 | }) 34 | }) 35 | 36 | // proxy api requests 37 | Object.keys(proxyTable).forEach(function (context) { 38 | var options = proxyTable[context] 39 | if (typeof options === 'string') { 40 | options = { target: options } 41 | } 42 | app.use(proxyMiddleware(context, options)) 43 | }) 44 | 45 | // handle fallback for HTML5 history API 46 | app.use(require('connect-history-api-fallback')()) 47 | 48 | // serve webpack bundle output 49 | app.use(devMiddleware) 50 | 51 | // enable hot-reload and state-preserving 52 | // compilation error display 53 | app.use(hotMiddleware) 54 | 55 | // serve pure static assets 56 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 57 | app.use(staticPath, express.static('./static')) 58 | 59 | module.exports = app.listen(port, function (err) { 60 | if (err) { 61 | console.log(err) 62 | return 63 | } 64 | console.log('Listening at http://localhost:' + port + '\n') 65 | }) 66 | -------------------------------------------------------------------------------- /example/todoVue/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | // generate loader string to be used with extract text plugin 15 | function generateLoaders (loaders) { 16 | var sourceLoader = loaders.map(function (loader) { 17 | var extraParamChar 18 | if (/\?/.test(loader)) { 19 | loader = loader.replace(/\?/, '-loader?') 20 | extraParamChar = '&' 21 | } else { 22 | loader = loader + '-loader' 23 | extraParamChar = '?' 24 | } 25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 26 | }).join('!') 27 | 28 | if (options.extract) { 29 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 30 | } else { 31 | return ['vue-style-loader', sourceLoader].join('!') 32 | } 33 | } 34 | 35 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 36 | return { 37 | css: generateLoaders(['css']), 38 | postcss: generateLoaders(['css']), 39 | less: generateLoaders(['css', 'less']), 40 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 41 | scss: generateLoaders(['css', 'sass']), 42 | stylus: generateLoaders(['css', 'stylus']), 43 | styl: generateLoaders(['css', 'stylus']) 44 | } 45 | } 46 | 47 | // Generate loaders for standalone style files (outside of .vue) 48 | exports.styleLoaders = function (options) { 49 | var output = [] 50 | var loaders = exports.cssLoaders(options) 51 | for (var extension in loaders) { 52 | var loader = loaders[extension] 53 | output.push({ 54 | test: new RegExp('\\.' + extension + '$'), 55 | loader: loader 56 | }) 57 | } 58 | return output 59 | } 60 | -------------------------------------------------------------------------------- /example/todoVue/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var projectRoot = path.resolve(__dirname, '../') 5 | 6 | module.exports = { 7 | entry: { 8 | app: './src/main.js' 9 | }, 10 | output: { 11 | path: config.build.assetsRoot, 12 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 13 | filename: '[name].js' 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.vue'], 17 | fallback: [path.join(__dirname, '../node_modules')], 18 | alias: { 19 | 'src': path.resolve(__dirname, '../src'), 20 | 'assets': path.resolve(__dirname, '../src/assets'), 21 | 'components': path.resolve(__dirname, '../src/components') 22 | } 23 | }, 24 | resolveLoader: { 25 | fallback: [path.join(__dirname, '../node_modules')] 26 | }, 27 | module: { 28 | loaders: [ 29 | { 30 | test: /\.vue$/, 31 | loader: 'vue' 32 | }, 33 | { 34 | test: /\.js$/, 35 | loader: 'babel', 36 | include: projectRoot, 37 | exclude: /node_modules/, 38 | query: { 39 | presets: [ 'es2015', 'react' ] 40 | } 41 | }, 42 | { 43 | test: /\.json$/, 44 | loader: 'json' 45 | }, 46 | { 47 | test: /\.html$/, 48 | loader: 'vue-html' 49 | }, 50 | { 51 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 52 | loader: 'url', 53 | query: { 54 | limit: 10000, 55 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 56 | } 57 | }, 58 | { 59 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 60 | loader: 'url', 61 | query: { 62 | limit: 10000, 63 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 64 | } 65 | } 66 | ] 67 | }, 68 | vue: { 69 | loaders: utils.cssLoaders() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /example/todoVue/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var config = require('../config') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var utils = require('./utils') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | 8 | // add hot-reload related code to entry chunks 9 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 11 | }) 12 | 13 | module.exports = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 16 | }, 17 | // eval-source-map is faster for development 18 | devtool: '#eval-source-map', 19 | plugins: [ 20 | new webpack.DefinePlugin({ 21 | 'process.env': config.dev.env 22 | }), 23 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 24 | new webpack.optimize.OccurenceOrderPlugin(), 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }) 33 | ] 34 | }) 35 | -------------------------------------------------------------------------------- /example/todoVue/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var env = process.env.NODE_ENV === 'testing' 10 | ? require('../config/test.env') 11 | : config.build.env 12 | 13 | var webpackConfig = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) 16 | }, 17 | devtool: config.build.productionSourceMap ? '#source-map' : false, 18 | output: { 19 | path: config.build.assetsRoot, 20 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 21 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 22 | }, 23 | vue: { 24 | loaders: utils.cssLoaders({ 25 | sourceMap: config.build.productionSourceMap, 26 | extract: true 27 | }) 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | } 38 | }), 39 | new webpack.optimize.OccurenceOrderPlugin(), 40 | // extract css into its own file 41 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 42 | // generate dist index.html with correct asset hash for caching. 43 | // you can customize output by editing /index.html 44 | // see https://github.com/ampedandwired/html-webpack-plugin 45 | new HtmlWebpackPlugin({ 46 | filename: process.env.NODE_ENV === 'testing' 47 | ? 'index.html' 48 | : config.build.index, 49 | template: 'index.html', 50 | inject: true, 51 | minify: { 52 | removeComments: true, 53 | collapseWhitespace: true, 54 | removeAttributeQuotes: true 55 | // more options: 56 | // https://github.com/kangax/html-minifier#options-quick-reference 57 | }, 58 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 59 | chunksSortMode: 'dependency' 60 | }), 61 | // split vendor js into its own file 62 | new webpack.optimize.CommonsChunkPlugin({ 63 | name: 'vendor', 64 | minChunks: function (module, count) { 65 | // any required modules inside node_modules are extracted to vendor 66 | return ( 67 | module.resource && 68 | /\.js$/.test(module.resource) && 69 | module.resource.indexOf( 70 | path.join(__dirname, '../node_modules') 71 | ) === 0 72 | ) 73 | } 74 | }), 75 | // extract webpack runtime and module manifest to its own file in order to 76 | // prevent vendor hash from being updated whenever app bundle is updated 77 | new webpack.optimize.CommonsChunkPlugin({ 78 | name: 'manifest', 79 | chunks: ['vendor'] 80 | }) 81 | ] 82 | }) 83 | 84 | if (config.build.productionGzip) { 85 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 86 | 87 | webpackConfig.plugins.push( 88 | new CompressionWebpackPlugin({ 89 | asset: '[path].gz[query]', 90 | algorithm: 'gzip', 91 | test: new RegExp( 92 | '\\.(' + 93 | config.build.productionGzipExtensions.join('|') + 94 | ')$' 95 | ), 96 | threshold: 10240, 97 | minRatio: 0.8 98 | }) 99 | ) 100 | } 101 | 102 | module.exports = webpackConfig 103 | -------------------------------------------------------------------------------- /example/todoVue/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /example/todoVue/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'] 18 | }, 19 | dev: { 20 | env: require('./dev.env'), 21 | port: 7777, 22 | assetsSubDirectory: 'static', 23 | assetsPublicPath: '/', 24 | proxyTable: {}, 25 | // CSS Sourcemaps off by default because relative paths are "buggy" 26 | // with this option, according to the CSS-Loader README 27 | // (https://github.com/webpack/css-loader#sourcemaps) 28 | // In our experience, they generally work as expected, 29 | // just be aware of this issue when enabling this option. 30 | cssSourceMap: false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/todoVue/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /example/todoVue/config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /example/todoVue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-fluder 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/todoVue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-fluder", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "chenjian03 ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "node build/build.js", 10 | "test": "" 11 | }, 12 | "dependencies": { 13 | "vue": "^1.0.21", 14 | "babel-runtime": "^6.0.0" 15 | }, 16 | "devDependencies": { 17 | "babel-core": "^6.0.0", 18 | "babel-loader": "^6.0.0", 19 | "babel-plugin-transform-runtime": "^6.0.0", 20 | "babel-preset-es2015": "^6.0.0", 21 | "babel-preset-stage-2": "^6.0.0", 22 | "babel-register": "^6.0.0", 23 | "connect-history-api-fallback": "^1.1.0", 24 | "css-loader": "^0.23.0", 25 | "eventsource-polyfill": "^0.9.6", 26 | "express": "^4.13.3", 27 | "extract-text-webpack-plugin": "^1.0.1", 28 | "file-loader": "^0.8.4", 29 | "function-bind": "^1.0.2", 30 | "html-webpack-plugin": "^2.8.1", 31 | "http-proxy-middleware": "^0.12.0", 32 | "json-loader": "^0.5.4", 33 | "ora": "^0.2.0", 34 | "shelljs": "^0.6.0", 35 | "url-loader": "^0.5.7", 36 | "vue-hot-reload-api": "^1.2.0", 37 | "vue-html-loader": "^1.0.0", 38 | "vue-loader": "^8.3.0", 39 | "vue-style-loader": "^1.0.0", 40 | "webpack": "^1.12.2", 41 | "webpack-dev-middleware": "^1.4.0", 42 | "webpack-hot-middleware": "^2.6.0", 43 | "webpack-merge": "^0.8.3", 44 | "fluder": "^0.1.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/todoVue/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 36 | -------------------------------------------------------------------------------- /example/todoVue/src/actions/todoAction.js: -------------------------------------------------------------------------------- 1 | // import {actionCreate} from 'fluder'; 2 | import {actionCreate} from '../../../../src'; 3 | import constants from '../constants' 4 | export default actionCreate({ 5 | addTodo:(item)=>({ 6 | type: constants.ADD_TODO, 7 | value: item 8 | }), 9 | delTodo:(i)=>({ 10 | type: constants.DEL_TODO, 11 | value: i 12 | }), 13 | toggleState:(i, key)=>({ 14 | type: constants.TOGGLE_TODO, 15 | value: i, 16 | key: key 17 | }), 18 | localStorage:(itemStringify)=>({ 19 | type: constants.LOCAL_STORAGE, 20 | value: itemStringify 21 | }), 22 | }); 23 | -------------------------------------------------------------------------------- /example/todoVue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noneven/Fluder/f82861d0f3f8c2e9e427ac4efd6fbd5198dd057b/example/todoVue/src/assets/logo.png -------------------------------------------------------------------------------- /example/todoVue/src/components/todoApp.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | 44 | 45 | 47 | -------------------------------------------------------------------------------- /example/todoVue/src/components/todoFooter.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 40 | 41 | 42 | 56 | -------------------------------------------------------------------------------- /example/todoVue/src/components/todoHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 37 | 48 | -------------------------------------------------------------------------------- /example/todoVue/src/components/todoMain.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 42 | 43 | 44 | 57 | -------------------------------------------------------------------------------- /example/todoVue/src/constants/index.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | ADD_TODO: "ADD_TODO", 4 | DEL_TODO: "DEL_TODO", 5 | TOGGLE_TODO: "TOGGLE_TODO", 6 | LOCAL_STORAGE: "LOCAL_STORAGE", 7 | AJAX_USER_STATE: "AJAX_USER_STATE" 8 | }; 9 | -------------------------------------------------------------------------------- /example/todoVue/src/localStore/store.js: -------------------------------------------------------------------------------- 1 | export function get(key){ 2 | return JSON.parse(localStorage.getItem(key)) 3 | } 4 | export function set(key, value){ 5 | try { 6 | localStorage.setItem(key, JSON.stringify(value)) 7 | return true 8 | } catch (e) { 9 | return false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/todoVue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | /* eslint-disable no-new */ 5 | new Vue({ 6 | el: 'body', 7 | components: { App } 8 | }) 9 | -------------------------------------------------------------------------------- /example/todoVue/src/stores/todoStore.js: -------------------------------------------------------------------------------- 1 | // import {applyMiddleware,storeCreate} from 'fluder'; 2 | import {applyMiddleware,storeCreate} from '../../../../src'; 3 | 4 | import constants from '../constants' 5 | import todoAction from '../actions/todoAction' 6 | import * as LocalStore from '../localStore/store'; 7 | 8 | /** 9 | * 中间件引入 10 | */ 11 | applyMiddleware((acrion, next)=>{ 12 | //loggor 13 | let {storeId, payload, store} = acrion; 14 | console.info(`actionType: \"${payload.type}\"`); 15 | console.info(`storeId: \"${storeId}\"`); 16 | console.log(`payload: ${JSON.stringify(payload)}`); 17 | next(); 18 | console.log('--------------------------------'); 19 | }).applyMiddleware((acrion, next)=>{ 20 | //trunk 21 | let {storeId, payload} = acrion; 22 | typeof payload == "function" 23 | ? payload() 24 | : next() 25 | }) 26 | 27 | let items = LocalStore.get('TODOAPP_ID')||[]; 28 | let mobile = ''; 29 | export default storeCreate(todoAction,{ 30 | /** 31 | * store只提供读权限 32 | */ 33 | getAll: function(){ 34 | return items 35 | }, 36 | getMobile: function(){ 37 | return mobile 38 | } 39 | },{ 40 | /** 41 | * handler提供读写权限 42 | */ 43 | [constants.ADD_TODO]: function(payload){ 44 | push(payload.value) 45 | return items 46 | }, 47 | [constants.DEL_TODO]: function(payload){ 48 | del(payload.value) 49 | return items 50 | }, 51 | [constants.TOGGLE_TODO]: function(payload){ 52 | toggle(payload.value, payload.key) 53 | return items 54 | }, 55 | [constants.LOCAL_STORAGE]: function(payload){ 56 | save(payload.value) 57 | return items 58 | }, 59 | [constants.AJAX_USER_STATE]: function(payload){ 60 | setMobile(payload.value) 61 | return mobile 62 | } 63 | }) 64 | 65 | /** 66 | * 写权限API 67 | */ 68 | function push(item){ 69 | items.push(item) 70 | } 71 | function del(i){ 72 | items.splice(i,1) 73 | } 74 | function toggle(i, key){ 75 | items[i][key] = !items[i][key] 76 | } 77 | function save(itemStringify){ 78 | LocalStore.set("TODOAPP_ID", itemStringify) 79 | } 80 | function setMobile(m){ 81 | mobile = m 82 | } 83 | -------------------------------------------------------------------------------- /example/todoVue/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noneven/Fluder/f82861d0f3f8c2e9e427ac4efd6fbd5198dd057b/example/todoVue/static/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluder", 3 | "version": "0.1.6", 4 | "description": "A unidirectional data flow tool based on flux", 5 | "main": "./src/index.js", 6 | "directories": { 7 | "doc": "docs", 8 | "example": "example", 9 | "test": "test" 10 | }, 11 | "dependencies": { 12 | "events": "^1.1.1" 13 | }, 14 | "devDependencies": { 15 | "babel-core": "^6.13.2", 16 | "babel-loader": "^6.2.4", 17 | "babel-preset-react": "^6.11.1", 18 | "babel-preset-es2015": "^6.13.2", 19 | "css-loader": "^0.23.1", 20 | "react": "^15.3.0", 21 | "react-dom": "^15.3.0", 22 | "style-loader": "^0.13.1", 23 | "webpack": "^1.13.1", 24 | "mocha": "^3.0.2", 25 | "chai": "^3.5.0" 26 | }, 27 | "scripts": { 28 | "test": "mocha; mocha test/src;", 29 | "build": "webpack --config webpack.lib.config.js", 30 | "exampleVue": "cd example/todoVue; npm install; npm run dev", 31 | "exampleReact": "cd example/todoReact; npm install; open index.html", 32 | "cov": "npm install -g istanbul; npm install coveralls; istanbul cover _mocha test/src test; cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/coderwin/Fluder.git" 37 | }, 38 | "keywords": [ 39 | "flux", 40 | "redux", 41 | "fluder", 42 | "react" 43 | ], 44 | "author": "imChenJian", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/coderwin/Fluder/issues" 48 | }, 49 | "homepage": "https://github.com/coderwin/Fluder#readme" 50 | } 51 | -------------------------------------------------------------------------------- /src/actionCreator.js: -------------------------------------------------------------------------------- 1 | var Fluder = require('./fluder') 2 | /** 3 | * create action [export API] 4 | * return the actions will dispatch the store change 5 | * 6 | * @param {object} - actionCreators [generate action] 7 | * @param {string} - storeId [not required, action and store 8 | * connect with the unique id, if no, store will create a __id__ with the action] 9 | * @return {object} - actionor 10 | */ 11 | function actionCreate (actionCreators, storeId) { 12 | /** 13 | * action handler empty 14 | */ 15 | if (!actionCreators || Object.keys(actionCreators).length === 0) { 16 | console.warn('action handler\'s length is 0, need you have a action handler?') 17 | } 18 | 19 | var creator 20 | var actions = {} 21 | /** 22 | * loop create action 23 | */ 24 | for (var name in actionCreators) { 25 | creator = actionCreators[name] 26 | 27 | actions[name] = function (creator) { 28 | return function () { 29 | /** 30 | * action dispatch the store change with the storeId 31 | * or the action hidden __id__ 32 | */ 33 | return this.dispatch(storeId || actions.__id__, creator.apply(null, arguments)) 34 | }.bind(this) 35 | }.call(Fluder, creator) 36 | } 37 | return actions 38 | } 39 | 40 | module.exports = actionCreate 41 | -------------------------------------------------------------------------------- /src/actionStoreCreator.js: -------------------------------------------------------------------------------- 1 | var Tool = require('./tools') 2 | var unique = Tool.unique 3 | var storeCreate = require('./storeCreator') 4 | var actionCreate = require('./actionCreator') 5 | /** 6 | * actionStoreCreate [export API] 7 | * generator the actionor and storeor conveniently, 8 | * enter the actionCreate and storeCreate require a storeId 9 | * with arguments or unique return. 10 | * 11 | * @param {object} actionCreators - generate action with the object 12 | * @param {object} method - store get the store data API 13 | * @param {object} handlers - handle the store action type 14 | * @param {object} storeId - not required, if no unique return 15 | * @return {object} - actionor and storeor 16 | */ 17 | function actionStoreCreate (actionCreators, method, handlers, storeId) { 18 | storeId = (storeId || unique()) 19 | return { 20 | actionor: actionCreate(actionCreators, storeId), 21 | storeor: storeCreate(storeId, method, handlers) 22 | } 23 | } 24 | 25 | module.exports = actionStoreCreate 26 | -------------------------------------------------------------------------------- /src/applyMiddleware.js: -------------------------------------------------------------------------------- 1 | var Fluder = require('./fluder') 2 | 3 | /** 4 | * Middleware [export API] 5 | * 6 | * with redux-middleware similar, 7 | * and the same with express handle the request 8 | * 9 | * @param {function} - middleware action use the middleware function 10 | */ 11 | function applyMiddleware (middleware) { 12 | if (typeof middleware === 'function') { 13 | /** 14 | * Middleware is a Queue, as action sending, 15 | * need the queue[all middleware] is finished, the handler will invoke 16 | * @param {function} - middleware 17 | */ 18 | Fluder.enqueue(middleware) 19 | } 20 | /** 21 | * you can participate the param with array 22 | */ 23 | if (({}).toString.call(middleware) === '[object Array]') { 24 | for (var i = 0; i < middleware.length; i++) { 25 | if (typeof middleware[i] === 'function') { 26 | applyMiddleware(middleware[i]) 27 | } 28 | } 29 | } 30 | /** 31 | * chain middleware 32 | */ 33 | return { 34 | applyMiddleware: applyMiddleware 35 | } 36 | } 37 | module.exports = applyMiddleware 38 | -------------------------------------------------------------------------------- /src/fluder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fluder 0.1.0 3 | * A unidirectional data flow tool based on flux. 4 | * 5 | * url: https://github.com/coderwin/Fluder 6 | * author: chenjiancj2011@outlook.com 7 | * weibo: imChenJian 8 | * date: 2016-08-10 9 | */ 10 | 11 | /** 12 | * workflow Queue 13 | */ 14 | var Queue = require('./queue') 15 | 16 | /** 17 | * catchError 18 | */ 19 | var Tool = require('./tools') 20 | var catchError = Tool.catchError 21 | 22 | /** 23 | * constructor 24 | * @return {object} - the Fluder object 25 | */ 26 | function Fluder () { 27 | /** 28 | * store handlers register map 29 | * @type {Object} 30 | */ 31 | this._registers = {} 32 | 33 | /** 34 | * dispatch stack 35 | * @type {Array} 36 | */ 37 | this._dispatchStack = [] 38 | 39 | /** 40 | * init 41 | */ 42 | this._init() 43 | } 44 | 45 | Fluder.prototype._init = function () { 46 | /** 47 | * init middleware to handle the action 48 | */ 49 | this._middleware = new Queue(true).after(function (payload) { 50 | /** 51 | * after middleware finished will invoke store handler 52 | */ 53 | this._invoke(payload) 54 | }.bind(this)) 55 | } 56 | 57 | /** 58 | * action invoke store change 59 | * this payload contain the storeId、the action payload and the store 60 | * 61 | * @param {object} payload - storeId/payload/store 62 | * @return {void} - return null 63 | */ 64 | Fluder.prototype._invoke = function (payload) { 65 | /** 66 | * storeId: map the register store 67 | * @type {string} 68 | */ 69 | var storeId = payload.storeId 70 | 71 | /** 72 | * store and the store handlers 73 | * @type {object} 74 | */ 75 | var store = this._registers[storeId]['store'] 76 | var handlers = this._registers[storeId]['handlers'] 77 | 78 | /** 79 | * action payload 80 | * @type {object} 81 | */ 82 | payload = payload.payload 83 | 84 | /** 85 | * map handler in handlers with actionType 86 | * @type {string} 87 | */ 88 | var handler = handlers[payload.type] 89 | 90 | if (typeof handler === 'function') { 91 | /** 92 | * TODO 93 | * result should be the store-state copy 94 | * the state is immutable datastructures will be better 95 | * 96 | * view-controller[Vue/React components] can't revise the store state 97 | */ 98 | 99 | var result 100 | // var result = handler.call(store, payload) 101 | /** 102 | * the result is not required, only set store state 103 | * payload to store change callback to render optimization and local rending 104 | */ 105 | handler.call(store, payload) 106 | // if (result !== undefined) 107 | store.emitChange(payload, result) 108 | } 109 | } 110 | 111 | /** 112 | * check action's paload and storeId 113 | * @param {string} storeId - store/action unique 114 | * @param {object} payload - actionType and actionValue 115 | * @return {void} - return null 116 | */ 117 | Fluder.prototype._beforeDispatch = function(storeId, payload){ 118 | /** 119 | * in current action invoke the store change sending the same action 120 | * which bring about the iteration of A-A, it will mack stackoverflow 121 | */ 122 | if (this._currentDispatch === storeId) { 123 | throw Error('action ' + (payload.value && payload.value.actionType) + ' __invoke__ myself!') 124 | } 125 | 126 | /** 127 | * in current action invoke the store change 128 | * sending the action in dispatch stack which bring about 129 | * the iteration of A-B-A/A-B-C-A, it will also mack stackoverflow 130 | */ 131 | if (this._dispatchStack.indexOf(storeId) !== -1) { 132 | throw Error(this._dispatchStack.join(' -> ') + storeId + ' : action __invoke__ to a circle!') 133 | } 134 | 135 | /** 136 | * actionType in action required,because the actionType 137 | * will be connect the store handler 138 | */ 139 | if (typeof payload === 'object' && !payload.type) { 140 | throw new Error('action type does not exist in \n' + JSON.stringify(payload, null, 2)) 141 | } 142 | } 143 | 144 | /** 145 | * start dispatch to update the stack[push storeId] 146 | * and record the current dispatch storeId 147 | */ 148 | Fluder.prototype._startDispatch = function (storeId) { 149 | this._dispatchStack || (this._dispatchStack = []) 150 | this._dispatchStack.push(storeId) 151 | this._currentDispatch = storeId 152 | } 153 | 154 | /** 155 | * handler finished to update the stack[pop stack] 156 | * and delete the current dispatch 157 | */ 158 | Fluder.prototype._endDispatch = function () { 159 | this._dispatchStack.pop() 160 | this._currentDispatch = null 161 | } 162 | 163 | /** 164 | * store and handler register 165 | * @param {string} storeId - store/action unique 166 | * @param {object} storeHandler - store/handler collection 167 | * @return {void} - return null 168 | */ 169 | Fluder.prototype.register = function (storeId, storeHandler) { 170 | this._registers[storeId] = storeHandler 171 | } 172 | 173 | /** 174 | * middleware queue 175 | * @param {function} middleware - middleware handle function 176 | * @return {void} - return null 177 | */ 178 | Fluder.prototype.enqueue = function (middleware) { 179 | this._middleware.enqueue(middleware) 180 | } 181 | 182 | /** 183 | * dispatch action 184 | * @param {string} storeId - store/action unique 185 | * @param {object} action - action data 186 | * @return {void} - return null 187 | */ 188 | Fluder.prototype.dispatch = function (storeId, payload) { 189 | /** 190 | * check action's paload and storeId 191 | */ 192 | this._beforeDispatch(storeId, payload) 193 | 194 | /** 195 | * as action is a function, (Fluder-thunk) 196 | * return execute function's value 197 | */ 198 | if(typeof payload === 'function'){ 199 | return payload() 200 | } 201 | 202 | this._startDispatch(storeId) 203 | 204 | try { 205 | /** 206 | * action sending will enter the Middleware 207 | * 208 | * { 209 | * storeId, 210 | * payload 211 | * } 212 | * 213 | * shallow Immutable 214 | */ 215 | this._middleware.execute(Object.freeze({ 216 | storeId: storeId, 217 | payload: payload, 218 | store: this._registers[storeId]['store'] 219 | })) 220 | } catch (e) { 221 | /** 222 | * handler execute error, end current dispatch 223 | * for not blocking the process 224 | */ 225 | this._endDispatch() 226 | 227 | /** 228 | * catch the error 229 | * you can use catchError middleware to handle 230 | */ 231 | catchError(e) 232 | } 233 | 234 | this._endDispatch() 235 | } 236 | 237 | module.exports = new Fluder() 238 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var storeCreate = require('./storeCreator') 2 | var actionCreate = require('./actionCreator') 3 | var applyMiddleware = require('./applyMiddleware') 4 | var actionStoreCreate = require('./actionStoreCreator') 5 | 6 | module.exports = { 7 | storeCreate: storeCreate, 8 | actionCreate: actionCreate, 9 | applyMiddleware: applyMiddleware, 10 | actionStoreCreate: actionStoreCreate 11 | } 12 | -------------------------------------------------------------------------------- /src/queue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * queue 3 | * @type {Array} 4 | */ 5 | var queue = [] 6 | 7 | /** 8 | * queue backup 9 | * @type {Array} 10 | */ 11 | var _queue = [] 12 | 13 | /** 14 | * queue class 15 | */ 16 | function Queue (loop) { 17 | this.loop = (typeof loop === 'undefined') ? true : loop 18 | } 19 | 20 | /** 21 | * enter queue 22 | * @param {Function} - middleware task 23 | */ 24 | Queue.prototype.enqueue = function (task) { 25 | queue.push(task) 26 | // queue backup 27 | this.loop && _queue.push(task) 28 | } 29 | 30 | /** 31 | * execute the handle function 32 | * 33 | * @param {Object} - not required, in middleware handle function can be empty 34 | * @param {Array} - not required, it's used to replace the middleware task 35 | */ 36 | Queue.prototype.execute = function (data, tasks) { 37 | /** 38 | * if tasks exist, replace the middleware tasks 39 | */ 40 | tasks = tasks || queue 41 | var task 42 | 43 | /** 44 | * tasks not be empty 45 | */ 46 | if (tasks.length) { 47 | /** 48 | * dequeue 49 | */ 50 | task = tasks.shift() 51 | task(data, this.execute.bind(this, data, tasks)) 52 | } else { 53 | /** 54 | * execute finished 55 | */ 56 | task = null 57 | this.tasksAchieved(data) 58 | 59 | // Get backup 60 | this.loop && (queue = _queue.concat()) 61 | } 62 | } 63 | 64 | /** 65 | * middleware task execute achieved will be execute the callback 66 | * 67 | * @param {Function} - fn 68 | * @return {object} - return the queue instance,mock Promise 69 | */ 70 | Queue.prototype.after = function (fn) { 71 | this.tasksAchieved = fn 72 | return this 73 | } 74 | 75 | module.exports = Queue 76 | -------------------------------------------------------------------------------- /src/storeCreator.js: -------------------------------------------------------------------------------- 1 | var Fluder = require('./fluder') 2 | /** 3 | * Thanks to 4 | * https://github.com/Gozala/events 5 | */ 6 | var EventEmitter = require('events') 7 | var unique = require('./tools').unique 8 | var getType = require('./tools').getType 9 | /** 10 | * create store[export API] 11 | 12 | * @param {string} actions - store need the actions to change 13 | * as actionStoreCreate entering, the actions is storeId which is a string. 14 | * @param {object} method - store state getter 15 | * @param {object} handlers - action sending 16 | * @return {object} - return a store instance 17 | */ 18 | function storeCreate (actions, method, handlers) { 19 | var storeId = typeof actions === 'object' 20 | ? unique() 21 | : actions 22 | /** 23 | * storeId is required 24 | */ 25 | if (typeof storeId === 'undefined') { 26 | throw Error('id is reauired as create a store, and the id is the same of store!') 27 | } 28 | var CHANGE_EVENT = 'change' 29 | /** 30 | * create store extend Emitter 31 | */ 32 | var store = Object.assign(method, EventEmitter.prototype, { 33 | /** 34 | * store change API(Emitter API) 35 | */ 36 | emitChange: function (payload, result) { 37 | this.emit(CHANGE_EVENT, payload, result) 38 | }, 39 | addChangeListener: function (callback) { 40 | this.on(CHANGE_EVENT, callback) 41 | }, 42 | removeChangeListener: function (callback) { 43 | this.removeListener(CHANGE_EVENT, callback) 44 | }, 45 | removeAllChangeListener: function () { 46 | this.removeAllListeners() 47 | } 48 | }) 49 | 50 | var definePropertyArray = getType(actions) === 'array' 51 | ? actions 52 | : typeof actions === 'object' 53 | ? [actions] 54 | : [] 55 | /** 56 | * added storeId in actions 57 | */ 58 | definePropertyArray.length && definePropertyArray.map(function (action) { 59 | Object.defineProperty(action, '__id__', { 60 | value: storeId, 61 | writable: false, 62 | enumerable: false, 63 | configurable: false 64 | }) 65 | }) 66 | Fluder.register(storeId, { 67 | store: store, 68 | handlers: handlers 69 | }) 70 | 71 | return store 72 | } 73 | 74 | module.exports = storeCreate 75 | -------------------------------------------------------------------------------- /src/tools.js: -------------------------------------------------------------------------------- 1 | function unique () { 2 | /** 3 | * Fluder Store id 4 | */ 5 | return '@@Fluder/StoreId/' + 6 | Math.random() 7 | .toString(36) 8 | .substring(2) 9 | } 10 | 11 | /** 12 | * middleware can be realize 13 | * 14 | * @param {error} e - error object 15 | */ 16 | function catchError (e) { 17 | var start = '\n\n@@Fluder/Start\n' 18 | var end = '\n@@Fluder/End\n\n' 19 | 20 | throw Error(start + 21 | 'Error: ' + 22 | (e.line ? (e.line + '行') : '') + 23 | (e.column ? (e.column + '列') : '') + 24 | e.message + 25 | end) 26 | } 27 | 28 | /** 29 | * get data object 30 | * 31 | * @param {*} p - enter data 32 | * @return {string} - return type 33 | */ 34 | function getType (p) { 35 | var tostring = Object.prototype.toString.call(p) 36 | return tostring.substring(8, tostring.length - 1).toLowerCase() 37 | } 38 | 39 | module.exports = { 40 | unique: unique, 41 | getType: getType, 42 | catchError: catchError 43 | } 44 | -------------------------------------------------------------------------------- /test/actionCreate.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * action create test 3 | */ 4 | 5 | var actionCreate = require('../src/actionCreator') 6 | var expect = require('chai').expect 7 | 8 | function objectValues (o) { 9 | var values = [] 10 | for (var k in o) { 11 | if (o.hasOwnProperty(k)) { 12 | values.push(o[k]) 13 | } 14 | } 15 | return values 16 | } 17 | 18 | describe('actionCreator tests', function () { 19 | // it('actionCreator no storeId should throw error', function () { 20 | // expect(function () { 21 | // actionCreate() 22 | // }).to.throw(/id is reauired as creating a action!/) 23 | // }) 24 | 25 | it('actionCreator no actionCreatorMap should not throw error,but return a empty object', function () { 26 | expect(actionCreate()).to.be.empty; 27 | expect(actionCreate({})).to.be.empty; 28 | }) 29 | 30 | var actionMap = { 31 | 'addTodo': function () { 32 | console.log('addTodo') 33 | } 34 | } 35 | it('actionCreator should return a object', function () { 36 | expect(actionCreate(actionMap)).to.be.an('object') 37 | expect(actionCreate(actionMap, 'storeId')).to.be.an('object') 38 | }) 39 | 40 | it('actionCreator should return a object contact enter object', function () { 41 | expect(actionCreate(actionMap)).to.have.property('addTodo') 42 | expect(actionCreate(actionMap, 'storeId')).to.have.property('addTodo') 43 | }) 44 | 45 | it('actionCreator return a object, it\'s values should function allways', function () { 46 | var values = objectValues(actionCreate('storeId', actionMap)) 47 | 48 | for (var i = 0; i < values.length; i++) { 49 | expect(values[i]).to.be.an('function') 50 | } 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/actionStoreCreate.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * actionStoreCreate test 3 | */ 4 | 5 | var actionStoreCreate = require('../src/actionStoreCreator') 6 | var expect = require('chai').expect; 7 | 8 | describe('actionStoreCreator tests', function () { 9 | var actionStoreArgs = [{ 10 | 'addTodo': function () { 11 | console.log('addTodo') 12 | } 13 | }, { 14 | 'getAll': function () { 15 | console.log('getAll') 16 | } 17 | }, { 18 | 'addTodo': function () { 19 | console.log('addTodo handler') 20 | } 21 | }] 22 | 23 | it('actionStoreCreator should return a object', function () { 24 | expect(actionStoreCreate.apply(null, actionStoreArgs)).to.be.an('object') 25 | }) 26 | 27 | it('actionStoreCreator return a object should have actionor and storeor; \n actionor should have a key contant storeId;\n storeor should have a const storeId;\n it can be not have storeId.', function () { 28 | var actionStore = actionStoreCreate.apply(null, actionStoreArgs) 29 | expect(actionStore).to.have.property('actionor') 30 | expect(actionStore).to.have.property('storeor') 31 | expect(actionStore.actionor).to.have.property('addTodo') 32 | expect(actionStoreCreate.bind(null, ...actionStoreArgs)).to.not.throw(Error) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/applyMiddleware.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * applyMiddleware test 3 | */ 4 | 5 | var applyMiddleware = require('../src/applyMiddleware') 6 | var expect = require('chai').expect; 7 | 8 | describe('applyMiddleware tests', function () { 9 | it('applyMiddleware should return a object', function () { 10 | expect(applyMiddleware(function (data, next) { 11 | console.log('applyMiddleware') 12 | })).to.be.an('object') 13 | }) 14 | 15 | it('applyMiddleware return a object should have a chain', function () { 16 | expect(applyMiddleware(function (data, next) { 17 | console.log('applyMiddleware') 18 | })).to.have.property('applyMiddleware') 19 | 20 | expect(applyMiddleware(function (data, next) { 21 | console.log('applyMiddleware') 22 | }).applyMiddleware).to.be.an('function') 23 | }) 24 | 25 | it('applyMiddleware\'s arguments can be an array', function () { 26 | expect(function () { 27 | applyMiddleware([ 28 | function () { 29 | console.log('applyMiddleware 1') 30 | }, 31 | function () { 32 | console.log('applyMiddleware 2') 33 | } 34 | ]) 35 | }).to.not.throw(Error) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/fluder.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fluder test 3 | */ 4 | 5 | var Fluder = require('../src/fluder') 6 | var expect = require('chai').expect 7 | 8 | describe('Fluder tests', function () { 9 | it('Fluder should be new correctly: \n should have register/enqueue/dispatch" prototype methods;\n should have "_registers/_middleware/_dispatchStack" constructor property.', function () { 10 | expect(Fluder).to.be.an('object') 11 | 12 | var __proto__Arr = ['register', 'enqueue', 'dispatch']; 13 | var constructorArr = ['_registers', '_middleware', '_dispatchStack']; 14 | 15 | __proto__Arr.forEach(function (item) { 16 | expect(Fluder.__proto__).to.have.property(item) 17 | }) 18 | constructorArr.forEach(function (item) { 19 | expect(Fluder).to.have.property(item) 20 | }) 21 | }) 22 | 23 | it('Fluder enqueue method can be invoke correctly', function () { 24 | expect(function () { 25 | Fluder.enqueue(() => {}) 26 | }).to.not.throw(Error) 27 | }) 28 | 29 | it('Fluder register method can be invoke correctly', function () { 30 | expect(function () { 31 | Fluder.register('storeId', {}) 32 | }).to.not.throw(Error) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/queue.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Queue test 3 | */ 4 | 5 | var Queue = require('../src/queue') 6 | var expect = require('chai').expect 7 | 8 | describe('Queue tests', function () { 9 | it('Queue can have not a loop arguments', function () { 10 | expect(function () { 11 | new Queue() 12 | }).to.not.throw(Error) 13 | expect(new Queue()).to.be.an('object') 14 | expect(new Queue()).to.have.property('loop') 15 | expect(new Queue().loop).to.be.ok 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test/src/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ADD_TODO: 'ADD_TODO', 3 | DEL_TODO: 'DEL_TODO', 4 | TOGGLE_TODO: 'TOGGLE_TODO', 5 | PUSH_KEYS: 'PUSH_KEYS', 6 | PUSH_VALUES: 'PUSH_VALUES' 7 | } 8 | -------------------------------------------------------------------------------- /test/src/formAction.js: -------------------------------------------------------------------------------- 1 | var actionCreate = require('../../src/actionCreator') 2 | var constants = require('./constants') 3 | const FORM_ID = 'FORMID' 4 | 5 | module.exports = actionCreate({ 6 | pushKey: (key) => ({ 7 | type: `${FORM_ID}/${constants.PUSH_KEYS}`, 8 | value: key 9 | }), 10 | pushValue: (val) => ({ 11 | type: `${FORM_ID}/${constants.PUSH_VALUES}`, 12 | value: val 13 | }), 14 | noTypeAction: (val) => ({ 15 | value: val 16 | }), 17 | functionTypeAction: fn => fn 18 | }) 19 | -------------------------------------------------------------------------------- /test/src/formStore.js: -------------------------------------------------------------------------------- 1 | var storeCreate = require('../../src/storeCreator') 2 | var constants = require('./constants') 3 | const FORM_ID = 'FORMID' 4 | const formAction = require('./formAction') 5 | let keys = [] 6 | let values = [] 7 | 8 | module.exports = storeCreate(formAction, { 9 | /** 10 | * store只提供读权限 11 | */ 12 | getAll: function () { 13 | return [keys, values] 14 | } 15 | }, { 16 | /** 17 | * handler提供读写权限 18 | */ 19 | [`${FORM_ID}/${constants.PUSH_KEYS}`]: function (payload) { 20 | pushKey(payload.value) 21 | return keys 22 | }, 23 | [`${FORM_ID}/${constants.PUSH_VALUES}`]: function (payload) { 24 | pushValue(payload.value) 25 | return values 26 | } 27 | }) 28 | 29 | /** 30 | * 写权限API 31 | */ 32 | function pushKey (item) { 33 | keys.push(item) 34 | } 35 | 36 | function pushValue(item) { 37 | values.push(item) 38 | } 39 | -------------------------------------------------------------------------------- /test/src/index.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect 2 | 3 | var todoAction = require('./todoAction') 4 | var formAction = require('./formAction') 5 | var todoStore = require('./todoStore') 6 | var formStore = require('./formStore') 7 | var middleware = require('./middleware') 8 | 9 | describe('Fluder process tests\n', function () { 10 | it('Action Store Middleware running currently', function () { 11 | function actionStoreMiddlewareRunning () { 12 | todoStore.addChangeListener(function () { 13 | /** 14 | * store 15 | */ 16 | // console.log(todoStore.getAll()) 17 | }) 18 | todoAction.addTodo({ 19 | text: 'read', 20 | done: false 21 | }) 22 | } 23 | expect(actionStoreMiddlewareRunning).to.not.throw(Error) 24 | }) 25 | 26 | it('Action sending can\'t __invoke__ myself', function () { 27 | function invokeMyself () { 28 | todoStore.addChangeListener(function () { 29 | // console.log(todoStore.getAll()) 30 | todoAction.addTodo({ 31 | text: 'sleep', 32 | done: false 33 | }) 34 | }) 35 | // change invoke myself 36 | todoAction.addTodo({ 37 | text: 'read', 38 | done: false 39 | }) 40 | } 41 | expect(invokeMyself).to.throw(/__invoke__ myself/) 42 | todoStore.removeAllChangeListener() 43 | }) 44 | 45 | it('Action sending can\'t __invoke__ circle', function () { 46 | function invokeMyself () { 47 | todoStore.addChangeListener(function () { 48 | // console.log(todoStore.getAll()) 49 | formAction.pushValue('read') 50 | }) 51 | formStore.addChangeListener(function () { 52 | // console.log(formStore.getAll()) 53 | todoAction.addTodo({ 54 | text: 'sleep', 55 | done: false 56 | }) 57 | }) 58 | // change invoke myself 59 | todoAction.addTodo({ 60 | text: 'read', 61 | done: false 62 | }) 63 | } 64 | expect(invokeMyself).to.throw(/__invoke__ to a circle/) 65 | todoStore.removeAllChangeListener() 66 | formStore.removeAllChangeListener() 67 | }) 68 | 69 | it('Action type is required as action sending', function () { 70 | expect(function () { 71 | formAction.noTypeAction('notype') 72 | }).to.throw(/action type does not exist/) 73 | }) 74 | 75 | it('Action return can be a function', function () { 76 | expect(function () { 77 | formAction.functionTypeAction(function(){ 78 | //console.log('function') 79 | }) 80 | }).to.not.throw(Error) 81 | }) 82 | 83 | it('Middleware should read storeId/store/payload', function () { 84 | middleware.applyMiddleware(function (data, next) { 85 | expect(data).to.have.property('store') 86 | expect(data).to.have.property('storeId') 87 | expect(data).to.have.property('payload') 88 | }) 89 | todoAction.addTodo({ 90 | text: 'read', 91 | done: false 92 | }) 93 | }) 94 | 95 | it('Store removeChangeListener/removeAllChangeListener is currently', function () { 96 | todoStore.removeChangeListener(function () {}) 97 | todoStore.removeAllChangeListener() 98 | }) 99 | }) 100 | -------------------------------------------------------------------------------- /test/src/middleware.js: -------------------------------------------------------------------------------- 1 | var applyMiddleware = require('../../src/applyMiddleware') 2 | 3 | /** 4 | * 中间件引入 5 | */ 6 | module.exports = applyMiddleware((data, next) => { 7 | let { 8 | storeId, 9 | payload, 10 | store 11 | } = data 12 | console.log('--------------middleware loggor------------------') 13 | console.info(`actionType: \"${payload.type}\"`) 14 | console.info(`storeId: \"${storeId}\"`) 15 | console.log(payload) 16 | console.log('old store') 17 | console.log(store.getAll && store.getAll()) 18 | next() 19 | console.log('new store') 20 | console.log(store.getAll && store.getAll()) 21 | console.log('--------------middleware loggor------------------\n') 22 | }) 23 | -------------------------------------------------------------------------------- /test/src/todoAction.js: -------------------------------------------------------------------------------- 1 | var actionCreate = require('../../src/actionCreator') 2 | var constants = require('./constants') 3 | const TODOAPP_ID = 'TODOAPP' 4 | 5 | module.exports = actionCreate({ 6 | addTodo: (item) => ({ 7 | type: `${TODOAPP_ID}/${constants.ADD_TODO}`, 8 | value: item 9 | }), 10 | delTodo: (i) => ({ 11 | type: `${TODOAPP_ID}/${constants.DEL_TODO}`, 12 | value: i 13 | }), 14 | toggleState: (i, key) => ({ 15 | type: `${TODOAPP_ID}/${constants.TOGGLE_TODO}`, 16 | value: i, 17 | key: key 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/src/todoStore.js: -------------------------------------------------------------------------------- 1 | var storeCreate = require('../../src/storeCreator') 2 | var constants = require('./constants') 3 | const TODOAPP_ID = 'TODOAPP' 4 | const todoAction = require('./todoAction') 5 | var items = [] 6 | module.exports = storeCreate(todoAction, { 7 | /** 8 | * store只提供读权限 9 | */ 10 | getAll: function () { 11 | return items 12 | } 13 | }, { 14 | /** 15 | * handler提供读写权限 16 | */ 17 | [`${TODOAPP_ID}/${constants.ADD_TODO}`]: function (payload) { 18 | push(payload.value) 19 | return items 20 | }, 21 | [`${TODOAPP_ID}/${constants.DEL_TODO}`]: function (payload) { 22 | del(payload.value) 23 | return items 24 | }, 25 | [`${TODOAPP_ID}/${constants.TOGGLE_TODO}`]: function (payload) { 26 | toggle(payload.value, payload.key) 27 | return items 28 | } 29 | }) 30 | 31 | /** 32 | * 写权限API 33 | */ 34 | function push (item) { 35 | items.push(item) 36 | } 37 | 38 | function del (i) { 39 | items.splice(i, 1) 40 | } 41 | 42 | function toggle (i, key) { 43 | items[i][key] = !items[i][key] 44 | } 45 | -------------------------------------------------------------------------------- /test/storeCreate.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * store create test 3 | */ 4 | 5 | var storeCreate = require('../src/storeCreator') 6 | var expect = require('chai').expect 7 | 8 | describe('storeCreator tests', function () { 9 | it('storeCreator no storeId should throw error', function () { 10 | expect(function () { 11 | storeCreate() 12 | }).to.throw(/id is reauired as create a store, and the id is the same of store!/) 13 | }) 14 | 15 | var store = storeCreate('storeId', { 16 | getAll: function () { 17 | console.log('getAll') 18 | } 19 | }, { 20 | 'ADD_TODO': function () { 21 | console.log('ADD_TODO') 22 | } 23 | }) 24 | var store1 = storeCreate({ 25 | addTodo: function(){ 26 | console.log("action") 27 | } 28 | }, { 29 | getAll: function () { 30 | console.log('getAll') 31 | } 32 | }, { 33 | 'ADD_TODO': function () { 34 | console.log('ADD_TODO') 35 | } 36 | }) 37 | 38 | 39 | it('storeCreator should return a object', function () { 40 | expect(store).to.be.an('object') 41 | expect(store1).to.be.an('object') 42 | }) 43 | 44 | it('storeCreator should return a object which have added methods and emitter', function () { 45 | expect(store).to.have.property('getAll') 46 | expect(store1).to.have.property('getAll') 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devtool: 'source-map', 3 | entry: { 4 | 'index': './example/todoReact/index.js' 5 | }, 6 | output: { 7 | path: __dirname + '/build/js', 8 | filename: '[name].bundle.js' 9 | }, 10 | module: { 11 | loaders: [{ 12 | test: /\.js$/, 13 | loader: 'babel-loader', 14 | query: { 15 | presets: ['es2015', 'react'] 16 | } 17 | }, { 18 | test: /\.css$/, 19 | loader: 'style-loader!css-loader' 20 | }] 21 | }, 22 | resolve: { 23 | extensions: ['', '.js', '.jsx'] 24 | }, 25 | plugins: [ 26 | /* 27 | * 压缩 28 | */ 29 | // new webpack.optimize.UglifyJsPlugin({ 30 | // compress: { 31 | // warnings: false 32 | // } 33 | // }), 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /webpack.lib.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devtool: 'source-map', 3 | entry: { 4 | 'index': './src/index.js' 5 | }, 6 | output: { 7 | path: __dirname + '/build/lib', 8 | filename: 'fluder.js', 9 | library: 'Fluder', 10 | libraryTarget: 'umd', 11 | umdNamedDefine: true 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.js$/, 16 | loader: 'babel-loader', 17 | query: { 18 | presets: ['es2015', 'react'] 19 | } 20 | }] 21 | }, 22 | resolve: { 23 | extensions: ['', '.js', '.jsx'] 24 | }, 25 | plugins: [ 26 | /** 27 | * 压缩 28 | */ 29 | // new webpack.optimize.UglifyJsPlugin({ 30 | // compress: { 31 | // warnings: false 32 | // } 33 | // }), 34 | ] 35 | } 36 | --------------------------------------------------------------------------------