@reckona/mreact-compat 0.0.92 → 0.0.93

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reckona/mreact-compat",
3
- "version": "0.0.92",
3
+ "version": "0.0.93",
4
4
  "description": "React-compatible runtime implementation for mreact.",
5
5
  "keywords": [
6
6
  "compatibility",
@@ -69,6 +69,6 @@
69
69
  "access": "public"
70
70
  },
71
71
  "dependencies": {
72
- "@reckona/mreact-shared": "0.0.92"
72
+ "@reckona/mreact-shared": "0.0.93"
73
73
  }
74
74
  }
@@ -174,6 +174,8 @@ interface ClassUpdateContext {
174
174
  interface ClassComponentGlobalState {
175
175
  lifecycleSnapshots: WeakMap<ClassComponentInstance, ClassLifecycleSnapshot>;
176
176
  updateContexts: WeakMap<ClassComponentInstance, ClassUpdateContext>;
177
+ pendingInstancesByRuntime: WeakMap<RootRuntime, Map<string, ClassComponentInstance>>;
178
+ dirtyPathsByRuntime: WeakMap<RootRuntime, Set<string>>;
177
179
  }
178
180
 
179
181
  const CLASS_COMPONENT_STATE_KEY = Symbol.for("modular.react.class_component_state");
@@ -183,9 +185,13 @@ const classComponentGlobalState =
183
185
  ] ??= {
184
186
  lifecycleSnapshots: new WeakMap(),
185
187
  updateContexts: new WeakMap(),
188
+ pendingInstancesByRuntime: new WeakMap(),
189
+ dirtyPathsByRuntime: new WeakMap(),
186
190
  });
187
191
  const classLifecycleSnapshots = classComponentGlobalState.lifecycleSnapshots;
188
192
  const classUpdateContexts = classComponentGlobalState.updateContexts;
193
+ const classPendingInstancesByRuntime = classComponentGlobalState.pendingInstancesByRuntime;
194
+ const classDirtyPathsByRuntime = classComponentGlobalState.dirtyPathsByRuntime;
189
195
 
190
196
  export type ClassComponentRenderResult =
