Skip to content

Commit a406c8f

Browse files
authoredNov 17, 2020
feature: Always freeze by default (#702)
BREAKING CHANGE: always freeze by default, even in production mode. Use `setAutoFreeze(process.env.NODE_ENV !== 'production')` for the old behavior. See #687 (comment) for the rationale. Fixes #649, #681, #687
1 parent 6c62eec commit a406c8f

File tree

11 files changed

+44
-28
lines changed

11 files changed

+44
-28
lines changed
 

‎.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: node_js
22
node_js:
3-
# - "10.18.1"
4-
- "node"
3+
- "10.18.1"
4+
# - "node"
55
env:
66
- NODE_ENV=TEST
77
cache:

‎__tests__/frozen.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import produce, {
33
setUseProxies,
44
setAutoFreeze,
5-
enableAllPlugins
5+
enableAllPlugins,
6+
freeze
67
} from "../src/immer"
78

89
enableAllPlugins()
@@ -246,3 +247,21 @@ function runTests(name, useProxies) {
246247
})
247248
})
248249
}
250+
251+
test("freeze - shallow", () => {
252+
const obj1 = {hello: {world: true}}
253+
const res = freeze(obj1)
254+
255+
expect(res).toBe(obj1)
256+
expect(Object.isFrozen(res)).toBe(true)
257+
expect(Object.isFrozen(res.hello)).toBe(false)
258+
})
259+
260+
test("freeze - deep", () => {
261+
const obj1 = {hello: {world: true}}
262+
const res = freeze(obj1, true)
263+
264+
expect(res).toBe(obj1)
265+
expect(Object.isFrozen(res)).toBe(true)
266+
expect(Object.isFrozen(res.hello)).toBe(true)
267+
})

