Skip to content

Commit c07b0a2

Browse files
committed
fix: Reorganize code for maximum tree-shakability
1 parent e2d222b commit c07b0a2

File tree

6 files changed

+292
-148
lines changed

6 files changed

+292
-148
lines changed

src/core/immerClass.ts

Lines changed: 27 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,37 @@ import {
33
IProduce,
44
ImmerState,
55
Drafted,
6-
isDraftable,
7-
processResult,
86
Patch,
97
Objectish,
10-
DRAFT_STATE,
118
Draft,
129
PatchListener,
13-
isDraft,
1410
isMap,
1511
isSet,
1612
createProxyProxy,
1713
getPlugin,
18-
die,
19-
enterScope,
20-
revokeScope,
21-
leaveScope,
22-
usePatchesInScope,
2314
getCurrentScope,
24-
NOTHING,
25-
freeze,
26-
current
15+
DEFAULT_AUTOFREEZE,
16+
DEFAULT_USE_STRICT_SHALLOW_COPY,
17+
ImmerContext,
18+
applyPatchesImpl,
19+
createDraftImpl,
20+
finishDraftImpl,
21+
produceImpl,
22+
produceWithPatchesImpl,
23+
setAutoFreezeImpl,
24+
setUseStrictShallowCopyImpl
2725
} from "../internal"
2826

2927
interface ProducersFns {
3028
produce: IProduce
3129
produceWithPatches: IProduceWithPatches
3230
}
3331

34-
export type StrictMode = boolean | "class_only";
32+
export type StrictMode = boolean | "class_only"
3533

