├── .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 | [](https://travis-ci.org/coderwin/Fluder)
8 | [](https://coveralls.io/github/coderwin/Fluder?branch=master)
9 | [](https://www.npmjs.com/package/fluder)
10 | [](https://www.npmjs.com/package/fluder)
11 | [](https://www.npmjs.com/package/fluder)
12 |
13 | ## 10秒了解Fluder
14 |
15 |
16 |
17 |
18 | todoAction
19 |
20 | export default actionCreate({
21 | addTodo:(item)=>({
22 | type: constants.ADD_TODO,
23 | value: item
24 | })
25 | })
26 |
27 | |
28 |
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 | |
47 |
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 | |
65 |
66 |
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 | |
84 |
85 |
86 |
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 | 
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 |
6 | actionCreator
7 |
8 | export actionCreate({
9 | addTodo:(item)=>({
10 | type: constants.ADD_TODO,
11 | value: item
12 | })
13 | })
14 |
15 | |
16 |
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 | |
35 |
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 | |
53 |
54 |
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 | |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/docs/design.md:
--------------------------------------------------------------------------------
1 | # Fluder Design
2 |
3 | > TODO
4 |
5 | 
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
43 |
44 |
45 |
47 |
--------------------------------------------------------------------------------
/example/todoVue/src/components/todoFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
40 |
41 |
42 |
56 |
--------------------------------------------------------------------------------
/example/todoVue/src/components/todoHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
35 |
36 |
37 |
48 |
--------------------------------------------------------------------------------
/example/todoVue/src/components/todoMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 | {{item.text}}
8 |
9 |
10 |
11 |
12 |
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 |
--------------------------------------------------------------------------------