├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── lib └── umd │ ├── meteor-immutable-observer.js │ └── meteor-immutable-observer.min.js ├── meteor ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── meteor.html ├── packages │ └── immutable-observer │ │ ├── README.md │ │ ├── global-meteor-immutable-observer.js │ │ ├── package.js │ │ └── tests │ │ ├── Players.js │ │ └── client │ │ └── ImmutableObserverSpec.js └── test ├── package.json ├── scripts └── build.sh ├── src ├── ImmutableListObserver.js ├── ImmutableMapObserver.js ├── index.js ├── setGlobalImmutableObserver.js └── updateDeep.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | !lib/umd 3 | /node_modules 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | scripts 3 | __tests__ 4 | examples 5 | .babelrc 6 | .eslintrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # meteor-immutable-observer 2 | 3 | **(work in progress!)** 4 | 5 | This uses [`Mongo.Cursor.observe`](http://docs.meteor.com/#/full/observe) and `Mongo.Cursor.observeChanges` 6 | to provide [Immutable.js](http://facebook.github.io/immutable-js/) views of the collection and its documents. 7 | This is especially handy to pass to React pure render components; when documents are changed, a custom 8 | `updateDeep` method is used so that objects/arrays inside them that didn't change will still be `===` their 9 | previous values. 10 | 11 | ## Installation 12 | 13 | ### Node/Webpack/Browserify/jspm/HTML9 Responsive Boilerstrap JS 14 | ``` 15 | npm install meteor-immutable-observer 16 | ``` 17 | Then `var ImmutableObserver = require('meteor-immutable-observer')` 18 | 19 | There are Webpack UMDs in the `lib/umd` folder (they rely on `immutable` being in a commons chunk) 20 | 21 | ### Meteor Package *(not deployed here yet)* 22 | ``` 23 | meteor add mindfront:immutable-observer 24 | ``` 25 | This will put `ImmutableObserver` in the package scope. 26 | 27 | ## API 28 | 29 | **Note**: this doesn't seem to work properly when you `Meteor.subscribe()` to your collection within the same reactive computation. I haven't yet investigated exactly why. Right now I just `subscribe()` outside of any reactive computation. 30 | 31 | ### `ImmutableObserver.Map(cursor: Mongo.Cursor)` 32 | 33 | Begins a live query via `cursor.observeChanges`, and tracks changes in an `Immutable.Map` of documents indexed by `_id`. 34 | 35 | Theoretically this should perform better than `ImmutableObserver.List`, since it doesn't keep track of document order. 36 | 37 | **This should not be called within a reactive computation.** Since its `observeChanges` can trigger dependency 38 | changes, it could cause an infinite autorun loop. 39 | 40 | *Make sure you `stop()` the observer when done with it.* 41 | 42 | ###### Example: 43 | 44 | ```javascript 45 | var Players = new Meteor.Collection('players'); 46 | var observer = ImmutableObserver.Map(Players.find({}, {limit: 10})); 47 | ... 48 | observer.stop(); 49 | ``` 50 | 51 | #### Methods 52 | 53 | ##### `documents(): Immutable.Map` 54 | 55 | Returns an `Immutable.Map` of the currently available documents, indexed by `_id`. 56 | 57 | Also registers a dependency on the underlying live query. 58 | 59 | ##### `stop()` 60 | 61 | Stops the live query (calls `stop()` on what `observeChanges` returned) 62 | 63 | ### `ImmutableObserver.List(cursor: Mongo.Cursor)` 64 | 65 | Begins a live query via `cursor.observe`, and tracks changes in an `Immutable.List` of documents in order. 66 | 67 | **This should not be called within a reactive computation.** Since its `observe` can trigger dependency 68 | changes, it could cause an infinite autorun loop. 69 | 70 | *Make sure you `stop()` the observer when done with it.* 71 | 72 | ###### Example: 73 | 74 | ```javascript 75 | var Players = new Meteor.Collection('players'); 76 | var observer = ImmutableObserver.List(Players.find({}, {sort: {score: 1}, limit: 10})); 77 | ... 78 | observer.stop(); 79 | ``` 80 | 81 | #### Methods 82 | 83 | ##### `documents(): Immutable.List` 84 | 85 | Returns an `Immutable.List` of the currently available documents. 86 | 87 | Also registers a dependency on the underlying live query. 88 | 89 | ##### `stop()` 90 | 91 | Stops the live query (calls `stop()` on what `observe` returned) 92 | 93 | ## Example (not tested) 94 | 95 | ```jsx 96 | import React from 'react'; 97 | import classNames from 'classnames'; 98 | import ImmutableObserver from 'meteor-immutable-observer'; 99 | import shouldPureComponentUpdate from 'react-pure-render/function'; 100 | 101 | class Post extends React.Component { 102 | shouldComponentUpdate = shouldPureComponentUpdate 103 | render() { 104 | var {post} = this.props; 105 | return
106 |
107 |

{post.get('title')}