‎docs/api.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ title: API overview
2121
| `enableMapSet()` | Enables support for `Map` and `Set` collections. | [Installation](installation#pick-your-immer-version) |
2222
| `enablePatches()` | Enables support for JSON patches. | [Installation](installation#pick-your-immer-version) |
2323
| `finishDraft` | Given an draft created using `createDraft`, seals the draft and produces and returns the next immutable state that captures all the changes | [Async](async.md) |
24+
| `freeze(obj, deep?)` | Freezes draftable objects. Returns the original object. By default freezes shallowly, but if the second argument is `true` it will freeze recursively. |
2425
| `Immer` | constructor that can be used to create a second "immer" instance (exposing all APIs listed in this instance), that doesn't share its settings with global instance. |
2526
| `immerable` | Symbol that can be added to a constructor or prototype, to indicate that Immer should treat the class as something that can be safely drafted | [Classes](complex-objects.md) |
2627
| `Immutable<T>` | Exposed TypeScript type to convert mutable types to immutable types | |
@@ -31,7 +32,7 @@ title: API overview
3132
| `Patch` | Exposed TypeScript type, describes the shape of an (inverse) patch object | [Patches](patches.md) |
3233
| `produce` | The core API of Immer, also exposed as the `default` export | [Produce](produce.md) |
3334
| `produceWithPatches` | Works the same as `produce`, but instead of just returning the produced object, it returns a tuple, consisting of `[result, patches, inversePatches]`. | [Patches](patches.md) |
34-
| `setAutoFreeze` | Enables / disables automatic freezing of the trees produces. By default enabled in development builds | [Freezing](freezing.md) |
35+
| `setAutoFreeze` | Enables / disables automatic freezing of the trees produces. By default enabled. | [Freezing](freezing.md) |
3536
| `setUseProxies` | Can be used to disable or force the use of `Proxy` objects. Useful when filing bug reports. | |
3637

3738
## Importing immer

‎docs/freezing.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ title: Auto freezing
1616
<a style="font-style:italic;padding:5px;margin:5px;" href="https://egghead.io/lessons/javascript-produces-immutable-data-and-avoid-unnecessary-creation-of-new-data-trees-with-immer">Hosted on egghead.io</a>
1717
</details>
1818

19-
Immer automatically freezes any state trees that are modified using `produce`. This protects against accidental modifications of the state tree outside of a producer. This comes with a performance impact, so it is recommended to disable this option in production. By default, it is turned on during local development and turned off in production. Use `setAutoFreeze(true / false)` to explicitly turn this feature on or off.
19+
Immer automatically freezes any state trees that are modified using `produce`. This protects against accidental modifications of the state tree outside of a producer. In most cases this provides the most optimal behavior, but `setAutoFreeze(true / false)` can be used to explicitly turn this feature on or off.
2020

2121
Immer will never freeze (the contents of) non-enumerable, non-own or symbolic properties, unless their content was drafted.
2222

23+
_⚠️ Immer freezes everything recursively, for large data objects that won't be changed in the future this might be over-kill, in that case it can be more efficient to shallowly pre-freeze data using the `freeze` utility.⚠️_
24+
2325
_⚠️ If auto freezing is enabled, recipes are not entirely side-effect free: Any plain object or array that ends up in the produced result, will be frozen, even when these objects were not frozen before the start of the producer! ⚠️_

‎docs/performance.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Most important observation:
4444

4545
### Pre-freeze data
4646

47-
When adding a large data set to the state tree in an Immer producer (for example data received from a JSON endpoint), it is worth to call `Object.freeze(json)` on the root of the data to be added first. This will allow Immer to add the new data to the tree faster, as it will skip freezing it, or searching the tree for any changes (drafts) that might be made.
47+
When adding a large data set to the state tree in an Immer producer (for example data received from a JSON endpoint), it is worth to call `freeze(json)` on the root of the data to be added first. This will allow Immer to add the new data to the tree faster, as it will skip _recursively_ freezing it, or searching the new data for any changes (drafts) that might be made.
4848

4949
### You can always opt-out
5050

‎src/core/immerClass.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
getPlugin,
1818
die,
1919
hasProxies,
20-
isMinified,
2120
enterScope,
2221
revokeScope,
2322
leaveScope,
@@ -36,7 +35,7 @@ interface ProducersFns {
3635
export class Immer implements ProducersFns {
3736
useProxies_: boolean = hasProxies
3837

39-
autoFreeze_: boolean = __DEV__ ? true /* istanbul ignore next */ : !isMinified
38+
autoFreeze_: boolean = true
4039

4140
constructor(config?: {useProxies?: boolean; autoFreeze?: boolean}) {
4241
if (typeof config?.useProxies === "boolean")

‎src/immer.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export {
1616
isDraft,
1717
isDraftable,
1818
NOTHING as nothing,
19-
DRAFTABLE as immerable
19+
DRAFTABLE as immerable,
20+
freeze
2021
} from "./internal"
2122

2223
const immer = new Immer()

‎src/types/index.js.flow

+2
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,5 @@ declare export function enableES5(): void
109109
declare export function enableMapSet(): void
110110
declare export function enablePatches(): void
111111
declare export function enableAllPlugins(): void
112+
113+
declare export function freeze<T>(obj: T, freeze?: boolean): T

‎src/utils/common.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,22 @@ export function shallowCopy(base: any) {
179179
return Object.create(Object.getPrototypeOf(base), descriptors)
180180
}
181181

182-
export function freeze(obj: any, deep: boolean): void {
183-
if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return
182+
/**
183+
* Freezes draftable objects. Returns the original object.
184+
* By default freezes shallowly, but if the second argument is `true` it will freeze recursively.
185+
*
186+
* @param obj
187+
* @param deep
188+
*/
189+
export function freeze<T>(obj: T, deep?: boolean): T
190+
export function freeze<T>(obj: any, deep: boolean = false): T {
191+
if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj
184192
if (getArchtype(obj) > 1 /* Map or Set */) {
185193
obj.set = obj.add = obj.clear = obj.delete = dontMutateFrozenCollections as any
186194
}
187195
Object.freeze(obj)
188196
if (deep) each(obj, (key, value) => freeze(value, true), true)
197+
return obj
189198
}
190199

191200
function dontMutateFrozenCollections() {

‎src/utils/env.ts

-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ export const hasProxies =
1212
typeof Proxy.revocable !== "undefined" &&
1313
typeof Reflect !== "undefined"
1414

15-
/* istanbul ignore next */
16-
function mini() {}
17-
export const isMinified = mini.name !== "mini"
18-
1915
/**
2016
* The sentinel value returned by producers to replace the draft with undefined.
2117
*/

‎website/static/css/custom.css

-13
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,3 @@ a {
2222
a:hover {
2323
text-decoration: underline;
2424
}
25-
26-
/* BLM */
27-
.slidingNav::before {
28-
content: "Black Lives Matter";
29-
position: fixed;
30-
left: 0;
31-
width: 100vw;
32-
top: 13px;
33-
text-align: center;
34-
pointer-events: none;
35-
text-transform: uppercase;
36-
font-weight: bold;
37-
}

0 commit comments

Comments
 (0)
Please sign in to comment.