36-
export class Immer implements ProducersFns {
37-
autoFreeze_: boolean = true
38-
useStrictShallowCopy_: StrictMode = false
34+
export class Immer implements ProducersFns, ImmerContext {
35+
autoFreeze_: boolean = DEFAULT_AUTOFREEZE
36+
useStrictShallowCopy_: StrictMode = DEFAULT_USE_STRICT_SHALLOW_COPY
3937

4038
constructor(config?: {
4139
autoFreeze?: boolean
@@ -66,139 +64,37 @@ export class Immer implements ProducersFns {
6664
* @param {Function} patchListener - optional function that will be called with all the patches produced here
6765
* @returns {any} a new state, or the initial state if nothing was modified
6866
*/
69-
produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
70-
// curried invocation
71-
if (typeof base === "function" && typeof recipe !== "function") {
72-
const defaultBase = recipe
73-
recipe = base
67+
produce: IProduce = produceImpl.bind(this)
7468

75-
const self = this
76-
return function curriedProduce(
77-
this: any,
78-
base = defaultBase,
79-
...args: any[]
80-
) {
81-
return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
82-
}
83-
}
69+
produceWithPatches: IProduceWithPatches = produceWithPatchesImpl.bind(this)
8470

85-
if (typeof recipe !== "function") die(6)
86-
if (patchListener !== undefined && typeof patchListener !== "function")
87-
die(7)
71+
createDraft = createDraftImpl.bind(this) as <T extends Objectish>(
72+
base: T
73+
) => Draft<T>
8874

89-
let result
90-
91-
// Only plain objects, arrays, and "immerable classes" are drafted.
92-
if (isDraftable(base)) {
93-
const scope = enterScope(this)
94-
const proxy = createProxy(base, undefined)
95-
let hasError = true
96-
try {
97-
result = recipe(proxy)
98-
hasError = false
99-
} finally {
100-
// finally instead of catch + rethrow better preserves original stack
101-
if (hasError) revokeScope(scope)
102-
else leaveScope(scope)
103-
}
104-
usePatchesInScope(scope, patchListener)
105-
return processResult(result, scope)
106-
} else if (!base || typeof base !== "object") {
107-
result = recipe(base)
108-
if (result === undefined) result = base
109-
if (result === NOTHING) result = undefined
110-
if (this.autoFreeze_) freeze(result, true)
111-
if (patchListener) {
112-
const p: Patch[] = []
113-
const ip: Patch[] = []
114-
getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
115-
patchListener(p, ip)
116-
}
117-
return result
118-
} else die(1, base)
119-
}
120-
121-
produceWithPatches: IProduceWithPatches = (base: any, recipe?: any): any => {
122-
// curried invocation
123-
if (typeof base === "function") {
124-
return (state: any, ...args: any[]) =>
125-
this.produceWithPatches(state, (draft: any) => base(draft, ...args))
126-
}
127-
128-
let patches: Patch[], inversePatches: Patch[]
129-
const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => {
130-
patches = p
131-
inversePatches = ip
132-
})
133-
return [result, patches!, inversePatches!]
134-
}
135-
136-
createDraft<T extends Objectish>(base: T): Draft<T> {
137-
if (!isDraftable(base)) die(8)
138-
if (isDraft(base)) base = current(base)
139-
const scope = enterScope(this)
140-
const proxy = createProxy(base, undefined)
141-
proxy[DRAFT_STATE].isManual_ = true
142-
leaveScope(scope)
143-
return proxy as any
144-
}
145-
146-
finishDraft<D extends Draft<any>>(
75+
finishDraft = finishDraftImpl.bind(this) as <D extends Draft<any>>(
14776
draft: D,
14877
patchListener?: PatchListener
149-
): D extends Draft<infer T> ? T : never {
150-
const state: ImmerState = draft && (draft as any)[DRAFT_STATE]
151-
if (!state || !state.isManual_) die(9)
152-
const {scope_: scope} = state
153-
usePatchesInScope(scope, patchListener)
154-
return processResult(undefined, scope)
155-
}
78+
) => D extends Draft<infer T> ? T : never
15679

15780
/**
15881
* Pass true to automatically freeze all copies created by Immer.
15982
*
16083
* By default, auto-freezing is enabled.
16184
*/
162-
setAutoFreeze(value: boolean) {
163-
this.autoFreeze_ = value
164-
}
85+
setAutoFreeze = setAutoFreezeImpl.bind(this)
16586

16687
/**
16788
* Pass true to enable strict shallow copy.
16889
*
16990
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
17091
*/
171-
setUseStrictShallowCopy(value: StrictMode) {
172-
this.useStrictShallowCopy_ = value
173-
}
174-
175-
applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T {
176-
// If a patch replaces the entire state, take that replacement as base
177-
// before applying patches
178-
let i: number
179-
for (i = patches.length - 1; i >= 0; i--) {
180-
const patch = patches[i]
181-
if (patch.path.length === 0 && patch.op === "replace") {
182-
base = patch.value
183-
break
184-
}
185-
}
186-
// If there was a patch that replaced the entire state, start from the
187-
// patch after that.
188-
if (i > -1) {
189-
patches = patches.slice(i + 1)
190-
}
92+
setUseStrictShallowCopy = setUseStrictShallowCopyImpl.bind(this)
19193

192-
const applyPatchesImpl = getPlugin("Patches").applyPatches_
193-
if (isDraft(base)) {
194-
// N.B: never hits if some patch a replacement, patches are never drafts
195-
return applyPatchesImpl(base, patches)
196-
}
197-
// Otherwise, produce a copy of the base state.
198-
return this.produce(base, (draft: Drafted) =>
199-
applyPatchesImpl(draft, patches)
200-
)
201-
}
94+
applyPatches = applyPatchesImpl.bind(this) as <T extends Objectish>(
95+
base: T,
96+
patches: readonly Patch[]
97+
) => T
20298
}
20399

204100
export function createProxy<T extends Objectish>(

src/core/immerContext.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {StrictMode} from "../internal"
2+
3+
export const DEFAULT_AUTOFREEZE = true
4+
export const DEFAULT_USE_STRICT_SHALLOW_COPY = false
5+
6+
export interface ImmerContext {
7+
autoFreeze_: boolean
8+
useStrictShallowCopy_: StrictMode
9+
}
10+
11+
export function createImmerContext(): ImmerContext {
12+
return {
13+
autoFreeze_: DEFAULT_AUTOFREEZE,
14+
useStrictShallowCopy_: DEFAULT_USE_STRICT_SHALLOW_COPY
15+
}
16+
}

0 commit comments

Comments
 (0)