191
197
  | {
@@ -255,11 +261,27 @@ export function renderClassComponentWithRuntime(
255
261
  props: Record<string, unknown>,
256
262
  runtime: RootRuntime,
257
263
  path: string,
258
- options: { hasDirtyDescendant?: boolean } = {},
264
+ options: {
265
+ currentInstance?: ClassComponentInstance;
266
+ hasDirtyDescendant?: boolean;
267
+ } = {},
259
268
  ): ClassComponentRenderResult {
260
269
  return renderWithRootRuntime(runtime, path, () => {
261
270
  const instanceRef = useRef<ClassComponentInstance | undefined>(undefined);
262
271
  const didCommitRef = useRef(false);
272
+ const currentInstance =
273
+ options.currentInstance instanceof type
274
+ ? options.currentInstance
275
+ : getPendingClassInstance(runtime, path, type);
276
+
277
+ if (
278
+ currentInstance !== undefined &&
279
+ instanceRef.current !== currentInstance
280
+ ) {
281
+ instanceRef.current = currentInstance;
282
+ didCommitRef.current = true;
283
+ }
284
+
263
285
  const previousInstance = instanceRef.current;
264
286
  const hasDifferentType =
265
287
  previousInstance !== undefined && !(previousInstance instanceof type);
@@ -282,6 +304,7 @@ export function renderClassComponentWithRuntime(
282
304
 
283
305
  instanceRef.current = instance;
284
306
  installClassUpdateMethods(instance, runtime, path);
307
+ clearPendingClassUpdate(runtime, path);
285
308
  const nextState = resolveDerivedStateFromProps(
286
309
  type,
287
310
  props,
@@ -488,7 +511,7 @@ function enqueueClassSetState(
488
511
  nextState,
489
512
  });
490
513
 
491
- markClassInstanceDirty(updateContext);
514
+ markClassInstanceDirty(instance, updateContext);
492
515
  callback?.call(instance);
493
516
  }
494
517
 
@@ -507,11 +530,15 @@ function enqueueClassForceUpdate(
507
530
  previousState: instance.state ?? {},
508
531
  force: true,
509
532
  });
510
- markClassInstanceDirty(updateContext);
533
+ markClassInstanceDirty(instance, updateContext);
511
534
  callback?.call(instance);
512
535
  }
513
536
 
514
- function markClassInstanceDirty(updateContext: ClassUpdateContext): void {
537
+ function markClassInstanceDirty(
538
+ instance: ClassComponentInstance,
539
+ updateContext: ClassUpdateContext,
540
+ ): void {
541
+ markPendingClassUpdate(instance, updateContext);
515
542
  const runtimeInstance = updateContext.runtime.instances.get(updateContext.path) as
516
543
  | { dirty?: boolean }
517
544
  | undefined;
@@ -521,6 +548,76 @@ function markClassInstanceDirty(updateContext: ClassUpdateContext): void {
521
548
  updateContext.runtime.rerender();
522
549
  }
523
550
 
551
+ export function hasDirtyClassUpdate(
552
+ runtime: RootRuntime | undefined,
553
+ keys: readonly string[],
554
+ prefix?: string,
555
+ ): boolean {
556
+ if (runtime === undefined) {
557
+ return false;
558
+ }
559
+
560
+ const dirtyPaths = classDirtyPathsByRuntime.get(runtime);
561
+ if (dirtyPaths === undefined) {
562
+ return false;
563
+ }
564
+
565
+ for (const key of keys) {
566
+ if (dirtyPaths.has(key)) {
567
+ return true;
568
+ }
569
+ }
570
+
571
+ if (prefix === undefined) {
572
+ return false;
573
+ }
574
+
575
+ for (const dirtyPath of dirtyPaths) {
576
+ if (dirtyPath === prefix || dirtyPath.startsWith(`${prefix}.`)) {
577
+ return true;
578
+ }
579
+ }
580
+
581
+ return false;
582
+ }
583
+
584
+ function markPendingClassUpdate(
585
+ instance: ClassComponentInstance,
586
+ updateContext: ClassUpdateContext,
587
+ ): void {
588
+ let pendingInstances = classPendingInstancesByRuntime.get(updateContext.runtime);
589
+ if (pendingInstances === undefined) {
590
+ pendingInstances = new Map();
591
+ classPendingInstancesByRuntime.set(updateContext.runtime, pendingInstances);
592
+ }
593
+ let dirtyPaths = classDirtyPathsByRuntime.get(updateContext.runtime);
594
+ if (dirtyPaths === undefined) {
595
+ dirtyPaths = new Set();
596
+ classDirtyPathsByRuntime.set(updateContext.runtime, dirtyPaths);
597
+ }
598
+ pendingInstances.set(updateContext.path, instance);
599
+ dirtyPaths.add(updateContext.path);
600
+ }
601
+
602
+ function getPendingClassInstance(
603
+ runtime: RootRuntime,
604
+ path: string,
605
+ type: ClassComponentType,
606
+ ): ClassComponentInstance | undefined {
607
+ const dirtyPaths = classDirtyPathsByRuntime.get(runtime);
608
+ if (dirtyPaths?.has(path) !== true) {
609
+ return undefined;
610
+ }
611
+
612
+ const instance = classPendingInstancesByRuntime.get(runtime)?.get(path);
613
+ return instance instanceof type ? instance : undefined;
614
+ }
615
+
616
+ function clearPendingClassUpdate(runtime: RootRuntime, path: string): void {
617
+ classDirtyPathsByRuntime.get(runtime)?.delete(path);
618
+ classPendingInstancesByRuntime.get(runtime)?.delete(path);
619
+ }
620
+
524
621
  function installClassLifecycleEffects(
525
622
  instance: ClassComponentInstance,
526
623
  didCommitRef: { current: boolean },
@@ -51,9 +51,11 @@ import {
51
51
  } from "./hooks.js";
52
52
  import { isThenable } from "./thenable.js";
53
53
  import {
54
+ hasDirtyClassUpdate,
54
55
  isClassComponentType,
55
56
  recoverClassComponentError,
56
57
  renderClassComponentWithRuntime,
58
+ type ClassComponentInstance,
57
59
  } from "./class-component.js";
58
60
  import { areMemoPropsEqual, getPendingProps, shallowEqual } from "./prop-comparison.js";
59
61
  import {
@@ -1204,7 +1206,7 @@ function createHostFiberImpl(
1204
1206
 
1205
1207
  if (
1206
1208
  previousMemoState !== undefined &&
1207
- !hasDirtyInstance(runtime, previousMemoState.instanceKeys) &&
1209
+ !hasDirtyInstance(runtime, previousMemoState.instanceKeys, memoPath) &&
1208
1210
  !hasUnflushedMountEffectInstance(runtime, previousMemoState.instanceKeys) &&
1209
1211
  areMemoPropsEqual(memoType, previousMemoState.props, node.props)
1210
1212
  ) {
@@ -1315,12 +1317,25 @@ function createHostFiberImpl(
1315
1317
  : createFiber("class-component", node.props, key);
1316
1318
  fiber.type = classType;
1317
1319
  const previousClassChildKeys = collectInstanceKeys(runtime, `${path}.class`);
1320
+ const currentClassInstance =
1321
+ current?.tag === "class-component" && current.type === classType
1322
+ ? (current.stateNode as ClassComponentInstance)
1323
+ : undefined;
1318
1324
  const rendered = renderClassComponentWithRuntime(
1319
1325
  classType,
1320
1326
  node.props,
1321
1327
  runtime,
1322
1328
  path,
1323
- { hasDirtyDescendant: hasDirtyInstance(runtime, previousClassChildKeys) },
1329
+ {
1330
+ ...(currentClassInstance === undefined
1331
+ ? {}
1332
+ : { currentInstance: currentClassInstance }),
1333
+ hasDirtyDescendant: hasDirtyInstance(
1334
+ runtime,
1335
+ previousClassChildKeys,
1336
+ `${path}.class`,
1337
+ ),
1338
+ },
1324
1339
  );
1325
1340
  applyRef(node.ref, rendered.kind === "skip" ? current?.stateNode : rendered.instance);
1326
1341
 
@@ -1398,7 +1413,7 @@ function createHostFiberImpl(
1398
1413
  runtime.strictReplayDepth === 0 &&
1399
1414
  previousFunctionState !== undefined &&
1400
1415
  (canReuseSameElement || canReuseExternalStoreSnapshot) &&
1401
- !hasDirtyInstance(runtime, previousFunctionState.instanceKeys) &&
1416
+ !hasDirtyInstance(runtime, previousFunctionState.instanceKeys, path) &&
1402
1417
  !hasUnflushedMountEffectInstance(runtime, previousFunctionState.instanceKeys) &&
1403
1418
  !hasPendingAsyncChild(current?.child)
1404
1419
  ) {
@@ -1495,6 +1510,7 @@ function createHostFiberImpl(
1495
1510
  current?.tag === "host-component" &&
1496
1511
  current.type === node.type &&
1497
1512
  Object.is(hostFiberChildrenProp(current.memoizedProps), node.props.children) &&
1513
+ !hasDirtyInstance(runtime, [], `${path}.c`) &&
1498
1514
  canReuseStaticHostSubtree(current.child)
1499
1515
  ) {
1500
1516
  fiber.child = current.child;
@@ -3064,11 +3080,40 @@ function markActiveInstanceKeys(runtime: RootRuntime, keys: readonly string[]):
3064
3080
  }
3065
3081
  }
3066
3082
 
3067
- function hasDirtyInstance(runtime: RootRuntime, keys: readonly string[]): boolean {
3068
- return keys.some(
3083
+ function hasDirtyInstance(
3084
+ runtime: RootRuntime | undefined,
3085
+ keys: readonly string[],
3086
+ prefix?: string,
3087
+ ): boolean {
3088
+ if (runtime === undefined) {
3089
+ return false;
3090
+ }
3091
+
3092
+ if (hasDirtyClassUpdate(runtime, keys, prefix)) {
3093
+ return true;
3094
+ }
3095
+
3096
+ if (keys.some(
3069
3097
  (key) =>
3070
3098
  (runtime.instances.get(key) as { dirty?: boolean } | undefined)?.dirty === true,
3071
- );
3099
+ )) {
3100
+ return true;
3101
+ }
3102
+
3103
+ if (prefix === undefined) {
3104
+ return false;
3105
+ }
3106
+
3107
+ for (const [key, instance] of runtime.instances) {
3108
+ if (
3109
+ (key === prefix || key.startsWith(`${prefix}.`)) &&
3110
+ (instance as { dirty?: boolean }).dirty === true
3111
+ ) {
3112
+ return true;
3113
+ }
3114
+ }
3115
+
3116
+ return false;
3072
3117
  }
3073
3118
 
3074
3119
  function hasUnflushedMountEffectInstance(runtime: RootRuntime, keys: readonly string[]): boolean {