108 | 109 |
110 |
111 | {post.get('content')} 112 |
113 |
; 114 | } 115 | } 116 | 117 | class PostList extends React.Component { 118 | shouldComponentUpdate = shouldPureComponentUpdate 119 | render() { 120 | var {posts} = this.props; 121 | var postComponents = []; 122 | posts.forEach(post => postComponents.push()); 123 | return
124 | {postComponents} 125 |
; 126 | } 127 | } 128 | 129 | export default React.createClass({ 130 | mixins: [ReactMeteorData], 131 | componentWillMount() { 132 | this.subscription = Meteor.subscribe('posts'); 133 | this.postsObserver = ImmutableObserver.List(Posts.find({}, {sort: {createdDate: 1}})); 134 | } 135 | componentWillUnmount() { 136 | this.subscription.stop(); 137 | this.postsObserver.stop(); 138 | } 139 | getMeteorData() { 140 | return { 141 | posts: postsObserver.documents(), 142 | }; 143 | }, 144 | render() { 145 | return ; 146 | } 147 | }); 148 | ``` 149 | -------------------------------------------------------------------------------- /lib/umd/meteor-immutable-observer.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("immutable")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["immutable"], factory); 6 | else if(typeof exports === 'object') 7 | exports["meteor-immutable-observer"] = factory(require("immutable")); 8 | else 9 | root["meteor-immutable-observer"] = factory(root["Immutable"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_2__) { 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 | exports.__esModule = true; 60 | 61 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 62 | 63 | var _ImmutableMapObserver = __webpack_require__(1); 64 | 65 | var _ImmutableMapObserver2 = _interopRequireDefault(_ImmutableMapObserver); 66 | 67 | var _ImmutableListObserver = __webpack_require__(4); 68 | 69 | var _ImmutableListObserver2 = _interopRequireDefault(_ImmutableListObserver); 70 | 71 | exports['default'] = { 72 | Map: _ImmutableMapObserver2['default'], 73 | List: _ImmutableListObserver2['default'] 74 | }; 75 | module.exports = exports['default']; 76 | 77 | /***/ }, 78 | /* 1 */ 79 | /***/ function(module, exports, __webpack_require__) { 80 | 81 | 'use strict'; 82 | 83 | exports.__esModule = true; 84 | exports['default'] = ImmutableMapObserver; 85 | 86 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 87 | 88 | var _immutable = __webpack_require__(2); 89 | 90 | var _immutable2 = _interopRequireDefault(_immutable); 91 | 92 | var _updateDeep = __webpack_require__(3); 93 | 94 | var _updateDeep2 = _interopRequireDefault(_updateDeep); 95 | 96 | function mergeChanges(document, fields) { 97 | return document.withMutations(function (document) { 98 | for (var key in fields) { 99 | if (fields.hasOwnProperty(key)) { 100 | var newValue = fields[key]; 101 | if (newValue === undefined) { 102 | document['delete'](key); 103 | } else { 104 | document.update(key, function (oldValue) { 105 | return _updateDeep2['default'](oldValue, _immutable2['default'].fromJS(newValue)); 106 | }); 107 | } 108 | } 109 | } 110 | }); 111 | } 112 | 113 | function ImmutableMapObserver(cursor) { 114 | var _documents = undefined; 115 | var dep = new Tracker.Dependency(); 116 | 117 | function update(newDocuments) { 118 | _documents = newDocuments; 119 | dep.changed(); 120 | } 121 | 122 | var initialDocuments = {}; 123 | var handle = cursor.observeChanges({ 124 | added: function added(id, fields) { 125 | fields._id = id; 126 | if (initialDocuments) { 127 | initialDocuments[id] = _immutable2['default'].fromJS(fields); 128 | } else { 129 | update(_documents.set(id, _immutable2['default'].fromJS(fields))); 130 | } 131 | }, 132 | changed: function changed(id, fields) { 133 | update(_documents.update(id, function (document) { 134 | return mergeChanges(document, fields); 135 | })); 136 | }, 137 | removed: function removed(id) { 138 | update(_documents['delete'](id)); 139 | } 140 | }); 141 | _documents = _immutable2['default'].Map(initialDocuments); 142 | initialDocuments = undefined; 143 | 144 | if (Tracker.active) { 145 | Tracker.onInvalidate(function () { 146 | handle.stop(); 147 | }); 148 | } 149 | 150 | return { 151 | documents: function documents() { 152 | dep.depend(); 153 | return _documents; 154 | }, 155 | stop: function stop() { 156 | handle.stop(); 157 | } 158 | }; 159 | } 160 | 161 | module.exports = exports['default']; 162 | 163 | /***/ }, 164 | /* 2 */ 165 | /***/ function(module, exports) { 166 | 167 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__; 168 | 169 | /***/ }, 170 | /* 3 */ 171 | /***/ function(module, exports, __webpack_require__) { 172 | 173 | 'use strict'; 174 | 175 | exports.__esModule = true; 176 | exports['default'] = updateDeep; 177 | 178 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 179 | 180 | var _immutable = __webpack_require__(2); 181 | 182 | var _immutable2 = _interopRequireDefault(_immutable); 183 | 184 | function updateDeep(a, b) { 185 | if (!(a instanceof _immutable2['default'].Collection) || !(b instanceof _immutable2['default'].Collection) || Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { 186 | return a === b ? a : b; 187 | } 188 | return a.withMutations(function (result) { 189 | a.forEach(function (oldValue, key) { 190 | if (!b.has(key)) { 191 | result['delete'](key); 192 | } 193 | }); 194 | b.forEach(function (newValue, key) { 195 | if (!a.has(key)) { 196 | result.set(key, newValue); 197 | } else { 198 | result.set(key, updateDeep(a.get(key), newValue)); 199 | } 200 | }); 201 | }); 202 | } 203 | 204 | module.exports = exports['default']; 205 | 206 | /***/ }, 207 | /* 4 */ 208 | /***/ function(module, exports, __webpack_require__) { 209 | 210 | 'use strict'; 211 | 212 | exports.__esModule = true; 213 | exports['default'] = ImmutableListObserver; 214 | 215 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 216 | 217 | var _immutable = __webpack_require__(2); 218 | 219 | var _immutable2 = _interopRequireDefault(_immutable); 220 | 221 | var _updateDeep = __webpack_require__(3); 222 | 223 | var _updateDeep2 = _interopRequireDefault(_updateDeep); 224 | 225 | function ImmutableListObserver(cursor) { 226 | var _documents = undefined; 227 | var dep = new Tracker.Dependency(); 228 | 229 | function update(newDocuments) { 230 | _documents = newDocuments; 231 | dep.changed(); 232 | } 233 | 234 | var initialDocuments = []; 235 | var handle = cursor.observe({ 236 | addedAt: function addedAt(document, atIndex, before) { 237 | if (initialDocuments) { 238 | initialDocuments.splice(atIndex, 0, _immutable2['default'].fromJS(document)); 239 | } else { 240 | update(_documents.splice(atIndex, 0, _immutable2['default'].fromJS(document))); 241 | } 242 | }, 243 | changedAt: function changedAt(newDocument, oldDocument, atIndex) { 244 | update(_documents.update(id, function (document) { 245 | return _updateDeep2['default'](document, _immutable2['default'].fromJS(newDocument)); 246 | })); 247 | }, 248 | removedAt: function removedAt(oldDocument, atIndex) { 249 | update(_documents.splice(atIndex, 1)); 250 | }, 251 | movedTo: function movedTo(document, fromIndex, toIndex, before) { 252 | var movedDocument = _documents.get(fromIndex); 253 | update(_documents.splice(fromIndex, 1).splice(toIndex, 0, movedDocument)); 254 | } 255 | }); 256 | _documents = _immutable2['default'].List(initialDocuments); 257 | initialDocuments = undefined; 258 | 259 | if (Tracker.active) { 260 | Tracker.onInvalidate(function () { 261 | handle.stop(); 262 | }); 263 | } 264 | 265 | return { 266 | documents: function documents() { 267 | dep.depend(); 268 | return _documents; 269 | }, 270 | stop: function stop() { 271 | handle.stop(); 272 | } 273 | }; 274 | } 275 | 276 | module.exports = exports['default']; 277 | 278 | /***/ } 279 | /******/ ]) 280 | }); 281 | ; -------------------------------------------------------------------------------- /lib/umd/meteor-immutable-observer.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("immutable")):"function"==typeof define&&define.amd?define(["immutable"],t):"object"==typeof exports?exports["meteor-immutable-observer"]=t(require("immutable")):e["meteor-immutable-observer"]=t(e.Immutable)}(this,function(e){return function(e){function t(o){if(n[o])return n[o].exports;var u=n[o]={exports:{},id:o,loaded:!1};return e[o].call(u.exports,u,u.exports,t),u.loaded=!0,u.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}t.__esModule=!0;var u=n(4),r=o(u),f=n(3),i=o(f);t["default"]={Map:r["default"],List:i["default"]},e.exports=t["default"]},function(t,n){t.exports=e},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function u(e,t){return e instanceof f["default"].Collection&&t instanceof f["default"].Collection&&Object.getPrototypeOf(e)===Object.getPrototypeOf(t)?e.withMutations(function(n){e.forEach(function(e,o){t.has(o)||n["delete"](o)}),t.forEach(function(t,o){e.has(o)?n.set(o,u(e.get(o),t)):n.set(o,t)})}):e===t?e:t}t.__esModule=!0,t["default"]=u;var r=n(1),f=o(r);e.exports=t["default"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function u(e){function t(e){n=e,o.changed()}var n=void 0,o=new Tracker.Dependency,u=[],r=e.observe({addedAt:function(e,o,r){u?u.splice(o,0,f["default"].fromJS(e)):t(n.splice(o,0,f["default"].fromJS(e)))},changedAt:function(e,o,u){t(n.update(id,function(t){return d["default"](t,f["default"].fromJS(e))}))},removedAt:function(e,o){t(n.splice(o,1))},movedTo:function(e,o,u,r){var f=n.get(o);t(n.splice(o,1).splice(u,0,f))}});return n=f["default"].List(u),u=void 0,Tracker.active&&Tracker.onInvalidate(function(){r.stop()}),{documents:function(){return o.depend(),n},stop:function(){r.stop()}}}t.__esModule=!0,t["default"]=u;var r=n(1),f=o(r),i=n(2),d=o(i);e.exports=t["default"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function u(e,t){return e.withMutations(function(e){for(var n in t)if(t.hasOwnProperty(n)){var o=t[n];void 0===o?e["delete"](n):e.update(n,function(e){return a["default"](e,i["default"].fromJS(o))})}})}function r(e){function t(e){n=e,o.changed()}var n=void 0,o=new Tracker.Dependency,r={},f=e.observeChanges({added:function(e,o){o._id=e,r?r[e]=i["default"].fromJS(o):t(n.set(e,i["default"].fromJS(o)))},changed:function(e,o){t(n.update(e,function(e){return u(e,o)}))},removed:function(e){t(n["delete"](e))}});return n=i["default"].Map(r),r=void 0,Tracker.active&&Tracker.onInvalidate(function(){f.stop()}),{documents:function(){return o.depend(),n},stop:function(){f.stop()}}}t.__esModule=!0,t["default"]=r;var f=n(1),i=o(f),d=n(2),a=o(d);e.exports=t["default"]}])}); -------------------------------------------------------------------------------- /meteor/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /meteor/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /meteor/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 196ubmjboa7qx2vr5ju 8 | -------------------------------------------------------------------------------- /meteor/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | insecure 9 | mindfront:immutable-observer 10 | velocity:html-reporter 11 | -------------------------------------------------------------------------------- /meteor/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /meteor/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.3 2 | -------------------------------------------------------------------------------- /meteor/.meteor/versions: -------------------------------------------------------------------------------- 1 | amplify@1.0.0 2 | autoupdate@1.2.1 3 | base64@1.0.3 4 | binary-heap@1.0.3 5 | blaze@2.1.2 6 | blaze-tools@1.0.3 7 | boilerplate-generator@1.0.3 8 | callback-hook@1.0.3 9 | check@1.0.5 10 | coffeescript@1.0.6 11 | dataflows:immutable@3.6.2 12 | ddp@1.1.0 13 | deps@1.0.7 14 | ejson@1.0.6 15 | fastclick@1.0.3 16 | geojson-utils@1.0.3 17 | grigio:babel@0.1.7 18 | html-tools@1.0.4 19 | htmljs@1.0.4 20 | http@1.1.0 21 | id-map@1.0.3 22 | insecure@1.0.3 23 | jedwards1211:immutable-observer@0.0.1 24 | jquery@1.11.3_2 25 | json@1.0.3 26 | launch-screen@1.0.2 27 | less@1.0.14 28 | livedata@1.0.13 29 | logging@1.0.7 30 | meteor@1.1.6 31 | meteor-platform@1.2.2 32 | minifiers@1.1.5 33 | minimongo@1.0.8 34 | mobile-status-bar@1.0.3 35 | mongo@1.1.0 36 | observe-sequence@1.0.6 37 | ordered-dict@1.0.3 38 | package-version-parser@3.0.3 39 | practicalmeteor:chai@2.1.0_1 40 | practicalmeteor:loglevel@1.2.0_2 41 | random@1.0.3 42 | reactive-dict@1.1.0 43 | reactive-var@1.0.5 44 | reload@1.1.3 45 | retry@1.0.3 46 | routepolicy@1.0.5 47 | sanjo:jasmine@0.18.0 48 | sanjo:karma@1.7.0 49 | sanjo:long-running-child-process@1.1.3 50 | sanjo:meteor-files-helpers@1.1.0_7 51 | sanjo:meteor-version@1.0.0 52 | session@1.1.0 53 | spacebars@1.0.6 54 | spacebars-compiler@1.0.6 55 | templating@1.1.1 56 | tracker@1.0.7 57 | ui@1.0.6 58 | underscore@1.0.3 59 | url@1.0.4 60 | velocity:chokidar@1.0.3_1 61 | velocity:core@0.9.3 62 | velocity:html-reporter@0.8.2 63 | velocity:meteor-internals@1.1.0_7 64 | velocity:meteor-stubs@1.1.0 65 | velocity:shim@0.1.0 66 | velocity:source-map-support@0.3.2_1 67 | webapp@1.2.0 68 | webapp-hashing@1.0.3 69 | -------------------------------------------------------------------------------- /meteor/meteor.html: -------------------------------------------------------------------------------- 1 | 2 | meteor 3 | 4 | 5 | 6 |

Welcome to Meteor!

7 | 8 | {{> hello}} 9 | -------------------------------------------------------------------------------- /meteor/packages/immutable-observer/README.md: -------------------------------------------------------------------------------- 1 | ../../../README.md -------------------------------------------------------------------------------- /meteor/packages/immutable-observer/global-meteor-immutable-observer.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("immutable")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["immutable"], factory); 6 | else if(typeof exports === 'object') 7 | exports["meteor-immutable-observer"] = factory(require("immutable")); 8 | else 9 | root["meteor-immutable-observer"] = factory(root["Immutable"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_3__) { 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 | var global = (function() { return this; })(); 58 | global.ImmutableObserver = __webpack_require__(1); 59 | 60 | 61 | /***/ }, 62 | /* 1 */ 63 | /***/ function(module, exports, __webpack_require__) { 64 | 65 | 'use strict'; 66 | 67 | exports.__esModule = true; 68 | 69 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 70 | 71 | var _ImmutableMapObserver = __webpack_require__(2); 72 | 73 | var _ImmutableMapObserver2 = _interopRequireDefault(_ImmutableMapObserver); 74 | 75 | var _ImmutableListObserver = __webpack_require__(5); 76 | 77 | var _ImmutableListObserver2 = _interopRequireDefault(_ImmutableListObserver); 78 | 79 | exports['default'] = { 80 | Map: _ImmutableMapObserver2['default'], 81 | List: _ImmutableListObserver2['default'] 82 | }; 83 | module.exports = exports['default']; 84 | 85 | /***/ }, 86 | /* 2 */ 87 | /***/ function(module, exports, __webpack_require__) { 88 | 89 | 'use strict'; 90 | 91 | exports.__esModule = true; 92 | exports['default'] = ImmutableMapObserver; 93 | 94 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 95 | 96 | var _immutable = __webpack_require__(3); 97 | 98 | var _immutable2 = _interopRequireDefault(_immutable); 99 | 100 | var _updateDeep = __webpack_require__(4); 101 | 102 | var _updateDeep2 = _interopRequireDefault(_updateDeep); 103 | 104 | function mergeChanges(document, fields) { 105 | return document.withMutations(function (document) { 106 | for (var key in fields) { 107 | if (fields.hasOwnProperty(key)) { 108 | var newValue = fields[key]; 109 | if (newValue === undefined) { 110 | document['delete'](key); 111 | } else { 112 | document.update(key, function (oldValue) { 113 | return _updateDeep2['default'](oldValue, _immutable2['default'].fromJS(newValue)); 114 | }); 115 | } 116 | } 117 | } 118 | }); 119 | } 120 | 121 | function ImmutableMapObserver(cursor) { 122 | var _documents = undefined; 123 | var dep = new Tracker.Dependency(); 124 | 125 | function update(newDocuments) { 126 | _documents = newDocuments; 127 | dep.changed(); 128 | } 129 | 130 | var initialDocuments = {}; 131 | var handle = cursor.observeChanges({ 132 | added: function added(id, fields) { 133 | fields._id = id; 134 | if (initialDocuments) { 135 | initialDocuments[id] = _immutable2['default'].fromJS(fields); 136 | } else { 137 | update(_documents.set(id, _immutable2['default'].fromJS(fields))); 138 | } 139 | }, 140 | changed: function changed(id, fields) { 141 | update(_documents.update(id, function (document) { 142 | return mergeChanges(document, fields); 143 | })); 144 | }, 145 | removed: function removed(id) { 146 | update(_documents['delete'](id)); 147 | } 148 | }); 149 | _documents = _immutable2['default'].Map(initialDocuments); 150 | initialDocuments = undefined; 151 | 152 | if (Tracker.active) { 153 | Tracker.onInvalidate(function () { 154 | handle.stop(); 155 | }); 156 | } 157 | 158 | return { 159 | documents: function documents() { 160 | dep.depend(); 161 | return _documents; 162 | }, 163 | stop: function stop() { 164 | handle.stop(); 165 | } 166 | }; 167 | } 168 | 169 | module.exports = exports['default']; 170 | 171 | /***/ }, 172 | /* 3 */ 173 | /***/ function(module, exports) { 174 | 175 | module.exports = __WEBPACK_EXTERNAL_MODULE_3__; 176 | 177 | /***/ }, 178 | /* 4 */ 179 | /***/ function(module, exports, __webpack_require__) { 180 | 181 | 'use strict'; 182 | 183 | exports.__esModule = true; 184 | exports['default'] = updateDeep; 185 | 186 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 187 | 188 | var _immutable = __webpack_require__(3); 189 | 190 | var _immutable2 = _interopRequireDefault(_immutable); 191 | 192 | function updateDeep(a, b) { 193 | if (!(a instanceof _immutable2['default'].Collection) || !(b instanceof _immutable2['default'].Collection) || Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { 194 | return a === b ? a : b; 195 | } 196 | return a.withMutations(function (result) { 197 | a.forEach(function (oldValue, key) { 198 | if (!b.has(key)) { 199 | result['delete'](key); 200 | } 201 | }); 202 | b.forEach(function (newValue, key) { 203 | if (!a.has(key)) { 204 | result.set(key, newValue); 205 | } else { 206 | result.set(key, updateDeep(a.get(key), newValue)); 207 | } 208 | }); 209 | }); 210 | } 211 | 212 | module.exports = exports['default']; 213 | 214 | /***/ }, 215 | /* 5 */ 216 | /***/ function(module, exports, __webpack_require__) { 217 | 218 | 'use strict'; 219 | 220 | exports.__esModule = true; 221 | exports['default'] = ImmutableListObserver; 222 | 223 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 224 | 225 | var _immutable = __webpack_require__(3); 226 | 227 | var _immutable2 = _interopRequireDefault(_immutable); 228 | 229 | var _updateDeep = __webpack_require__(4); 230 | 231 | var _updateDeep2 = _interopRequireDefault(_updateDeep); 232 | 233 | function ImmutableListObserver(cursor) { 234 | var _documents = undefined; 235 | var dep = new Tracker.Dependency(); 236 | 237 | function update(newDocuments) { 238 | _documents = newDocuments; 239 | dep.changed(); 240 | } 241 | 242 | var initialDocuments = []; 243 | var handle = cursor.observe({ 244 | addedAt: function addedAt(document, atIndex, before) { 245 | if (initialDocuments) { 246 | initialDocuments.splice(atIndex, 0, _immutable2['default'].fromJS(document)); 247 | } else { 248 | update(_documents.splice(atIndex, 0, _immutable2['default'].fromJS(document))); 249 | } 250 | }, 251 | changedAt: function changedAt(newDocument, oldDocument, atIndex) { 252 | update(_documents.update(id, function (document) { 253 | return _updateDeep2['default'](document, _immutable2['default'].fromJS(newDocument)); 254 | })); 255 | }, 256 | removedAt: function removedAt(oldDocument, atIndex) { 257 | update(_documents.splice(atIndex, 1)); 258 | }, 259 | movedTo: function movedTo(document, fromIndex, toIndex, before) { 260 | var movedDocument = _documents.get(fromIndex); 261 | update(_documents.splice(fromIndex, 1).splice(toIndex, 0, movedDocument)); 262 | } 263 | }); 264 | _documents = _immutable2['default'].List(initialDocuments); 265 | initialDocuments = undefined; 266 | 267 | if (Tracker.active) { 268 | Tracker.onInvalidate(function () { 269 | handle.stop(); 270 | }); 271 | } 272 | 273 | return { 274 | documents: function documents() { 275 | dep.depend(); 276 | return _documents; 277 | }, 278 | stop: function stop() { 279 | handle.stop(); 280 | } 281 | }; 282 | } 283 | 284 | module.exports = exports['default']; 285 | 286 | /***/ } 287 | /******/ ]) 288 | }); 289 | ; -------------------------------------------------------------------------------- /meteor/packages/immutable-observer/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'mindfront:immutable-observer', 3 | version: '0.0.1', 4 | // Brief, one-line summary of the package. 5 | summary: 'Creates and auto-updates Immutable.js collections from Meteor Collection cursors', 6 | // URL to the Git repository containing the source code for this package. 7 | git: 'https://github.com/mindfront/meteor-immutable-observer', 8 | // By default, Meteor will default to using README.md for documentation. 9 | // To avoid submitting documentation, set this field to null. 10 | documentation: 'README.md' 11 | }); 12 | 13 | Package.onUse(function(api) { 14 | api.versionsFrom('1.1.0.3'); 15 | api.use('dataflows:immutable@3.6.2') 16 | api.addFiles('global-meteor-immutable-observer.js'); 17 | }); 18 | 19 | Package.onTest(function(api) { 20 | api.use('sanjo:jasmine@0.18.0'); 21 | api.use('dataflows:immutable@3.6.2') 22 | api.use('insecure'); 23 | api.addFiles('global-meteor-immutable-observer.js'); 24 | api.addFiles('tests/Players.js'); 25 | api.addFiles('tests/client/ImmutableObserverSpec.js', 'client'); 26 | }); -------------------------------------------------------------------------------- /meteor/packages/immutable-observer/tests/Players.js: -------------------------------------------------------------------------------- 1 | // Set up a collection to contain player information. On the server, 2 | // it is backed by a MongoDB collection named "players". 3 | 4 | Players = new Meteor.Collection("players"); 5 | 6 | // On server startup, create some players if the database is empty. 7 | if (Meteor.isServer) { 8 | Meteor.methods({ 9 | clearPlayers: function() { 10 | Players.remove({}); 11 | } 12 | }); 13 | 14 | Meteor.startup(function () { 15 | Meteor.publish('players', function(){ 16 | return Players.find(); 17 | }); 18 | }); 19 | } -------------------------------------------------------------------------------- /meteor/packages/immutable-observer/tests/client/ImmutableObserverSpec.js: -------------------------------------------------------------------------------- 1 | describe('ImmutableObserver', function() { 2 | beforeEach(function(done) { 3 | cleanupContext = { 4 | stoppables: [], 5 | }; 6 | Meteor.call('clearPlayers', function() { 7 | stopAfter(Meteor.subscribe('players', {onReady: function() { 8 | done(); 9 | }})); 10 | }); 11 | }); 12 | 13 | afterEach(function(done) { 14 | cleanupContext.stoppables.forEach(function(stoppable) { 15 | stoppable.stop(); 16 | }); 17 | done(); 18 | }); 19 | 20 | function stopAfter(stoppable) { 21 | cleanupContext.stoppables.push(stoppable); 22 | } 23 | 24 | describe('ImmutableObserver.List', function() { 25 | it('gets multiple items in correct sorted order', function(done) { 26 | var observer = ImmutableObserver.List(Players.find({}, {sort: {score: 1}})); 27 | stopAfter(observer); 28 | 29 | stopAfter(Tracker.autorun(function() { 30 | var list = observer.documents(); 31 | console.log(JSON.stringify(list.toJS(), null, " ")); 32 | var prev; 33 | list.forEach(function(next) { 34 | if (prev) { 35 | expect(next.get('score')).not.toBeLessThan(prev.get('score')); 36 | } 37 | prev = next; 38 | }); 39 | if (list.size === 5) { 40 | done(); 41 | } 42 | })); 43 | 44 | Players.insert({name: 'Andy', score: 1000000}); 45 | Players.insert({name: 'Jim', score: 30516}); 46 | Players.insert({name: 'John', score: 178982}); 47 | Players.insert({name: 'Kate', score: 999999}); 48 | Players.insert({name: 'Carla', score: 97817}); 49 | }); 50 | }); 51 | 52 | describe('ImmutableObserver.Map', function() { 53 | it('basic test', function(done) { 54 | var observer = ImmutableObserver.Map(Players.find({name: 'Andy', score: {$gt: 1000000}})); 55 | stopAfter(observer); 56 | 57 | stopAfter(Tracker.autorun(function() { 58 | var documents = observer.documents(); 59 | console.log(JSON.stringify(documents.toJS(), null, " ")); 60 | if (documents.size) { 61 | done(); 62 | } 63 | })); 64 | Players.insert({name: 'Andy', score: 1000000}, function(error, _id) { 65 | if (error) { 66 | done.fail(error); 67 | } 68 | Players.update({_id: _id}, {$inc: {score: 5}}, function(error) { 69 | if (error) { 70 | done.fail(error); 71 | } 72 | }); 73 | }); 74 | }); 75 | 76 | it('updates fields correctly', function(done) { 77 | var observer = ImmutableObserver.Map(Players.find()); 78 | stopAfter(observer); 79 | 80 | var _id; 81 | 82 | Players.insert({name: 'Andy', test: {a: [{b: [{c: 1}, {d: 2}]}], e: [{f: 1}]}}, function(error, id) { 83 | if (error) done.fail(error); 84 | _id = id; 85 | phase1(); 86 | }); 87 | 88 | var initPlayer; 89 | 90 | function phase1() { 91 | var comp = Tracker.autorun(function(comp) { 92 | var documents = observer.documents(); 93 | console.log(JSON.stringify(documents.toJS(), null, " ")); 94 | if (documents.size === 1 && documents.has(_id)) { 95 | initPlayer = documents.get(_id); 96 | comp.stop(); 97 | phase2(); 98 | } 99 | }); 100 | stopAfter(comp); 101 | } 102 | 103 | function phase2() { 104 | var updated = false; 105 | var newTest = {test: {a: [{b: [{c: 1}, {d: 2, g: 3, h: 4}, {i: 5}]}], e: [{f: 1}]}}; 106 | Players.update({_id: _id}, {$set: newTest, $unset: {name: ""}}); 107 | 108 | var comp = Tracker.autorun(function() { 109 | Meteor.subscribe('players'); 110 | var documents = observer.documents(); 111 | console.log(JSON.stringify(documents.toJS(), null, " ")); 112 | if (documents.size === 1 && documents.get(_id) !== initPlayer) { 113 | var newPlayer = documents.get(_id); 114 | var unchangedPaths = [['test', 'a', 0, 'b', 0], ['test', 'e', 0]]; 115 | var changedPaths = [['name'], ['test', 'a', 0, 'b', 1], ['test', 'a', 0, 'b', 2]]; 116 | unchangedPaths.forEach(function(path) { 117 | expect(newPlayer.getIn(path)).toBe(initPlayer.getIn(path)); 118 | }); 119 | changedPaths.forEach(function(path) { 120 | expect(newPlayer.getIn(path)).not.toBe(initPlayer.getIn(path)); 121 | }); 122 | expect(newPlayer.mergeDeep(newTest)).toBe(newPlayer); 123 | expect(newPlayer.get('name')).toBeUndefined(); 124 | done(); 125 | } 126 | }); 127 | stopAfter(comp); 128 | } 129 | }); 130 | }); 131 | 132 | }); -------------------------------------------------------------------------------- /meteor/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VELOCITY_TEST_PACKAGES=1 meteor test-packages --driver-package velocity:html-reporter mindfront:immutable-observer 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-immutable-observer", 3 | "version": "0.0.1", 4 | "description": "Creates and auto-updates Immutable.js collections from Meteor Collection cursors", 5 | "main": "lib/index", 6 | "scripts": { 7 | "build": "./scripts/build.sh", 8 | "prepublish": "npm run build" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mindfront/meteor-immutable-observer" 13 | }, 14 | "keywords": [ 15 | "meteor", 16 | "immutable", 17 | "immutablejs", 18 | "observer", 19 | "collection", 20 | "mongo" 21 | ], 22 | "author": "Andy Edwards", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/mindfront/meteor-immutable-observer/issues" 26 | }, 27 | "homepage": "https://github.com/mindfront/meteor-immutable-observer", 28 | "peerDependencies": { 29 | "immutable": "^3.7.5" 30 | }, 31 | "devDependencies": { 32 | "babel": "^5.8.23", 33 | "babel-core": "^5.8.23", 34 | "babel-loader": "^5.3.2", 35 | "immutable": "^3.7.5", 36 | "webpack": "^1.12.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | babel=node_modules/.bin/babel 3 | webpack=node_modules/.bin/webpack 4 | build_dir=lib 5 | meteor_package_dir=meteor/packages/immutable-observer 6 | 7 | rm -rf $build_dir 8 | 9 | $babel ./src -d $build_dir --ignore "__tests__" --loose all 10 | 11 | NODE_ENV=production $webpack src/index.js $build_dir/umd/meteor-immutable-observer.js 12 | NODE_ENV=production $webpack -p src/index.js $build_dir/umd/meteor-immutable-observer.min.js 13 | NODE_ENV=production $webpack src/setGlobalImmutableObserver.js $meteor_package_dir/global-meteor-immutable-observer.js 14 | 15 | echo "gzipped, the global build is `gzip -c $build_dir/umd/meteor-immutable-observer.min.js | wc -c | sed -e 's/^[[:space:]]*//'` bytes" 16 | -------------------------------------------------------------------------------- /src/ImmutableListObserver.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | 3 | import updateDeep from './updateDeep'; 4 | 5 | export default function ImmutableListObserver(cursor) { 6 | let documents; 7 | let dep = new Tracker.Dependency(); 8 | 9 | function update(newDocuments) { 10 | documents = newDocuments; 11 | dep.changed(); 12 | } 13 | 14 | let initialDocuments = []; 15 | let handle = cursor.observe({ 16 | addedAt: (document, atIndex, before) => { 17 | if (initialDocuments) { 18 | initialDocuments.splice(atIndex, 0, Immutable.fromJS(document)); 19 | } 20 | else { 21 | update(documents.splice(atIndex, 0, Immutable.fromJS(document))); 22 | } 23 | }, 24 | changedAt: (newDocument, oldDocument, atIndex) => { 25 | update(documents.update(id, document => updateDeep(document, Immutable.fromJS(newDocument)))); 26 | }, 27 | removedAt: (oldDocument, atIndex) => { 28 | update(documents.splice(atIndex, 1)); 29 | }, 30 | movedTo: (document, fromIndex, toIndex, before) => { 31 | var movedDocument = documents.get(fromIndex); 32 | update(documents.splice(fromIndex, 1).splice(toIndex, 0, movedDocument)); 33 | }, 34 | }); 35 | documents = Immutable.List(initialDocuments); 36 | initialDocuments = undefined; 37 | 38 | if (Tracker.active) { 39 | Tracker.onInvalidate(() => { 40 | handle.stop(); 41 | }); 42 | } 43 | 44 | return { 45 | documents() { 46 | dep.depend(); 47 | return documents; 48 | }, 49 | stop() { 50 | handle.stop(); 51 | } 52 | }; 53 | } -------------------------------------------------------------------------------- /src/ImmutableMapObserver.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | 3 | import updateDeep from './updateDeep'; 4 | 5 | function mergeChanges(document, fields) { 6 | return document.withMutations(document => { 7 | for (var key in fields) { 8 | if (fields.hasOwnProperty(key)) { 9 | var newValue = fields[key]; 10 | if (newValue === undefined) { 11 | document.delete(key); 12 | } 13 | else { 14 | document.update(key, oldValue => updateDeep(oldValue, Immutable.fromJS(newValue))); 15 | } 16 | } 17 | } 18 | }); 19 | } 20 | 21 | export default function ImmutableMapObserver(cursor) { 22 | let documents; 23 | let dep = new Tracker.Dependency(); 24 | 25 | function update(newDocuments) { 26 | documents = newDocuments; 27 | dep.changed(); 28 | } 29 | 30 | let initialDocuments = {}; 31 | let handle = cursor.observeChanges({ 32 | added: (id, fields) => { 33 | fields._id = id; 34 | if (initialDocuments) { 35 | initialDocuments[id] = Immutable.fromJS(fields); 36 | } 37 | else { 38 | update(documents.set(id, Immutable.fromJS(fields))); 39 | } 40 | }, 41 | changed: (id, fields) => { 42 | update(documents.update(id, document => mergeChanges(document, fields))); 43 | }, 44 | removed: (id) => { 45 | update(documents.delete(id)); 46 | }, 47 | }); 48 | documents = Immutable.Map(initialDocuments); 49 | initialDocuments = undefined; 50 | 51 | if (Tracker.active) { 52 | Tracker.onInvalidate(() => { 53 | handle.stop(); 54 | }); 55 | } 56 | 57 | return { 58 | documents() { 59 | dep.depend(); 60 | return documents; 61 | }, 62 | stop() { 63 | handle.stop(); 64 | } 65 | }; 66 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Map from './ImmutableMapObserver'; 2 | import List from './ImmutableListObserver'; 3 | 4 | export default { 5 | Map, 6 | List, 7 | }; 8 | -------------------------------------------------------------------------------- /src/setGlobalImmutableObserver.js: -------------------------------------------------------------------------------- 1 | var global = (function() { return this; })(); 2 | global.ImmutableObserver = require('./index'); 3 | -------------------------------------------------------------------------------- /src/updateDeep.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | 3 | export default function updateDeep(a, b) { 4 | if (!(a instanceof Immutable.Collection) || 5 | !(b instanceof Immutable.Collection) || 6 | Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { 7 | return a === b ? a : b; 8 | } 9 | return a.withMutations(result => { 10 | a.forEach((oldValue, key) => { 11 | if (!b.has(key)) { 12 | result.delete(key); 13 | } 14 | }); 15 | b.forEach((newValue, key) => { 16 | if (!a.has(key)) { 17 | result.set(key, newValue); 18 | } 19 | else { 20 | result.set(key, updateDeep(a.get(key), newValue)); 21 | } 22 | }); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | 5 | output: { 6 | library: 'meteor-immutable-observer', 7 | libraryTarget: 'umd' 8 | }, 9 | 10 | externals: [ 11 | { 12 | "immutable": { 13 | root: "Immutable", 14 | commonjs2: "immutable", 15 | commonjs: "immutable", 16 | amd: "immutable" 17 | } 18 | } 19 | ], 20 | 21 | module: { 22 | loaders: [ 23 | { test: /\.js$/, exclude: /node_modules|setGlobalImmutableObserver\.js/, loader: 'babel?loose=all' } 24 | ] 25 | }, 26 | 27 | plugins: [ 28 | new webpack.DefinePlugin({ 29 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 30 | }) 31 | ] 32 | 33 | }; 34 | --------------------------------------------------------------------------------