Comparing version 0.7.0 to 0.8.0
# Changelog | ||
### 0.8.0 | ||
* The built is now being rolled up [#64](https://github.com/mweststrate/immer/pull/64) by [Arthur Denner](https://github.com/arthurdenner). A minified gzipped built is only 2kb! | ||
* There are no longer separate builds available for the proxy and es5 implementation. The sources where merged to allow for more code reuse. | ||
* The package now exposes an ES module as well. | ||
### 0.7.0 | ||
@@ -4,0 +10,0 @@ |
@@ -1,47 +0,62 @@ | ||
"use strict"; | ||
// @ts-check | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { | ||
return typeof obj; | ||
} : function (obj) { | ||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; | ||
}; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
var PROXY_STATE = Symbol("immer-proxy-state"); // TODO: create per closure, to avoid sharing proxies between multiple immer version | ||
var autoFreeze = true; | ||
exports.default = produce; | ||
exports.setAutoFreeze = setAutoFreeze; | ||
function isProxy(value) { | ||
return !!value && !!value[PROXY_STATE]; | ||
} | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function isProxyable(value) { | ||
if (!value) return false; | ||
if ((typeof value === "undefined" ? "undefined" : _typeof(value)) !== "object") return false; | ||
if (Array.isArray(value)) return true; | ||
var proto = Object.getPrototypeOf(value); | ||
return proto === null || proto === Object.prototype; | ||
} | ||
if (typeof Proxy === "undefined") throw new Error("Immer requires `Proxy` to be available, but it seems to be not available on your platform. Consider requiring immer '\"immer/es5\"' instead."); | ||
function freeze(value) { | ||
if (autoFreeze) { | ||
Object.freeze(value); | ||
} | ||
return value; | ||
} | ||
var PROXY_STATE = Symbol("immer-proxy-state"); | ||
var autoFreeze = true; | ||
/** | ||
* Automatically freezes any state trees generated by immer. | ||
* This protects against accidental modifications of the state tree outside of an immer function. | ||
* This comes with a performance impact, so it is recommended to disable this option in production. | ||
* It is by default enabled. | ||
* | ||
* @returns {void} | ||
*/ | ||
function setAutoFreeze(enableAutoFreeze) { | ||
autoFreeze = enableAutoFreeze; | ||
} | ||
// @ts-check | ||
var revocableProxies = null; | ||
var objectTraps = { | ||
get: function get(target, prop) { | ||
if (prop === PROXY_STATE) return target; | ||
return target.get(prop); | ||
}, | ||
get: get$1, | ||
has: function has(target, prop) { | ||
return prop in target.source; | ||
return prop in source(target); | ||
}, | ||
ownKeys: function ownKeys(target) { | ||
return Reflect.ownKeys(target.source); | ||
return Reflect.ownKeys(source(target)); | ||
}, | ||
set: function set(target, prop, value) { | ||
target.set(prop, value); | ||
return true; | ||
}, | ||
deleteProperty: function deleteProperty(target, prop) { | ||
target.deleteProp(prop); | ||
return true; | ||
}, | ||
getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) { | ||
return target.getOwnPropertyDescriptor(prop); | ||
}, | ||
defineProperty: function defineProperty(target, property, descriptor) { | ||
target.defineProperty(property, descriptor); | ||
}, | ||
set: set$1, | ||
deleteProperty: deleteProperty, | ||
getOwnPropertyDescriptor: getOwnPropertyDescriptor, | ||
defineProperty: defineProperty$1, | ||
setPrototypeOf: function setPrototypeOf() { | ||
@@ -52,32 +67,381 @@ throw new Error("Don't even try this..."); | ||
var arrayTraps = { | ||
get: function get(target, prop) { | ||
if (prop === PROXY_STATE) return target[0]; | ||
return target[0].get(prop); | ||
}, | ||
has: function has(target, prop) { | ||
return prop === PROXY_STATE || prop in target[0].source; | ||
}, | ||
ownKeys: function ownKeys(target) { | ||
return Reflect.ownKeys(target[0].source); | ||
}, | ||
set: function set(target, prop, value) { | ||
target[0].set(prop, value); | ||
return true; | ||
}, | ||
deleteProperty: function deleteProperty(target, prop) { | ||
target[0].deleteProp(prop); | ||
return true; | ||
}, | ||
getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) { | ||
return target[0].getOwnPropertyDescriptor(prop); | ||
}, | ||
defineProperty: function defineProperty(target, property, descriptor) { | ||
target[0].defineProperty(property, descriptor); | ||
}, | ||
setPrototypeOf: function setPrototypeOf() { | ||
throw new Error("Don't even try this..."); | ||
} | ||
var arrayTraps = {}; | ||
var _loop = function _loop(key) { | ||
arrayTraps[key] = function () { | ||
arguments[0] = arguments[0][0]; | ||
return objectTraps[key].apply(this, arguments); | ||
}; | ||
}; | ||
for (var key in objectTraps) { | ||
_loop(key); | ||
} | ||
function createState(parent, base) { | ||
return { | ||
modified: false, | ||
finalized: false, | ||
parent: parent, | ||
base: base, | ||
copy: undefined, | ||
proxies: {} | ||
}; | ||
} | ||
function source(state) { | ||
return state.modified === true ? state.copy : state.base; | ||
} | ||
function get$1(state, prop) { | ||
if (prop === PROXY_STATE) return state; | ||
if (state.modified) { | ||
var value = state.copy[prop]; | ||
if (!isProxy(value) && isProxyable(value)) return state.copy[prop] = createProxy(state, value); | ||
return value; | ||
} else { | ||
if (prop in state.proxies) return state.proxies[prop]; | ||
var _value = state.base[prop]; | ||
if (!isProxy(_value) && isProxyable(_value)) return state.proxies[prop] = createProxy(state, _value); | ||
return _value; | ||
} | ||
} | ||
function set$1(state, prop, value) { | ||
if (!state.modified) { | ||
if (prop in state.base && Object.is(state.base[prop], value) || prop in state.proxies && state.proxies[prop] === value) return true; | ||
markChanged(state); | ||
} | ||
state.copy[prop] = value; | ||
return true; | ||
} | ||
function deleteProperty(state, prop) { | ||
markChanged(state); | ||
delete state.copy[prop]; | ||
return true; | ||
} | ||
function getOwnPropertyDescriptor(state, prop) { | ||
var owner = state.modified ? state.copy : prop in state.proxies ? state.proxies : state.base; | ||
var descriptor = Reflect.getOwnPropertyDescriptor(owner, prop); | ||
if (descriptor && !(Array.isArray(owner) && prop === "length")) descriptor.configurable = true; | ||
return descriptor; | ||
} | ||
function defineProperty$1() { | ||
throw new Error("Immer does currently not support defining properties on draft objects"); | ||
} | ||
function markChanged(state) { | ||
if (!state.modified) { | ||
state.modified = true; | ||
state.copy = Array.isArray(state.base) ? state.base.slice() : Object.assign({}, state.base); // TODO: eliminate those isArray checks? | ||
Object.assign(state.copy, state.proxies); // yup that works for arrays as well | ||
if (state.parent) markChanged(state.parent); | ||
} | ||
} | ||
// creates a proxy for plain objects / arrays | ||
function createProxy(parentState, base) { | ||
var state = createState(parentState, base); | ||
var proxy = void 0; | ||
if (Array.isArray(base)) { | ||
proxy = Proxy.revocable([state], arrayTraps); | ||
} else { | ||
proxy = Proxy.revocable(state, objectTraps); | ||
} | ||
revocableProxies.push(proxy); | ||
return proxy.proxy; | ||
} | ||
// given a base object, returns it if unmodified, or return the changed cloned if modified | ||
function finalize(base) { | ||
if (isProxy(base)) { | ||
var state = base[PROXY_STATE]; | ||
if (state.modified === true) { | ||
if (state.finalized === true) return state.copy; | ||
state.finalized = true; | ||
if (Array.isArray(state.base)) return finalizeArray(state); | ||
return finalizeObject(state); | ||
} else return state.base; | ||
} else if (base !== null && (typeof base === "undefined" ? "undefined" : _typeof(base)) === "object") { | ||
// If finalize is called on an object that was not a proxy, it means that it is an object that was not there in the original | ||
// tree and it could contain proxies at arbitrarily places. Let's find and finalize them as well | ||
// TODO: optimize this; walk the tree without writing to find proxies | ||
if (Array.isArray(base)) { | ||
for (var i = 0; i < base.length; i++) { | ||
base[i] = finalize(base[i]); | ||
}return freeze(base); | ||
} | ||
var proto = Object.getPrototypeOf(base); | ||
if (proto === null || proto === Object.prototype) { | ||
for (var key in base) { | ||
base[key] = finalize(base[key]); | ||
}return freeze(base); | ||
} | ||
} | ||
return base; | ||
} | ||
function finalizeObject(state) { | ||
var copy = state.copy; | ||
var base = state.base; | ||
for (var prop in copy) { | ||
if (copy[prop] !== base[prop]) copy[prop] = finalize(copy[prop]); | ||
} | ||
return freeze(copy); | ||
} | ||
function finalizeArray(state) { | ||
var copy = state.copy; | ||
var base = state.base; | ||
for (var i = 0; i < copy.length; i++) { | ||
if (copy[i] !== base[i]) copy[i] = finalize(copy[i]); | ||
} | ||
return freeze(copy); | ||
} | ||
function produceProxy(baseState, producer) { | ||
var previousProxies = revocableProxies; | ||
revocableProxies = []; | ||
try { | ||
// create proxy for root | ||
var rootClone = createProxy(undefined, baseState); | ||
// execute the thunk | ||
var maybeVoidReturn = producer(rootClone); | ||
//values either than undefined will trigger warning; | ||
!Object.is(maybeVoidReturn, undefined) && console.warn("Immer callback expects no return value. However " + (typeof maybeVoidReturn === "undefined" ? "undefined" : _typeof(maybeVoidReturn)) + " was returned"); | ||
// and finalize the modified proxy | ||
var res = finalize(rootClone); | ||
// revoke all proxies | ||
revocableProxies.forEach(function (p) { | ||
return p.revoke(); | ||
}); | ||
return res; | ||
} finally { | ||
revocableProxies = previousProxies; | ||
} | ||
} | ||
// @ts-check | ||
var descriptors = {}; | ||
var states = null; | ||
function createState$1(parent, proxy, base) { | ||
return { | ||
modified: false, | ||
hasCopy: false, | ||
parent: parent, | ||
base: base, | ||
proxy: proxy, | ||
copy: undefined, | ||
finished: false, | ||
finalizing: false | ||
}; | ||
} | ||
function source$1(state) { | ||
return state.hasCopy ? state.copy : state.base; | ||
} | ||
function _get(state, prop) { | ||
assertUnfinished(state); | ||
var value = source$1(state)[prop]; | ||
if (!state.finalizing && !isProxy(value) && isProxyable(value)) { | ||
prepareCopy(state); | ||
return state.copy[prop] = createProxy$1(state, value); | ||
} | ||
return value; | ||
} | ||
function _set(state, prop, value) { | ||
assertUnfinished(state); | ||
if (!state.modified) { | ||
if (Object.is(source$1(state)[prop], value)) return; | ||
markChanged$1(state); | ||
} | ||
prepareCopy(state); | ||
state.copy[prop] = value; | ||
} | ||
function markChanged$1(state) { | ||
if (!state.modified) { | ||
state.modified = true; | ||
if (state.parent) markChanged$1(state.parent); | ||
} | ||
} | ||
function prepareCopy(state) { | ||
if (state.hasCopy) return; | ||
state.hasCopy = true; | ||
state.copy = Array.isArray(state.base) ? state.base.slice() : Object.assign({}, state.base); | ||
} | ||
// creates a proxy for plain objects / arrays | ||
function createProxy$1(parent, base) { | ||
var proxy = void 0; | ||
if (Array.isArray(base)) proxy = createArrayProxy(base);else proxy = createObjectProxy(base); | ||
var state = createState$1(parent, proxy, base); | ||
createHiddenProperty(proxy, PROXY_STATE, state); | ||
states.push(state); | ||
return proxy; | ||
} | ||
function createPropertyProxy(prop) { | ||
return descriptors[prop] || (descriptors[prop] = { | ||
configurable: true, | ||
enumerable: true, | ||
get: function get$$1() { | ||
return _get(this[PROXY_STATE], prop); | ||
}, | ||
set: function set$$1(value) { | ||
_set(this[PROXY_STATE], prop, value); | ||
} | ||
}); | ||
} | ||
function createObjectProxy(base) { | ||
var proxy = Object.assign({}, base); | ||
Object.keys(base).forEach(function (prop) { | ||
return Object.defineProperty(proxy, prop, createPropertyProxy(prop)); | ||
}); | ||
return proxy; | ||
} | ||
function createArrayProxy(base) { | ||
var proxy = new Array(base.length); | ||
for (var i = 0; i < base.length; i++) { | ||
Object.defineProperty(proxy, "" + i, createPropertyProxy("" + i)); | ||
}return proxy; | ||
} | ||
function assertUnfinished(state) { | ||
if (state.finished === true) throw new Error("Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process?"); | ||
} | ||
// this sounds very expensive, but actually it is not that extensive in practice | ||
// as it will only visit proxies, and only do key-based change detection for objects for | ||
// which it is not already know that they are changed (that is, only object for which no known key was changed) | ||
function markChanges() { | ||
// intentionally we process the proxies in reverse order; | ||
// ideally we start by processing leafs in the tree, because if a child has changed, we don't have to check the parent anymore | ||
// reverse order of proxy creation approximates this | ||
for (var i = states.length - 1; i >= 0; i--) { | ||
var state = states[i]; | ||
if (state.modified === false) { | ||
if (Array.isArray(state.base)) { | ||
if (hasArrayChanges(state)) markChanged$1(state); | ||
} else if (hasObjectChanges(state)) markChanged$1(state); | ||
} | ||
} | ||
} | ||
function hasObjectChanges(state) { | ||
var baseKeys = Object.keys(state.base); | ||
var keys = Object.keys(state.proxy); | ||
return !shallowEqual(baseKeys, keys); | ||
} | ||
function hasArrayChanges(state) { | ||
return state.proxy.length !== state.base.length; | ||
} | ||
function finalize$1(proxy) { | ||
// TODO: almost litterally same as Proxy impl; let's reduce code duplication and rollup | ||
if (isProxy(proxy)) { | ||
var state = proxy[PROXY_STATE]; | ||
if (state.modified === true) { | ||
if (Array.isArray(state.base)) return finalizeArray$1(proxy, state); | ||
return finalizeObject$1(proxy, state); | ||
} else return state.base; | ||
} else if (proxy !== null && (typeof proxy === "undefined" ? "undefined" : _typeof(proxy)) === "object") { | ||
// If finalize is called on an object that was not a proxy, it means that it is an object that was not there in the original | ||
// tree and it could contain proxies at arbitrarily places. Let's find and finalize them as well | ||
if (Array.isArray(proxy)) { | ||
for (var i = 0; i < proxy.length; i++) { | ||
proxy[i] = finalize$1(proxy[i]); | ||
}return freeze(proxy); | ||
} | ||
var proto = Object.getPrototypeOf(proxy); | ||
if (proto === null || proto === Object.prototype) { | ||
for (var key in proxy) { | ||
proxy[key] = finalize$1(proxy[key]); | ||
}return freeze(proxy); | ||
} | ||
} | ||
return proxy; | ||
} | ||
function finalizeObject$1(proxy, state) { | ||
var res = Object.assign({}, proxy); | ||
var base = state.base; | ||
for (var prop in res) { | ||
if (proxy[prop] !== base[prop]) res[prop] = finalize$1(res[prop]); | ||
} | ||
return freeze(res); | ||
} | ||
function finalizeArray$1(proxy, state) { | ||
var res = proxy.slice(); | ||
var base = state.base; | ||
for (var i = 0; i < res.length; i++) { | ||
if (res[i] !== base[i]) res[i] = finalize$1(res[i]); | ||
} | ||
return freeze(res); | ||
} | ||
function produceEs5(baseState, producer) { | ||
var prevStates = states; | ||
states = []; | ||
try { | ||
// create proxy for root | ||
var rootClone = createProxy$1(undefined, baseState); | ||
// execute the thunk | ||
var maybeVoidReturn = producer(rootClone); | ||
//values either than undefined will trigger warning; | ||
!Object.is(maybeVoidReturn, undefined) && console.warn("Immer callback expects no return value. However " + (typeof maybeVoidReturn === "undefined" ? "undefined" : _typeof(maybeVoidReturn)) + " was returned"); | ||
// and finalize the modified proxy | ||
for (var i = 0; i < states.length; i++) { | ||
states[i].finalizing = true; | ||
} // find and mark all changes (for parts not done yet) | ||
markChanges(); | ||
var res = finalize$1(rootClone); | ||
// make sure all proxies become unusable | ||
for (var _i = 0; _i < states.length; _i++) { | ||
states[_i].finished = true; | ||
}return res; | ||
} finally { | ||
states = prevStates; | ||
} | ||
} | ||
function shallowEqual(objA, objB) { | ||
//From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js | ||
if (Object.is(objA, objB)) return true; | ||
if ((typeof objA === "undefined" ? "undefined" : _typeof(objA)) !== "object" || objA === null || (typeof objB === "undefined" ? "undefined" : _typeof(objB)) !== "object" || objB === null) { | ||
return false; | ||
} | ||
var keysA = Object.keys(objA); | ||
var keysB = Object.keys(objB); | ||
if (keysA.length !== keysB.length) return false; | ||
for (var i = 0; i < keysA.length; i++) { | ||
if (!hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function createHiddenProperty(target, prop, value) { | ||
Object.defineProperty(target, prop, { | ||
value: value, | ||
enumerable: false, | ||
writable: true | ||
}); | ||
} | ||
var useProxies = typeof Proxy !== "undefined"; | ||
/** | ||
@@ -114,188 +478,13 @@ * produce takes a state, and runs a function against it. | ||
} | ||
var revocableProxies = []; | ||
var State = function () { | ||
function State(parent, base) { | ||
_classCallCheck(this, State); | ||
this.modified = false; | ||
this.finalized = false; | ||
this.parent = parent; | ||
this.base = base; | ||
this.copy = undefined; | ||
this.proxies = {}; | ||
} | ||
_createClass(State, [{ | ||
key: "get", | ||
value: function get(prop) { | ||
if (this.modified) { | ||
var value = this.copy[prop]; | ||
if (!isProxy(value) && isProxyable(value)) return this.copy[prop] = createProxy(this, value); | ||
return value; | ||
} else { | ||
if (prop in this.proxies) return this.proxies[prop]; | ||
var _value = this.base[prop]; | ||
if (!isProxy(_value) && isProxyable(_value)) return this.proxies[prop] = createProxy(this, _value); | ||
return _value; | ||
} | ||
} | ||
}, { | ||
key: "set", | ||
value: function set(prop, value) { | ||
if (!this.modified) { | ||
if (prop in this.base && Object.is(this.base[prop], value) || prop in this.proxies && this.proxies[prop] === value) return; | ||
this.markChanged(); | ||
} | ||
this.copy[prop] = value; | ||
} | ||
}, { | ||
key: "deleteProp", | ||
value: function deleteProp(prop) { | ||
this.markChanged(); | ||
delete this.copy[prop]; | ||
} | ||
}, { | ||
key: "getOwnPropertyDescriptor", | ||
value: function getOwnPropertyDescriptor(prop) { | ||
var owner = this.modified ? this.copy : prop in this.proxies ? this.proxies : this.base; | ||
var descriptor = Reflect.getOwnPropertyDescriptor(owner, prop); | ||
if (descriptor && !(Array.isArray(owner) && prop === "length")) descriptor.configurable = true; | ||
return descriptor; | ||
} | ||
}, { | ||
key: "defineProperty", | ||
value: function defineProperty(property, descriptor) { | ||
throw new Error("Immer does currently not support defining properties on draft objects"); | ||
} | ||
}, { | ||
key: "markChanged", | ||
value: function markChanged() { | ||
if (!this.modified) { | ||
this.modified = true; | ||
this.copy = Array.isArray(this.base) ? this.base.slice() : Object.assign({}, this.base); // TODO: eliminate those isArray checks? | ||
Object.assign(this.copy, this.proxies); // yup that works for arrays as well | ||
if (this.parent) this.parent.markChanged(); | ||
} | ||
} | ||
}, { | ||
key: "source", | ||
get: function get() { | ||
return this.modified === true ? this.copy : this.base; | ||
} | ||
}]); | ||
return State; | ||
}(); | ||
// creates a proxy for plain objects / arrays | ||
function createProxy(parentState, base) { | ||
var state = new State(parentState, base); | ||
var proxy = void 0; | ||
if (Array.isArray(base)) { | ||
proxy = Proxy.revocable([state], arrayTraps); | ||
} else { | ||
proxy = Proxy.revocable(state, objectTraps); | ||
} | ||
revocableProxies.push(proxy); | ||
return proxy.proxy; | ||
} | ||
// given a base object, returns it if unmodified, or return the changed cloned if modified | ||
function finalize(base) { | ||
if (isProxy(base)) { | ||
var state = base[PROXY_STATE]; | ||
if (state.modified === true) { | ||
if (state.finalized === true) return state.copy; | ||
state.finalized = true; | ||
if (Array.isArray(state.base)) return finalizeArray(state); | ||
return finalizeObject(state); | ||
} else return state.base; | ||
} else if (base !== null && (typeof base === "undefined" ? "undefined" : _typeof(base)) === "object") { | ||
// If finalize is called on an object that was not a proxy, it means that it is an object that was not there in the original | ||
// tree and it could contain proxies at arbitrarily places. Let's find and finalize them as well | ||
// TODO: optimize this; walk the tree without writing to find proxies | ||
if (Array.isArray(base)) { | ||
for (var i = 0; i < base.length; i++) { | ||
base[i] = finalize(base[i]); | ||
}return freeze(base); | ||
} | ||
var proto = Object.getPrototypeOf(base); | ||
if (proto === null || proto === Object.prototype) { | ||
for (var key in base) { | ||
base[key] = finalize(base[key]); | ||
}return freeze(base); | ||
} | ||
} | ||
return base; | ||
} | ||
function finalizeObject(state) { | ||
var copy = state.copy; | ||
var base = state.base; | ||
for (var prop in copy) { | ||
if (copy[prop] !== base[prop]) copy[prop] = finalize(copy[prop]); | ||
} | ||
return freeze(copy); | ||
} | ||
function finalizeArray(state) { | ||
var copy = state.copy; | ||
var base = state.base; | ||
for (var i = 0; i < copy.length; i++) { | ||
if (copy[i] !== base[i]) copy[i] = finalize(copy[i]); | ||
} | ||
return freeze(copy); | ||
} | ||
// create proxy for root | ||
var rootClone = createProxy(undefined, baseState); | ||
// execute the thunk | ||
var maybeVoidReturn = producer(rootClone); | ||
//values either than undefined will trigger warning; | ||
!Object.is(maybeVoidReturn, undefined) && console.warn("Immer callback expects no return value. However " + (typeof maybeVoidReturn === "undefined" ? "undefined" : _typeof(maybeVoidReturn)) + " was returned"); | ||
// and finalize the modified proxy | ||
var res = finalize(rootClone); | ||
// revoke all proxies | ||
revocableProxies.forEach(function (p) { | ||
return p.revoke(); | ||
}); | ||
return res; | ||
return useProxies ? produceProxy(baseState, producer) : produceEs5(baseState, producer); | ||
} | ||
function isProxy(value) { | ||
return !!value && !!value[PROXY_STATE]; | ||
function setUseProxies(value) { | ||
useProxies = value; | ||
} | ||
function isProxyable(value) { | ||
if (!value) return false; | ||
if ((typeof value === "undefined" ? "undefined" : _typeof(value)) !== "object") return false; | ||
if (Array.isArray(value)) return true; | ||
var proto = Object.getPrototypeOf(value); | ||
return proto === null || proto === Object.prototype; | ||
} | ||
function freeze(value) { | ||
// short circuit to achieve 100% code coverage instead of 98% | ||
/* | ||
if(autoFreeze) { | ||
Object.freeze(value) | ||
} | ||
* */ | ||
autoFreeze && Object.freeze(value); | ||
return value; | ||
} | ||
/** | ||
* Automatically freezes any state trees generated by immer. | ||
* This protects against accidental modifications of the state tree outside of an immer function. | ||
* This comes with a performance impact, so it is recommended to disable this option in production. | ||
* It is by default enabled. | ||
* | ||
* @returns {void} | ||
*/ | ||
function setAutoFreeze(enableAutoFreeze) { | ||
autoFreeze = enableAutoFreeze; | ||
} | ||
exports['default'] = produce; | ||
exports.setUseProxies = setUseProxies; | ||
exports.setAutoFreeze = setAutoFreeze; | ||
//# sourceMappingURL=immer.js.map |
@@ -1,302 +0,2 @@ | ||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.immer = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
"use strict"; | ||
// @ts-check | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
exports.default = produce; | ||
exports.setAutoFreeze = setAutoFreeze; | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
if (typeof Proxy === "undefined") throw new Error("Immer requires `Proxy` to be available, but it seems to be not available on your platform. Consider requiring immer '\"immer/es5\"' instead."); | ||
var PROXY_STATE = Symbol("immer-proxy-state"); | ||
var autoFreeze = true; | ||
var objectTraps = { | ||
get: function get(target, prop) { | ||
if (prop === PROXY_STATE) return target; | ||
return target.get(prop); | ||
}, | ||
has: function has(target, prop) { | ||
return prop in target.source; | ||
}, | ||
ownKeys: function ownKeys(target) { | ||
return Reflect.ownKeys(target.source); | ||
}, | ||
set: function set(target, prop, value) { | ||
target.set(prop, value); | ||
return true; | ||
}, | ||
deleteProperty: function deleteProperty(target, prop) { | ||
target.deleteProp(prop); | ||
return true; | ||
}, | ||
getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) { | ||
return target.getOwnPropertyDescriptor(prop); | ||
}, | ||
defineProperty: function defineProperty(target, property, descriptor) { | ||
target.defineProperty(property, descriptor); | ||
}, | ||
setPrototypeOf: function setPrototypeOf() { | ||
throw new Error("Don't even try this..."); | ||
} | ||
}; | ||
var arrayTraps = { | ||
get: function get(target, prop) { | ||
if (prop === PROXY_STATE) return target[0]; | ||
return target[0].get(prop); | ||
}, | ||
has: function has(target, prop) { | ||
return prop === PROXY_STATE || prop in target[0].source; | ||
}, | ||
ownKeys: function ownKeys(target) { | ||
return Reflect.ownKeys(target[0].source); | ||
}, | ||
set: function set(target, prop, value) { | ||
target[0].set(prop, value); | ||
return true; | ||
}, | ||
deleteProperty: function deleteProperty(target, prop) { | ||
target[0].deleteProp(prop); | ||
return true; | ||
}, | ||
getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) { | ||
return target[0].getOwnPropertyDescriptor(prop); | ||
}, | ||
defineProperty: function defineProperty(target, property, descriptor) { | ||
target[0].defineProperty(property, descriptor); | ||
}, | ||
setPrototypeOf: function setPrototypeOf() { | ||
throw new Error("Don't even try this..."); | ||
} | ||
}; | ||
/** | ||
* produce takes a state, and runs a function against it. | ||
* That function can freely mutate the state, as it will create copies-on-write. | ||
* This means that the original state will stay unchanged, and once the function finishes, the modified state is returned | ||
* | ||
* @export | ||
* @param {any} baseState - the state to start with | ||
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified | ||
* @returns {any} a new state, or the base state if nothing was modified | ||
*/ | ||
function produce(baseState, producer) { | ||
// curried invocation | ||
if (arguments.length === 1) { | ||
var _producer = baseState; | ||
// prettier-ignore | ||
if (typeof _producer !== "function") throw new Error("if produce is called with 1 argument, the first argument should be a function"); | ||
return function () { | ||
var args = arguments; | ||
return produce(args[0], function (draft) { | ||
args[0] = draft; // blegh! | ||
baseState.apply(null, args); | ||
}); | ||
}; | ||
} | ||
// prettier-ignore | ||
{ | ||
if (arguments.length !== 2) throw new Error("produce expects 1 or 2 arguments, got " + arguments.length); | ||
if (!isProxyable(baseState)) throw new Error("the first argument to produce should be a plain object or array, got " + (typeof baseState === "undefined" ? "undefined" : _typeof(baseState))); | ||
if (typeof producer !== "function") throw new Error("the second argument to produce should be a function"); | ||
} | ||
var revocableProxies = []; | ||
var State = function () { | ||
function State(parent, base) { | ||
_classCallCheck(this, State); | ||
this.modified = false; | ||
this.finalized = false; | ||
this.parent = parent; | ||
this.base = base; | ||
this.copy = undefined; | ||
this.proxies = {}; | ||
} | ||
_createClass(State, [{ | ||
key: "get", | ||
value: function get(prop) { | ||
if (this.modified) { | ||
var value = this.copy[prop]; | ||
if (!isProxy(value) && isProxyable(value)) return this.copy[prop] = createProxy(this, value); | ||
return value; | ||
} else { | ||
if (prop in this.proxies) return this.proxies[prop]; | ||
var _value = this.base[prop]; | ||
if (!isProxy(_value) && isProxyable(_value)) return this.proxies[prop] = createProxy(this, _value); | ||
return _value; | ||
} | ||
} | ||
}, { | ||
key: "set", | ||
value: function set(prop, value) { | ||
if (!this.modified) { | ||
if (prop in this.base && Object.is(this.base[prop], value) || prop in this.proxies && this.proxies[prop] === value) return; | ||
this.markChanged(); | ||
} | ||
this.copy[prop] = value; | ||
} | ||
}, { | ||
key: "deleteProp", | ||
value: function deleteProp(prop) { | ||
this.markChanged(); | ||
delete this.copy[prop]; | ||
} | ||
}, { | ||
key: "getOwnPropertyDescriptor", | ||
value: function getOwnPropertyDescriptor(prop) { | ||
var owner = this.modified ? this.copy : prop in this.proxies ? this.proxies : this.base; | ||
var descriptor = Reflect.getOwnPropertyDescriptor(owner, prop); | ||
if (descriptor && !(Array.isArray(owner) && prop === "length")) descriptor.configurable = true; | ||
return descriptor; | ||
} | ||
}, { | ||
key: "defineProperty", | ||
value: function defineProperty(property, descriptor) { | ||
throw new Error("Immer does currently not support defining properties on draft objects"); | ||
} | ||
}, { | ||
key: "markChanged", | ||
value: function markChanged() { | ||
if (!this.modified) { | ||
this.modified = true; | ||
this.copy = Array.isArray(this.base) ? this.base.slice() : Object.assign({}, this.base); // TODO: eliminate those isArray checks? | ||
Object.assign(this.copy, this.proxies); // yup that works for arrays as well | ||
if (this.parent) this.parent.markChanged(); | ||
} | ||
} | ||
}, { | ||
key: "source", | ||
get: function get() { | ||
return this.modified === true ? this.copy : this.base; | ||
} | ||
}]); | ||
return State; | ||
}(); | ||
// creates a proxy for plain objects / arrays | ||
function createProxy(parentState, base) { | ||
var state = new State(parentState, base); | ||
var proxy = void 0; | ||
if (Array.isArray(base)) { | ||
proxy = Proxy.revocable([state], arrayTraps); | ||
} else { | ||
proxy = Proxy.revocable(state, objectTraps); | ||
} | ||
revocableProxies.push(proxy); | ||
return proxy.proxy; | ||
} | ||
// given a base object, returns it if unmodified, or return the changed cloned if modified | ||
function finalize(base) { | ||
if (isProxy(base)) { | ||
var state = base[PROXY_STATE]; | ||
if (state.modified === true) { | ||
if (state.finalized === true) return state.copy; | ||
state.finalized = true; | ||
if (Array.isArray(state.base)) return finalizeArray(state); | ||
return finalizeObject(state); | ||
} else return state.base; | ||
} else if (base !== null && (typeof base === "undefined" ? "undefined" : _typeof(base)) === "object") { | ||
// If finalize is called on an object that was not a proxy, it means that it is an object that was not there in the original | ||
// tree and it could contain proxies at arbitrarily places. Let's find and finalize them as well | ||
// TODO: optimize this; walk the tree without writing to find proxies | ||
if (Array.isArray(base)) { | ||
for (var i = 0; i < base.length; i++) { | ||
base[i] = finalize(base[i]); | ||
}return freeze(base); | ||
} | ||
var proto = Object.getPrototypeOf(base); | ||
if (proto === null || proto === Object.prototype) { | ||
for (var key in base) { | ||
base[key] = finalize(base[key]); | ||
}return freeze(base); | ||
} | ||
} | ||
return base; | ||
} | ||
function finalizeObject(state) { | ||
var copy = state.copy; | ||
var base = state.base; | ||
for (var prop in copy) { | ||
if (copy[prop] !== base[prop]) copy[prop] = finalize(copy[prop]); | ||
} | ||
return freeze(copy); | ||
} | ||
function finalizeArray(state) { | ||
var copy = state.copy; | ||
var base = state.base; | ||
for (var i = 0; i < copy.length; i++) { | ||
if (copy[i] !== base[i]) copy[i] = finalize(copy[i]); | ||
} | ||
return freeze(copy); | ||
} | ||
// create proxy for root | ||
var rootClone = createProxy(undefined, baseState); | ||
// execute the thunk | ||
var maybeVoidReturn = producer(rootClone); | ||
//values either than undefined will trigger warning; | ||
!Object.is(maybeVoidReturn, undefined) && console.warn("Immer callback expects no return value. However " + (typeof maybeVoidReturn === "undefined" ? "undefined" : _typeof(maybeVoidReturn)) + " was returned"); | ||
// and finalize the modified proxy | ||
var res = finalize(rootClone); | ||
// revoke all proxies | ||
revocableProxies.forEach(function (p) { | ||
return p.revoke(); | ||
}); | ||
return res; | ||
} | ||
function isProxy(value) { | ||
return !!value && !!value[PROXY_STATE]; | ||
} | ||
function isProxyable(value) { | ||
if (!value) return false; | ||
if ((typeof value === "undefined" ? "undefined" : _typeof(value)) !== "object") return false; | ||
if (Array.isArray(value)) return true; | ||
var proto = Object.getPrototypeOf(value); | ||
return proto === null || proto === Object.prototype; | ||
} | ||
function freeze(value) { | ||
// short circuit to achieve 100% code coverage instead of 98% | ||
/* | ||
if(autoFreeze) { | ||
Object.freeze(value) | ||
} | ||
* */ | ||
autoFreeze && Object.freeze(value); | ||
return value; | ||
} | ||
/** | ||
* Automatically freezes any state trees generated by immer. | ||
* This protects against accidental modifications of the state tree outside of an immer function. | ||
* This comes with a performance impact, so it is recommended to disable this option in production. | ||
* It is by default enabled. | ||
* | ||
* @returns {void} | ||
*/ | ||
function setAutoFreeze(enableAutoFreeze) { | ||
autoFreeze = enableAutoFreeze; | ||
} | ||
},{}]},{},[1])(1) | ||
}); | ||
var e,r;e=this,r=function(e){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n=Symbol("immer-proxy-state"),t=!0;function o(e){return!!e&&!!e[n]}function i(e){if(!e)return!1;if("object"!==(void 0===e?"undefined":r(e)))return!1;if(Array.isArray(e))return!0;var n=Object.getPrototypeOf(e);return null===n||n===Object.prototype}function f(e){return t&&Object.freeze(e),e}var a=null,u={get:function(e,r){if(r===n)return e;if(e.modified){var t=e.copy[r];return!o(t)&&i(t)?e.copy[r]=l(e,t):t}if(r in e.proxies)return e.proxies[r];var f=e.base[r];return!o(f)&&i(f)?e.proxies[r]=l(e,f):f},has:function(e,r){return r in d(e)},ownKeys:function(e){return Reflect.ownKeys(d(e))},set:function(e,r,n){if(!e.modified){if(r in e.base&&Object.is(e.base[r],n)||r in e.proxies&&e.proxies[r]===n)return!0;p(e)}return e.copy[r]=n,!0},deleteProperty:function(e,r){return p(e),delete e.copy[r],!0},getOwnPropertyDescriptor:function(e,r){var n=e.modified?e.copy:r in e.proxies?e.proxies:e.base,t=Reflect.getOwnPropertyDescriptor(n,r);!t||Array.isArray(n)&&"length"===r||(t.configurable=!0);return t},defineProperty:function(){throw new Error("Immer does currently not support defining properties on draft objects")},setPrototypeOf:function(){throw new Error("Don't even try this...")}},c={},s=function(e){c[e]=function(){return arguments[0]=arguments[0][0],u[e].apply(this,arguments)}};for(var y in u)s(y);function d(e){return!0===e.modified?e.copy:e.base}function p(e){e.modified||(e.modified=!0,e.copy=Array.isArray(e.base)?e.base.slice():Object.assign({},e.base),Object.assign(e.copy,e.proxies),e.parent&&p(e.parent))}function l(e,r){var n={modified:!1,finalized:!1,parent:e,base:r,copy:void 0,proxies:{}},t=void 0;return t=Array.isArray(r)?Proxy.revocable([n],c):Proxy.revocable(n,u),a.push(t),t.proxy}function b(e){if(o(e)){var t=e[n];return!0===t.modified?!0===t.finalized?t.copy:(t.finalized=!0,Array.isArray(t.base)?function(e){for(var r=e.copy,n=e.base,t=0;t<r.length;t++)r[t]!==n[t]&&(r[t]=b(r[t]));return f(r)}(t):function(e){var r=e.copy,n=e.base;for(var t in r)r[t]!==n[t]&&(r[t]=b(r[t]));return f(r)}(t)):t.base}if(null!==e&&"object"===(void 0===e?"undefined":r(e))){if(Array.isArray(e)){for(var i=0;i<e.length;i++)e[i]=b(e[i]);return f(e)}var a=Object.getPrototypeOf(e);if(null===a||a===Object.prototype){for(var u in e)e[u]=b(e[u]);return f(e)}}return e}var v={},h=null;function m(e){return e.hasCopy?e.copy:e.base}function g(e){e.modified||(e.modified=!0,e.parent&&g(e.parent))}function j(e){e.hasCopy||(e.hasCopy=!0,e.copy=Array.isArray(e.base)?e.base.slice():Object.assign({},e.base))}function O(e,r){var t,o,i=void 0;Array.isArray(r)?i=function(e){for(var r=new Array(e.length),n=0;n<e.length;n++)Object.defineProperty(r,""+n,w(""+n));return r}(r):(t=r,o=Object.assign({},t),Object.keys(t).forEach(function(e){return Object.defineProperty(o,e,w(e))}),i=o);var f,a,u,c={modified:!1,hasCopy:!1,parent:e,base:r,proxy:i,copy:void 0,finished:!1,finalizing:!1};return f=i,a=n,u=c,Object.defineProperty(f,a,{value:u,enumerable:!1,writable:!0}),h.push(c),i}function w(e){return v[e]||(v[e]={configurable:!0,enumerable:!0,get:function(){return function(e,r){x(e);var n=m(e)[r];return e.finalizing||o(n)||!i(n)?n:(j(e),e.copy[r]=O(e,n))}(this[n],e)},set:function(r){!function(e,r,n){if(x(e),!e.modified){if(Object.is(m(e)[r],n))return;g(e)}j(e),e.copy[r]=n}(this[n],e,r)}})}function x(e){if(!0===e.finished)throw new Error("Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process?")}function A(e){if(o(e)){var t=e[n];return!0===t.modified?Array.isArray(t.base)?function(e,r){for(var n=e.slice(),t=r.base,o=0;o<n.length;o++)n[o]!==t[o]&&(n[o]=A(n[o]));return f(n)}(e,t):function(e,r){var n=Object.assign({},e),t=r.base;for(var o in n)e[o]!==t[o]&&(n[o]=A(n[o]));return f(n)}(e,t):t.base}if(null!==e&&"object"===(void 0===e?"undefined":r(e))){if(Array.isArray(e)){for(var i=0;i<e.length;i++)e[i]=A(e[i]);return f(e)}var a=Object.getPrototypeOf(e);if(null===a||a===Object.prototype){for(var u in e)e[u]=A(e[u]);return f(e)}}return e}function P(e,n){var t=h;h=[];try{var o=O(void 0,e),i=n(o);!Object.is(i,void 0)&&console.warn("Immer callback expects no return value. However "+(void 0===i?"undefined":r(i))+" was returned");for(var f=0;f<h.length;f++)h[f].finalizing=!0;!function(){for(var e=h.length-1;e>=0;e--){var n=h[e];!1===n.modified&&(Array.isArray(n.base)?(f=n).proxy.length!==f.base.length&&g(n):(t=n,o=Object.keys(t.base),i=Object.keys(t.proxy),function(e,n){if(Object.is(e,n))return!0;if("object"!==(void 0===e?"undefined":r(e))||null===e||"object"!==(void 0===n?"undefined":r(n))||null===n)return!1;var t=Object.keys(e),o=Object.keys(n);if(t.length!==o.length)return!1;for(var i=0;i<t.length;i++)if(!hasOwnProperty.call(n,t[i])||!Object.is(e[t[i]],n[t[i]]))return!1;return!0}(o,i)||g(n)))}var t,o,i,f}();for(var a=A(o),u=0;u<h.length;u++)h[u].finished=!0;return a}finally{h=t}}var k="undefined"!=typeof Proxy;e.default=function e(n,t){if(1===arguments.length){if("function"!=typeof n)throw new Error("if produce is called with 1 argument, the first argument should be a function");return function(){var r=arguments;return e(r[0],function(e){r[0]=e,n.apply(null,r)})}}if(2!==arguments.length)throw new Error("produce expects 1 or 2 arguments, got "+arguments.length);if(!i(n))throw new Error("the first argument to produce should be a plain object or array, got "+(void 0===n?"undefined":r(n)));if("function"!=typeof t)throw new Error("the second argument to produce should be a function");return k?function(e,n){var t=a;a=[];try{var o=l(void 0,e),i=n(o);!Object.is(i,void 0)&&console.warn("Immer callback expects no return value. However "+(void 0===i?"undefined":r(i))+" was returned");var f=b(o);return a.forEach(function(e){return e.revoke()}),f}finally{a=t}}(n,t):P(n,t)},e.setUseProxies=function(e){k=e},e.setAutoFreeze=function(e){t=e},Object.defineProperty(e,"__esModule",{value:!0})},"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r(e.immer={}); | ||
//# sourceMappingURL=immer.umd.js.map |
{ | ||
"name": "immer", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "Create your next immutable state by mutating the current one", | ||
"main": "dist/index.js", | ||
"types": "./index.d.ts", | ||
"main": "dist/immer.js", | ||
"umd:main": "dist/immer.umd.js", | ||
"module": "dist/immer.module.js", | ||
"jsnext:main": "dist/immer.module.js", | ||
"react-native": "dist/immer.module.js", | ||
"types": "./immer.d.ts", | ||
"scripts": { | ||
"test": "jest", | ||
"test:perf": "yarn build:babel && node --expose-gc node_modules/jest-cli/bin/jest.js --verbose --testRegex '__performance_tests__/.*'", | ||
"test:perf": "yarn build && node --expose-gc node_modules/jest-cli/bin/jest.js --verbose --testRegex '__performance_tests__/.*'", | ||
"coveralls": "jest --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", | ||
"build": "yarn build:babel && yarn build:umd", | ||
"build:babel": "cd src && babel immer.js -o ../proxy.js && babel --no-babelrc --presets=es2015 -d ../dist *.js", | ||
"build:umd": "browserify dist/immer.js -o dist/immer.umd.js -s immer && browserify dist/es5.js -o dist/es5.umd.js -s immer", | ||
"build": "rimraf dist/ && cross-env NODE_ENV=production rollup -c", | ||
"prettier": "prettier \"*/**/*.js\" --ignore-path ./.prettierignore --write", | ||
@@ -36,4 +38,2 @@ "prepublish": "yarn build" | ||
"files": [ | ||
"es5.js", | ||
"proxy.js", | ||
"index.d.ts", | ||
@@ -47,7 +47,8 @@ "dist/" | ||
"babel-jest": "^22.0.4", | ||
"babel-plugin-external-helpers": "^6.22.0", | ||
"babel-preset-env": "^1.6.1", | ||
"babel-preset-es2015": "^6.24.1", | ||
"babel-preset-modern-browsers": "^10.0.1", | ||
"browserify": "^15.0.0", | ||
"coveralls": "^3.0.0", | ||
"cross-env": "^5.1.3", | ||
"deep-freeze": "^0.0.1", | ||
@@ -60,3 +61,11 @@ "immutable": "^3.8.2", | ||
"regenerator-runtime": "^0.11.1", | ||
"typescript": "^2.6.2" | ||
"rimraf": "^2.6.2", | ||
"rollup": "^0.54.0", | ||
"rollup-plugin-babel": "^3.0.3", | ||
"rollup-plugin-commonjs": "^8.2.6", | ||
"rollup-plugin-filesize": "^1.5.0", | ||
"rollup-plugin-node-resolve": "^3.0.2", | ||
"rollup-plugin-uglify": "^2.0.1", | ||
"typescript": "^2.6.2", | ||
"uglify-es": "^3.3.6" | ||
}, | ||
@@ -63,0 +72,0 @@ "jest": { |
119
readme.md
@@ -13,3 +13,3 @@ # Immer | ||
* NPM / Yarn: `npm install immer` | ||
* CDN: https://unpkg.com/immer/dist/immer.umd.js or https://unpkg.com/immer/dist/es5.umd.js. Exposed global is `immer`. | ||
* CDN: https://unpkg.com/immer/dist/immer.umd.js. Exposed global is `immer`. | ||
@@ -90,2 +90,3 @@ --- | ||
* Boilerplate reduction. Less noise, more concise code. | ||
* Small: bundled and minified: 2KB. | ||
@@ -187,86 +188,16 @@ Read further to see all these benefits explained. | ||
## Using Immer on older JavaScript environments | ||
## Immer on older JavaScript environments? | ||
By default `produce` tries to use proxies for optimal performance. | ||
However, on older JavaScript engines `Proxy` is not available. | ||
For example, Microsoft Internet Explorer or React Native on Android. | ||
For example, when running Microsoft Internet Explorer or React Native on Android. | ||
In such cases Immer will fallback to an ES5 compatible implementation which works identical, but is a bit slower. | ||
However, if you want to slim down your bundle, you could only include the Proxy based implementation by importing from `immer/proxy`. | ||
An overview of the available builds: | ||
Immer exposes its functionality in 3 different ways: | ||
* `import produce from "immer"`, the default, ships with both the Proxy and ES5 compatible implementation, and picks at runtime the best matching one. This is the most convenient, but adds the most to the bundle size (~2KB) | ||
* `import produce from "immer/proxy"`: This build is optimized for modern browser, which support Proxies and other modern language features, and doesn't polyfill things like `Symbol`. Use this if you are targetting a modern environment only | ||
* `import produce from "immer/es5"`: Only bundle the ES5 compatible implementation (which of course also works for modern browsers) | ||
## More reducer examples | ||
Here are some typical reducer examples, take from the Redux [Immutable Update Patterns](https://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html) page, and their Immer counter part. | ||
These examples are semantically equivalent and produce the exact same state. | ||
```javascript | ||
// Plain reducer | ||
function insertItem(array, action) { | ||
return [ | ||
...array.slice(0, action.index), | ||
action.item, | ||
...array.slice(action.index) | ||
] | ||
} | ||
// With immer | ||
function insertItem(array, action) { | ||
return produce(array, draft => { | ||
draft.splice(action.index, 0, action.item) | ||
}) | ||
} | ||
// Plain reducer | ||
function removeItem(array, action) { | ||
return [ | ||
...array.slice(0, action.index), | ||
...array.slice(action.index + 1) | ||
]; | ||
} | ||
// With immer | ||
function removeItem(array, action) { | ||
return produce(array, draft => { | ||
draft.splice(action.index, 1) | ||
}) | ||
} | ||
// Plain reducer | ||
function updateObjectInArray(array, action) { | ||
return array.map( (item, index) => { | ||
if(index !== action.index) { | ||
// This isn't the item we care about - keep it as-is | ||
return item; | ||
} | ||
// Otherwise, this is the one we want - return an updated value | ||
return { | ||
...item, | ||
...action.item | ||
}; | ||
}); | ||
} | ||
// With immer | ||
function updateObjectInArray(array, action) { | ||
return produce(array, draft => { | ||
draft[action.index] = { ...item, ...action.item} | ||
// Alternatively, since arbitrarily deep updates are supported: | ||
// Object.assign(draft[action.index], action.item) | ||
}) | ||
} | ||
``` | ||
## Pitfalls | ||
* Currently, Immer only supports plain objects and arrays. PRs are welcome for more language built-in types like `Map` and `Set`. | ||
* Immer only processes native arrays and plain objects (with a prototype of `null` or `Object`). Any other type of value will be treated verbatim! So if you modify a `Map` or `Buffer` or whatever complex object from the draft state, it will be that very same object in both the base state as the next state. In such cases, make sure to always produce fresh instances if you want to keep your state truly immutable. | ||
* Since Immer uses proxies, reading huge amounts of data from state comes with an overhead (especially in the ES5 implementation). If this ever becomes an issue (measure before you optimize!), do the current state analysis before entering the producer function or read from the `currentState` rather than the `draftState` | ||
* Some debuggers (at least Node 6 is known) have trouble debugging when Proxies are in play. Node 8 is known to work correctly. | ||
1. Currently, Immer only supports plain objects and arrays. PRs are welcome for more language built-in types like `Map` and `Set`. | ||
2. Immer only processes native arrays and plain objects (with a prototype of `null` or `Object`). Any other type of value will be treated verbatim! So if you modify a `Map` or `Buffer` (or whatever complex object from the draft state), the changes will be persisted. But, both in your new and old state! So, in such cases, make sure to always produce fresh instances if you want to keep your state truly immutable. | ||
3. For example, working with `Date` objects is no problem, just make sure you never modify them (by using methods like `setYear` on an existing instance). Instead, always create fresh `Date` instances. Which is probably what you were unconsciously doing already. | ||
4. Since Immer uses proxies, reading huge amounts of data from state comes with an overhead (especially in the ES5 implementation). If this ever becomes an issue (measure before you optimize!), do the current state analysis before entering the producer function or read from the `currentState` rather than the `draftState` | ||
5. Some debuggers (at least Node 6 is known) have trouble debugging when Proxies are in play. Node 8 is known to work correctly. | ||
@@ -296,2 +227,34 @@ ## How does Immer work? | ||
## FAQ | ||
_(for those who skimmed the above instead of actually reading)_ | ||
**Q: Does Immer use structural sharing? So that my selectors can be memoized and such?** | ||
A: Yes | ||
**Q: Does Immer support deep updates?** | ||
A: Yes | ||
**Q: I can't rely on Proxies being present on my target environments. Can I use Immer?** | ||
A: Yes | ||
**Q: Can I typecheck my data structures when using Immer?** | ||
A: Yes | ||
**Q: Can I store `Date` objects, functions etc in my state tree when using Immer?** | ||
A: Yes | ||
**Q: Is it fast?** | ||
A: Yes | ||
**Q: Idea! Can Immer freeze the state for me?** | ||
A: Yes | ||
## Credits | ||
@@ -298,0 +261,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
140233
0
26
10
888
263
1