mobx 6.15.3 → 6.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -53,6 +53,8 @@ export declare class ObservableMap<K = any, V = any> implements Map<K, V>, IInte
53
53
  private updateValue_;
54
54
  private addValue_;
55
55
  get(key: K): V | undefined;
56
+ getOrInsert(key: K, value: V): V;
57
+ getOrInsertComputed(key: K, callback: (key: K) => V): V;
56
58
  private dehanceValue_;
57
59
  keys(): MapIterator<K>;
58
60
  values(): MapIterator<V>;
@@ -27,7 +27,7 @@ export type IObjectWillChange<T = any> = {
27
27
  };
28
28
  export declare class ObservableObjectAdministration implements IInterceptable<IObjectWillChange>, IListenable {
29
29
  target_: any;
30
- values_: Map<PropertyKey, ObservableValue<any> | ComputedValue<any>>;
30
+ values_: Map<PropertyKey, ComputedValue<any> | ObservableValue<any>>;
31
31
  name_: string;
32
32
  defaultAnnotation_: Annotation;
33
33
  keysAtom_: IAtom;
@@ -37,8 +37,12 @@ export declare class ObservableObjectAdministration implements IInterceptable<IO
37
37
  isPlainObject_: boolean;
38
38
  appliedAnnotations_?: object;
39
39
  private pendingKeys_;
40
- constructor(target_: any, values_: Map<PropertyKey, ObservableValue<any> | ComputedValue<any>> | undefined, name_: string, defaultAnnotation_?: Annotation);
40
+ lazyComputedKeys_: undefined | Map<PropertyKey, () => ComputedValue<any>>;
41
+ lazyObservableKeys_: undefined | Map<PropertyKey, () => ObservableValue<any>>;
42
+ constructor(target_: any, values_: Map<PropertyKey, ComputedValue<any> | ObservableValue<any>> | undefined, name_: string, defaultAnnotation_?: Annotation);
41
43
  getObservablePropValue_(key: PropertyKey): any;
44
+ materializeLazyComputed_(key: PropertyKey): ComputedValue<any> | undefined;
45
+ materializeLazyObservable_(key: PropertyKey): ObservableValue<any> | undefined;
42
46
  setObservablePropValue_(key: PropertyKey, newValue: any): boolean | null;
43
47
  get_(key: PropertyKey): any;
44
48
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobx",
3
- "version": "6.15.3",
3
+ "version": "6.16.0",
4
4
  "description": "Simple, scalable state management.",
5
5
  "source": "src/mobx.ts",
6
6
  "main": "dist/index.js",
@@ -68,13 +68,14 @@
68
68
  "perf": "scripts/perf.sh",
69
69
  "perf-legacy": "node --expose-gc ./__tests__/perf/index.js legacy",
70
70
  "perf-proxy": "node --expose-gc ./__tests__/perf/index.js proxy",
71
- "test:performance": "yarn perf proxy && yarn perf legacy",
72
- "test:mixed-versions": "yarn test --testRegex mixed-versions",
71
+ "perf-decorator": "tsc -p ./__tests__/perf/tsconfig.decorator.json && node --expose-gc ./__tests__/perf/compiled/lazy-computed-decorator.js && node --expose-gc ./__tests__/perf/compiled/lazy-observable-decorator.js",
72
+ "test:performance": "npm run perf -- proxy && npm run perf -- legacy && npm run perf-decorator",
73
+ "test:mixed-versions": "npm test -- --testRegex mixed-versions",
73
74
  "test:types": "tsc --noEmit",
74
75
  "test:flow": "flow check",
75
- "test:coverage": "yarn test -i --coverage",
76
- "test:size": "yarn import-size --report . observable computed autorun action",
77
- "test:check": "yarn test:types",
76
+ "test:coverage": "npm test -- -i --coverage",
77
+ "test:size": "import-size --report . observable computed autorun action",
78
+ "test:check": "npm run test:types",
78
79
  "prepublishOnly": "node ./scripts/prepublish.js && npm run build -- --target publish"
79
80
  }
80
81
  }
@@ -7,7 +7,11 @@ export function _isComputed(value, property?: PropertyKey): boolean {
7
7
  if (isObservableObject(value) === false) {
8
8
  return false
9
9
  }
10
- if (!value[$mobx].values_.has(property)) {
10
+ const adm = value[$mobx]
11
+ if (adm.lazyComputedKeys_?.has(property)) {
12
+ return true
13
+ }
14
+ if (!adm.values_.has(property)) {
11
15
  return false
12
16
  }
13
17
  const atom = getAtom(value, property)
@@ -21,7 +21,12 @@ function _isObservable(value, property?: PropertyKey): boolean {
21
21
  )
22
22
  }
23
23
  if (isObservableObject(value)) {
24
- return value[$mobx].values_.has(property)
24
+ const adm = value[$mobx]
25
+ return (
26
+ adm.values_.has(property) ||
27
+ !!adm.lazyComputedKeys_?.has(property) ||
28
+ !!adm.lazyObservableKeys_?.has(property)
29
+ )
25
30
  }
26
31
  return false
27
32
  }
@@ -54,17 +54,23 @@ function decorate_20223_(this: Annotation, get, context: ClassGetterDecoratorCon
54
54
  const ann = this
55
55
  const { name: key, addInitializer } = context
56
56
 
57
+ // Defer ComputedValue creation until first access — avoids allocating
58
+ // ComputedValues for getters that are never read on a given instance.
59
+ // The factory is materialised by ObservableObjectAdministration on demand.
57
60
  addInitializer(function () {
58
61
  const adm: ObservableObjectAdministration = asObservableObject(this)[$mobx]
59
- const options = {
60
- ...ann.options_,
61
- get,
62
- context: this
63
- }
64
- options.name ||= __DEV__
65
- ? `${adm.name_}.${key.toString()}`
66
- : `ObservableObject.${key.toString()}`
67
- adm.values_.set(key, new ComputedValue(options))
62
+ const target = this
63
+ ;(adm.lazyComputedKeys_ ??= new Map()).set(key, () => {
64
+ const options = {
65
+ ...ann.options_,
66
+ get,
67
+ context: target
68
+ }
69
+ options.name ||= __DEV__
70
+ ? `${adm.name_}.${key.toString()}`
71
+ : `ObservableObject.${key.toString()}`
72
+ return new ComputedValue(options)
73
+ })
68
74
  })
69
75
 
70
76
  return function () {
@@ -64,51 +64,45 @@ function decorate_20223_(
64
64
  const ann = this
65
65
  const { kind, name } = context
66
66
 
67
- // The laziness here is not ideal... It's a workaround to how 2022.3 Decorators are implemented:
68
- // `addInitializer` callbacks are executed _before_ any accessors are defined (instead of the ideal-for-us right after each).
69
- // This means that, if we were to do our stuff in an `addInitializer`, we'd attempt to read a private slot
70
- // before it has been initialized. The runtime doesn't like that and throws a `Cannot read private member
71
- // from an object whose class did not declare it` error.
72
- // TODO: it seems that this will not be required anymore in the final version of the spec
73
- // See TODO: link
74
- const initializedObjects = new WeakSet()
67
+ if (kind !== "accessor") {
68
+ return
69
+ }
75
70
 
76
- function initializeObservable(target, value) {
71
+ // Defer ObservableValue construction until first access. The factory is
72
+ // materialised by ObservableObjectAdministration on demand, so unused
73
+ // fields on wide classes never pay the per-instance allocation cost.
74
+ function registerLazy(target: any, value: any): ObservableObjectAdministration {
77
75
  const adm: ObservableObjectAdministration = asObservableObject(target)[$mobx]
78
- const observable = new ObservableValue(
79
- value,
80
- ann.options_?.enhancer ?? deepEnhancer,
81
- __DEV__ ? `${adm.name_}.${name.toString()}` : `ObservableObject.${name.toString()}`,
82
- false
76
+ ;(adm.lazyObservableKeys_ ??= new Map()).set(
77
+ name,
78
+ () =>
79
+ new ObservableValue(
80
+ value,
81
+ ann.options_?.enhancer ?? deepEnhancer,
82
+ __DEV__
83
+ ? `${adm.name_}.${name.toString()}`
84
+ : `ObservableObject.${name.toString()}`,
85
+ false
86
+ )
83
87
  )
84
- adm.values_.set(name, observable)
85
- initializedObjects.add(target)
88
+ return adm
86
89
  }
87
90
 
88
- if (kind == "accessor") {
89
- return {
90
- get() {
91
- if (!initializedObjects.has(this)) {
92
- initializeObservable(this, desc.get.call(this))
93
- }
94
- return this[$mobx].getObservablePropValue_(name)
95
- },
96
- set(value) {
97
- if (!initializedObjects.has(this)) {
98
- initializeObservable(this, value)
99
- }
100
- return this[$mobx].setObservablePropValue_(name, value)
101
- },
102
- init(value) {
103
- if (!initializedObjects.has(this)) {
104
- initializeObservable(this, value)
105
- }
106
- return value
107
- }
91
+ return {
92
+ get() {
93
+ const adm: ObservableObjectAdministration =
94
+ this[$mobx] ?? registerLazy(this, desc.get.call(this))
95
+ return adm.getObservablePropValue_(name)
96
+ },
97
+ set(value) {
98
+ const adm: ObservableObjectAdministration = this[$mobx] ?? registerLazy(this, value)
99
+ return adm.setObservablePropValue_(name, value)
100
+ },
101
+ init(value) {
102
+ registerLazy(this, value)
103
+ return value
108
104
  }
109
105
  }
110
-
111
- return
112
106
  }
113
107
 
114
108
  function assertObservableDescriptor(
@@ -289,6 +289,20 @@ export class ObservableMap<K = any, V = any>
289
289
  return this.dehanceValue_(undefined)
290
290
  }
291
291
 
292
+ getOrInsert(key: K, value: V): V {
293
+ if (!this.has(key)) {
294
+ this.set(key, value)
295
+ }
296
+ return this.get(key)!
297
+ }
298
+
299
+ getOrInsertComputed(key: K, callback: (key: K) => V): V {
300
+ if (!this.has(key)) {
301
+ this.set(key, callback(key))
302
+ }
303
+ return this.get(key)!
304
+ }
305
+
292
306
  private dehanceValue_<X extends V | undefined>(value: X): X {
293
307
  if (this.dehancer !== undefined) {
294
308
  return this.dehancer(value)
@@ -98,6 +98,8 @@ export class ObservableObjectAdministration
98
98
  isPlainObject_: boolean
99
99
  appliedAnnotations_?: object
100
100
  private pendingKeys_: undefined | Map<PropertyKey, ObservableValue<boolean>>
101
+ lazyComputedKeys_: undefined | Map<PropertyKey, () => ComputedValue<any>>
102
+ lazyObservableKeys_: undefined | Map<PropertyKey, () => ObservableValue<any>>
101
103
 
102
104
  constructor(
103
105
  public target_: any,
@@ -119,11 +121,47 @@ export class ObservableObjectAdministration
119
121
  }
120
122
 
121
123
  getObservablePropValue_(key: PropertyKey): any {
122
- return this.values_.get(key)!.get()
124
+ // Hot path: single map lookup. Lazy entries (rare) take the materialise branch.
125
+ const observable =
126
+ this.values_.get(key) ??
127
+ this.materializeLazyComputed_(key) ??
128
+ this.materializeLazyObservable_(key)
129
+ return observable!.get()
130
+ }
131
+
132
+ materializeLazyComputed_(key: PropertyKey): ComputedValue<any> | undefined {
133
+ const factory = this.lazyComputedKeys_?.get(key)
134
+ if (!factory) {
135
+ return undefined
136
+ }
137
+ this.lazyComputedKeys_!.delete(key)
138
+ if (this.lazyComputedKeys_!.size === 0) {
139
+ this.lazyComputedKeys_ = undefined
140
+ }
141
+ const computed = factory()
142
+ this.values_.set(key, computed)
143
+ return computed
144
+ }
145
+
146
+ materializeLazyObservable_(key: PropertyKey): ObservableValue<any> | undefined {
147
+ const factory = this.lazyObservableKeys_?.get(key)
148
+ if (!factory) {
149
+ return undefined
150
+ }
151
+ this.lazyObservableKeys_!.delete(key)
152
+ if (this.lazyObservableKeys_!.size === 0) {
153
+ this.lazyObservableKeys_ = undefined
154
+ }
155
+ const observable = factory()
156
+ this.values_.set(key, observable)
157
+ return observable
123
158
  }
124
159
 
125
160
  setObservablePropValue_(key: PropertyKey, newValue): boolean | null {
126
- const observable = this.values_.get(key)
161
+ const observable =
162
+ this.values_.get(key) ??
163
+ this.materializeLazyComputed_(key) ??
164
+ this.materializeLazyObservable_(key)
127
165
  if (observable instanceof ComputedValue) {
128
166
  observable.set(newValue)
129
167
  return true
@@ -47,7 +47,11 @@ export function getAtom(thing: any, property?: PropertyKey): IDepTreeNode {
47
47
  if (!property) {
48
48
  return die(26)
49
49
  }
50
- const observable = (thing as any)[$mobx].values_.get(property)
50
+ const adm = (thing as any)[$mobx]
51
+ const observable =
52
+ adm.values_.get(property) ??
53
+ adm.materializeLazyComputed_(property) ??
54
+ adm.materializeLazyObservable_(property)
51
55
  if (!observable) {
52
56
  die(27, property, getDebugName(thing))
53
57
  }
@@ -1,7 +1,8 @@
1
1
  import { getGlobal } from "../internal"
2
2
 
3
3
  // safely get iterator prototype if available
4
- const maybeIteratorPrototype = getGlobal().Iterator?.prototype || {}
4
+ const global = getGlobal()
5
+ const maybeIteratorPrototype = global.Iterator?.prototype || {}
5
6
 
6
7
  export function makeIterable<T, TReturn = unknown>(
7
8
  iterator: Iterator<T>