@lynx-js/react 0.109.2 → 0.110.1

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.
Files changed (202) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/components/lib/Page.js.map +1 -1
  3. package/components/src/Page.ts +1 -1
  4. package/package.json +1 -1
  5. package/refresh/.turbo/turbo-build.log +1 -1
  6. package/runtime/lib/backgroundSnapshot.d.ts +2 -1
  7. package/runtime/lib/backgroundSnapshot.js +66 -42
  8. package/runtime/lib/backgroundSnapshot.js.map +1 -1
  9. package/runtime/lib/compat/initData.d.ts +7 -5
  10. package/runtime/lib/compat/initData.js +1 -2
  11. package/runtime/lib/compat/initData.js.map +1 -1
  12. package/runtime/lib/compat/lynxComponent.js +10 -12
  13. package/runtime/lib/compat/lynxComponent.js.map +1 -1
  14. package/runtime/lib/debug/profile.js +1 -0
  15. package/runtime/lib/debug/profile.js.map +1 -1
  16. package/runtime/lib/gesture/processGestureBagkround.d.ts +1 -1
  17. package/runtime/lib/gesture/processGestureBagkround.js +4 -1
  18. package/runtime/lib/gesture/processGestureBagkround.js.map +1 -1
  19. package/runtime/lib/gesture/types.js.map +1 -1
  20. package/runtime/lib/hooks/react.js +3 -3
  21. package/runtime/lib/hooks/react.js.map +1 -1
  22. package/runtime/lib/hooks/useLynxGlobalEventListener.d.ts +1 -1
  23. package/runtime/lib/hydrate.d.ts +1 -1
  24. package/runtime/lib/hydrate.js +5 -4
  25. package/runtime/lib/hydrate.js.map +1 -1
  26. package/runtime/lib/internal.d.ts +3 -2
  27. package/runtime/lib/internal.js +3 -2
  28. package/runtime/lib/internal.js.map +1 -1
  29. package/runtime/lib/legacy-react-runtime/index.js +1 -1
  30. package/runtime/lib/legacy-react-runtime/index.js.map +1 -1
  31. package/runtime/lib/lifecycle/destroy.js +1 -0
  32. package/runtime/lib/lifecycle/destroy.js.map +1 -1
  33. package/runtime/lib/lifecycle/event/delayEvents.js +3 -0
  34. package/runtime/lib/lifecycle/event/delayEvents.js.map +1 -1
  35. package/runtime/lib/lifecycle/event/delayLifecycleEvents.d.ts +3 -2
  36. package/runtime/lib/lifecycle/event/delayLifecycleEvents.js +1 -12
  37. package/runtime/lib/lifecycle/event/delayLifecycleEvents.js.map +1 -1
  38. package/runtime/lib/lifecycle/event/jsReady.d.ts +1 -1
  39. package/runtime/lib/lifecycle/event/jsReady.js +3 -2
  40. package/runtime/lib/lifecycle/event/jsReady.js.map +1 -1
  41. package/runtime/lib/lifecycle/patch/commit.d.ts +0 -2
  42. package/runtime/lib/lifecycle/patch/commit.js +8 -36
  43. package/runtime/lib/lifecycle/patch/commit.js.map +1 -1
  44. package/runtime/lib/lifecycle/patch/error.d.ts +8 -0
  45. package/runtime/lib/lifecycle/patch/error.js +47 -0
  46. package/runtime/lib/lifecycle/patch/error.js.map +1 -0
  47. package/runtime/lib/lifecycle/patch/isMainThreadHydrationFinished.js +3 -0
  48. package/runtime/lib/lifecycle/patch/isMainThreadHydrationFinished.js.map +1 -1
  49. package/runtime/lib/lifecycle/patch/snapshotPatch.d.ts +2 -2
  50. package/runtime/lib/lifecycle/patch/snapshotPatch.js.map +1 -1
  51. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js +10 -10
  52. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js.map +1 -1
  53. package/runtime/lib/lifecycle/patch/updateMainThread.d.ts +0 -1
  54. package/runtime/lib/lifecycle/patch/updateMainThread.js +7 -14
  55. package/runtime/lib/lifecycle/patch/updateMainThread.js.map +1 -1
  56. package/runtime/lib/lifecycle/ref/delay.d.ts +31 -0
  57. package/runtime/lib/lifecycle/ref/delay.js +80 -0
  58. package/runtime/lib/lifecycle/ref/delay.js.map +1 -0
  59. package/runtime/lib/lifecycle/reload.d.ts +1 -1
  60. package/runtime/lib/lifecycle/reload.js +8 -5
  61. package/runtime/lib/lifecycle/reload.js.map +1 -1
  62. package/runtime/lib/lifecycle/render.js +3 -2
  63. package/runtime/lib/lifecycle/render.js.map +1 -1
  64. package/runtime/lib/lifecycleConstant.d.ts +10 -7
  65. package/runtime/lib/lifecycleConstant.js +8 -8
  66. package/runtime/lib/lifecycleConstant.js.map +1 -1
  67. package/runtime/lib/list.d.ts +1 -45
  68. package/runtime/lib/list.js +31 -206
  69. package/runtime/lib/list.js.map +1 -1
  70. package/runtime/lib/listUpdateInfo.d.ts +38 -0
  71. package/runtime/lib/listUpdateInfo.js +152 -0
  72. package/runtime/lib/listUpdateInfo.js.map +1 -0
  73. package/runtime/lib/lynx/calledByNative.js +9 -6
  74. package/runtime/lib/lynx/calledByNative.js.map +1 -1
  75. package/runtime/lib/lynx/component.js +7 -0
  76. package/runtime/lib/lynx/component.js.map +1 -1
  77. package/runtime/lib/lynx/dynamic-js.d.ts +5 -1
  78. package/runtime/lib/lynx/dynamic-js.js +1 -0
  79. package/runtime/lib/lynx/dynamic-js.js.map +1 -1
  80. package/runtime/lib/lynx/env.d.ts +1 -1
  81. package/runtime/lib/lynx/env.js +13 -13
  82. package/runtime/lib/lynx/env.js.map +1 -1
  83. package/runtime/lib/lynx/lazy-bundle.js +7 -5
  84. package/runtime/lib/lynx/lazy-bundle.js.map +1 -1
  85. package/runtime/lib/lynx/performance.js +1 -1
  86. package/runtime/lib/lynx/performance.js.map +1 -1
  87. package/runtime/lib/lynx/runWithForce.js +3 -0
  88. package/runtime/lib/lynx/runWithForce.js.map +1 -1
  89. package/runtime/lib/lynx/tt.js +12 -33
  90. package/runtime/lib/lynx/tt.js.map +1 -1
  91. package/runtime/lib/lynx-api.d.ts +1 -1
  92. package/runtime/lib/lynx-api.js +3 -0
  93. package/runtime/lib/lynx-api.js.map +1 -1
  94. package/runtime/lib/lynx.js +6 -6
  95. package/runtime/lib/lynx.js.map +1 -1
  96. package/runtime/lib/opcodes.js +2 -1
  97. package/runtime/lib/opcodes.js.map +1 -1
  98. package/runtime/lib/pendingListUpdates.d.ts +6 -0
  99. package/runtime/lib/pendingListUpdates.js +16 -0
  100. package/runtime/lib/pendingListUpdates.js.map +1 -0
  101. package/runtime/lib/renderToOpcodes/index.js +7 -7
  102. package/runtime/lib/renderToOpcodes/index.js.map +1 -1
  103. package/runtime/lib/snapshot/dynamicPartType.d.ts +12 -0
  104. package/runtime/lib/snapshot/dynamicPartType.js +17 -0
  105. package/runtime/lib/snapshot/dynamicPartType.js.map +1 -0
  106. package/runtime/lib/snapshot/gesture.js +3 -0
  107. package/runtime/lib/snapshot/gesture.js.map +1 -1
  108. package/runtime/lib/snapshot/list.d.ts +3 -0
  109. package/runtime/lib/snapshot/list.js +23 -0
  110. package/runtime/lib/snapshot/list.js.map +1 -0
  111. package/runtime/lib/snapshot/platformInfo.d.ts +10 -0
  112. package/runtime/lib/snapshot/platformInfo.js +6 -3
  113. package/runtime/lib/snapshot/platformInfo.js.map +1 -1
  114. package/runtime/lib/snapshot/ref.d.ts +17 -9
  115. package/runtime/lib/snapshot/ref.js +64 -73
  116. package/runtime/lib/snapshot/ref.js.map +1 -1
  117. package/runtime/lib/snapshot/spread.d.ts +2 -2
  118. package/runtime/lib/snapshot/spread.js +12 -12
  119. package/runtime/lib/snapshot/spread.js.map +1 -1
  120. package/runtime/lib/snapshot/workletEvent.js +1 -1
  121. package/runtime/lib/snapshot/workletEvent.js.map +1 -1
  122. package/runtime/lib/snapshot/workletRef.d.ts +3 -3
  123. package/runtime/lib/snapshot/workletRef.js +27 -8
  124. package/runtime/lib/snapshot/workletRef.js.map +1 -1
  125. package/runtime/lib/snapshot.d.ts +9 -18
  126. package/runtime/lib/snapshot.js +46 -40
  127. package/runtime/lib/snapshot.js.map +1 -1
  128. package/runtime/lib/snapshotInstanceHydrationMap.d.ts +9 -0
  129. package/runtime/lib/snapshotInstanceHydrationMap.js +16 -0
  130. package/runtime/lib/snapshotInstanceHydrationMap.js.map +1 -0
  131. package/runtime/lib/utils.js +1 -1
  132. package/runtime/lib/utils.js.map +1 -1
  133. package/runtime/lib/worklet/workletRef.js +1 -2
  134. package/runtime/lib/worklet/workletRef.js.map +1 -1
  135. package/runtime/src/backgroundSnapshot.ts +75 -52
  136. package/runtime/src/compat/initData.ts +10 -9
  137. package/runtime/src/compat/lynxComponent.ts +12 -13
  138. package/runtime/src/debug/profile.ts +1 -0
  139. package/runtime/src/gesture/processGestureBagkround.ts +5 -1
  140. package/runtime/src/gesture/types.ts +3 -0
  141. package/runtime/src/hooks/react.ts +3 -3
  142. package/runtime/src/hooks/useLynxGlobalEventListener.ts +1 -1
  143. package/runtime/src/hydrate.ts +6 -4
  144. package/runtime/src/internal.ts +3 -2
  145. package/runtime/src/legacy-react-runtime/index.ts +1 -1
  146. package/runtime/src/lifecycle/destroy.ts +1 -0
  147. package/runtime/src/lifecycle/event/delayEvents.ts +3 -0
  148. package/runtime/src/lifecycle/event/delayLifecycleEvents.ts +7 -13
  149. package/runtime/src/lifecycle/event/jsReady.ts +4 -3
  150. package/runtime/src/lifecycle/patch/commit.ts +10 -41
  151. package/runtime/src/lifecycle/patch/error.ts +61 -0
  152. package/runtime/src/lifecycle/patch/isMainThreadHydrationFinished.ts +3 -0
  153. package/runtime/src/lifecycle/patch/snapshotPatch.ts +2 -2
  154. package/runtime/src/lifecycle/patch/snapshotPatchApply.ts +28 -28
  155. package/runtime/src/lifecycle/patch/updateMainThread.ts +7 -16
  156. package/runtime/src/lifecycle/ref/delay.ts +99 -0
  157. package/runtime/src/lifecycle/reload.ts +10 -7
  158. package/runtime/src/lifecycle/render.ts +3 -2
  159. package/runtime/src/lifecycleConstant.ts +11 -7
  160. package/runtime/src/list.ts +33 -269
  161. package/runtime/src/listUpdateInfo.ts +221 -0
  162. package/runtime/src/lynx/calledByNative.ts +15 -10
  163. package/runtime/src/lynx/component.ts +9 -0
  164. package/runtime/src/lynx/dynamic-js.ts +5 -1
  165. package/runtime/src/lynx/env.ts +17 -17
  166. package/runtime/src/lynx/lazy-bundle.ts +22 -14
  167. package/runtime/src/lynx/performance.ts +1 -1
  168. package/runtime/src/lynx/runWithForce.ts +9 -5
  169. package/runtime/src/lynx/tt.ts +19 -37
  170. package/runtime/src/lynx-api.ts +5 -2
  171. package/runtime/src/lynx.ts +7 -6
  172. package/runtime/src/opcodes.ts +2 -1
  173. package/runtime/src/pendingListUpdates.ts +18 -0
  174. package/runtime/src/renderToOpcodes/index.ts +7 -7
  175. package/runtime/src/snapshot/dynamicPartType.ts +16 -0
  176. package/runtime/src/snapshot/gesture.ts +3 -0
  177. package/runtime/src/snapshot/list.ts +36 -0
  178. package/runtime/src/snapshot/platformInfo.ts +19 -5
  179. package/runtime/src/snapshot/ref.ts +78 -87
  180. package/runtime/src/snapshot/spread.ts +47 -22
  181. package/runtime/src/snapshot/workletEvent.ts +1 -1
  182. package/runtime/src/snapshot/workletRef.ts +36 -10
  183. package/runtime/src/snapshot.ts +62 -51
  184. package/runtime/src/snapshotInstanceHydrationMap.ts +17 -0
  185. package/runtime/src/utils.ts +3 -3
  186. package/runtime/src/worklet/workletRef.ts +1 -2
  187. package/testing-library/dist/env/vitest.js +8 -6
  188. package/testing-library/dist/pure.js +4 -3
  189. package/testing-library/dist/vitest.config.js +7 -7
  190. package/transform/cjs/main.cjs +4 -0
  191. package/transform/dist/wasm.cjs +1 -1
  192. package/worklet-runtime/dist/dev.js +1 -4
  193. package/worklet-runtime/dist/main.js +0 -3
  194. package/worklet-runtime/lib/bindings/observers.d.ts +14 -1
  195. package/worklet-runtime/lib/bindings/observers.js +7 -7
  196. package/worklet-runtime/lib/bindings/observers.js.map +1 -1
  197. package/worklet-runtime/lib/hydrate.js +0 -5
  198. package/worklet-runtime/lib/hydrate.js.map +1 -1
  199. package/runtime/lib/lifecycle/delayUnmount.d.ts +0 -8
  200. package/runtime/lib/lifecycle/delayUnmount.js +0 -65
  201. package/runtime/lib/lifecycle/delayUnmount.js.map +0 -1
  202. package/runtime/src/lifecycle/delayUnmount.ts +0 -77
@@ -0,0 +1,99 @@
1
+ // Copyright 2024 The Lynx Authors. All rights reserved.
2
+ // Licensed under the Apache License Version 2.0 that can be found in the
3
+ // LICENSE file in the root directory of this source tree.
4
+
5
+ import type { NodesRef, SelectorQuery } from '@lynx-js/types';
6
+
7
+ import { hydrationMap } from '../../snapshotInstanceHydrationMap.js';
8
+
9
+ type RefTask = (nodesRef: NodesRef) => SelectorQuery;
10
+
11
+ /**
12
+ * A flag to indicate whether UI operations should be delayed.
13
+ * When set to true, UI operations will be queued in the `delayedUiOps` array
14
+ * and executed later when `runDelayedUiOps` is called.
15
+ * This is used before hydration to ensure UI operations are batched
16
+ * and executed at the appropriate time.
17
+ */
18
+ const shouldDelayUiOps = { value: true };
19
+
20
+ /**
21
+ * An array of functions that will be executed later when `runDelayedUiOps` is called.
22
+ * These functions contain UI operations that need to be delayed.
23
+ */
24
+ const delayedUiOps: (() => void)[] = [];
25
+
26
+ /**
27
+ * Runs a task either immediately or delays it based on the `shouldDelayUiOps` flag.
28
+ * @param task - The function to execute.
29
+ */
30
+ function runOrDelay(task: () => void): void {
31
+ if (shouldDelayUiOps.value) {
32
+ delayedUiOps.push(task);
33
+ } else {
34
+ task();
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Executes all delayed UI operations.
40
+ */
41
+ function runDelayedUiOps(): void {
42
+ for (const task of delayedUiOps) {
43
+ task();
44
+ }
45
+ shouldDelayUiOps.value = false;
46
+ delayedUiOps.length = 0;
47
+ }
48
+
49
+ /**
50
+ * A proxy class designed for managing and executing reference-based tasks.
51
+ * It delays the execution of tasks until hydration is complete.
52
+ */
53
+ class RefProxy {
54
+ private readonly refAttr: [snapshotInstanceId: number, expIndex: number];
55
+ private task: RefTask | undefined;
56
+
57
+ constructor(refAttr: [snapshotInstanceId: number, expIndex: number]) {
58
+ this.refAttr = refAttr;
59
+ }
60
+
61
+ private setTask<K extends keyof NodesRef>(
62
+ method: K,
63
+ args: Parameters<NodesRef[K]>,
64
+ ): this {
65
+ this.task = (nodesRef) => {
66
+ return (nodesRef[method] as unknown as (...args: any[]) => SelectorQuery)(...args);
67
+ };
68
+ return this;
69
+ }
70
+
71
+ invoke(...args: Parameters<NodesRef['invoke']>): RefProxy {
72
+ return new RefProxy(this.refAttr).setTask('invoke', args);
73
+ }
74
+
75
+ path(...args: Parameters<NodesRef['path']>): RefProxy {
76
+ return new RefProxy(this.refAttr).setTask('path', args);
77
+ }
78
+
79
+ fields(...args: Parameters<NodesRef['fields']>): RefProxy {
80
+ return new RefProxy(this.refAttr).setTask('fields', args);
81
+ }
82
+
83
+ setNativeProps(...args: Parameters<NodesRef['setNativeProps']>): RefProxy {
84
+ return new RefProxy(this.refAttr).setTask('setNativeProps', args);
85
+ }
86
+
87
+ exec(): void {
88
+ runOrDelay(() => {
89
+ const realRefId = hydrationMap.get(this.refAttr[0]) ?? this.refAttr[0];
90
+ const refSelector = `[react-ref-${realRefId}-${this.refAttr[1]}]`;
91
+ this.task!(lynx.createSelectorQuery().select(refSelector)).exec();
92
+ });
93
+ }
94
+ }
95
+
96
+ /**
97
+ * @internal
98
+ */
99
+ export { RefProxy, runDelayedUiOps, shouldDelayUiOps };
@@ -11,27 +11,28 @@ import { render } from 'preact';
11
11
 
12
12
  import { hydrate } from '../hydrate.js';
13
13
  import { LifecycleConstant } from '../lifecycleConstant.js';
14
- import { __pendingListUpdates } from '../list.js';
14
+ import { __pendingListUpdates } from '../pendingListUpdates.js';
15
15
  import { __root, setRoot } from '../root.js';
16
+ import { destroyBackground } from './destroy.js';
17
+ import { applyRefQueue } from '../snapshot/workletRef.js';
16
18
  import { SnapshotInstance, __page, snapshotInstanceManager } from '../snapshot.js';
17
- import { takeGlobalRefPatchMap } from '../snapshot/ref.js';
18
19
  import { isEmptyObject } from '../utils.js';
19
20
  import { destroyWorklet } from '../worklet/destroy.js';
20
- import { destroyBackground } from './destroy.js';
21
21
  import { clearJSReadyEventIdSwap, isJSReady } from './event/jsReady.js';
22
22
  import { increaseReloadVersion } from './pass.js';
23
+ import { setMainThreadHydrationFinished } from './patch/isMainThreadHydrationFinished.js';
23
24
  import { deinitGlobalSnapshotPatch } from './patch/snapshotPatch.js';
25
+ import { shouldDelayUiOps } from './ref/delay.js';
24
26
  import { renderMainThread } from './render.js';
25
- import { setMainThreadHydrationFinished } from './patch/isMainThreadHydrationFinished.js';
26
27
 
27
- function reloadMainThread(data: any, options: UpdatePageOption): void {
28
+ function reloadMainThread(data: unknown, options: UpdatePageOption): void {
28
29
  if (__PROFILE__) {
29
30
  console.profile('reloadTemplate');
30
31
  }
31
32
 
32
33
  increaseReloadVersion();
33
34
 
34
- if (typeof data == 'object' && !isEmptyObject(data)) {
35
+ if (typeof data == 'object' && !isEmptyObject(data as Record<string, unknown>)) {
35
36
  Object.assign(lynx.__initData, data);
36
37
  }
37
38
 
@@ -51,13 +52,13 @@ function reloadMainThread(data: any, options: UpdatePageOption): void {
51
52
 
52
53
  // always call this before `__FlushElementTree`
53
54
  __pendingListUpdates.flush();
55
+ applyRefQueue();
54
56
 
55
57
  if (isJSReady) {
56
58
  __OnLifecycleEvent([
57
59
  LifecycleConstant.firstScreen, /* FIRST_SCREEN */
58
60
  {
59
61
  root: JSON.stringify(__root),
60
- refPatch: JSON.stringify(takeGlobalRefPatchMap()),
61
62
  },
62
63
  ]);
63
64
  }
@@ -84,6 +85,8 @@ function reloadBackground(updateData: Record<string, any>): void {
84
85
  // COW when modify `lynx.__initData` to make sure Provider & Consumer works
85
86
  lynx.__initData = Object.assign({}, lynx.__initData, updateData);
86
87
 
88
+ shouldDelayUiOps.value = true;
89
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
87
90
  render(__root.__jsx, __root as any);
88
91
 
89
92
  if (__PROFILE__) {
@@ -18,6 +18,7 @@ function renderMainThread(): void {
18
18
  process.env['NODE_ENV'] === 'test' && typeof __TESTING_FORCE_RENDER_TO_OPCODE__ !== 'undefined'
19
19
  && !__TESTING_FORCE_RENDER_TO_OPCODE__
20
20
  ) {
21
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
21
22
  render(__root.__jsx, __root as any);
22
23
  } else {
23
24
  let opcodes;
@@ -25,8 +26,7 @@ function renderMainThread(): void {
25
26
  if (__PROFILE__) {
26
27
  console.profile('renderToString');
27
28
  }
28
- // @ts-ignore
29
- opcodes = renderToString(__root.__jsx);
29
+ opcodes = renderToString(__root.__jsx, undefined);
30
30
  } catch (e) {
31
31
  lynx.reportError(e as Error);
32
32
  opcodes = [];
@@ -39,6 +39,7 @@ function renderMainThread(): void {
39
39
  if (__PROFILE__) {
40
40
  console.profile('renderOpcodesInto');
41
41
  }
42
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
42
43
  renderOpcodesInto(opcodes, __root as any);
43
44
  if (__ENABLE_SSR__) {
44
45
  __root.__opcodes = opcodes;
@@ -1,13 +1,17 @@
1
1
  // Copyright 2024 The Lynx Authors. All rights reserved.
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
- export class LifecycleConstant {
5
- public static readonly firstScreen = 'rLynxFirstScreen';
6
- public static readonly updateFromRoot = 'updateFromRoot';
7
- public static readonly globalEventFromLepus = 'globalEventFromLepus';
8
- public static readonly ref = 'rLynxRef';
9
- public static readonly jsReady = 'rLynxJSReady';
10
- public static readonly patchUpdate = 'rLynxChange';
4
+ export const enum LifecycleConstant {
5
+ firstScreen = 'rLynxFirstScreen',
6
+ updateFromRoot = 'updateFromRoot',
7
+ globalEventFromLepus = 'globalEventFromLepus',
8
+ jsReady = 'rLynxJSReady',
9
+ patchUpdate = 'rLynxChange',
10
+ }
11
+
12
+ export interface FirstScreenData {
13
+ root: string;
14
+ jsReadyEventIdSwap: Record<string | number, number>;
11
15
  }
12
16
 
13
17
  export const enum NativeUpdateDataType {
@@ -1,240 +1,12 @@
1
1
  // Copyright 2024 The Lynx Authors. All rights reserved.
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
- import { hydrate } from './hydrate.js';
5
- import { commitMainThreadPatchUpdate } from './lifecycle/patch/updateMainThread.js';
4
+ import { applyRefQueue } from './snapshot/workletRef.js';
6
5
  import type { SnapshotInstance } from './snapshot.js';
7
6
 
8
- export interface ListUpdateInfo {
9
- flush(): void;
10
- onInsertBefore(
11
- newNode: SnapshotInstance,
12
- existingNode?: SnapshotInstance,
13
- ): void;
14
- onRemoveChild(child: SnapshotInstance): void;
15
- onSetAttribute(child: SnapshotInstance, attr: any, oldAttr: any): void;
16
- }
17
-
18
- interface InsertAction {
19
- position: number;
20
- type: string;
21
- }
22
-
23
- interface UpdateAction {
24
- from: number;
25
- to: number;
26
- type: string;
27
- flush: boolean;
28
- }
29
-
30
- interface ListOperations {
31
- insertAction: InsertAction[];
32
- removeAction: number[];
33
- updateAction: UpdateAction[];
34
- }
35
-
36
- // class ListUpdateInfoDiffing implements ListUpdateInfo {
37
- // private oldChildNodes: SnapshotInstance[];
38
- // constructor(private list: SnapshotInstance) {
39
- // this.oldChildNodes = list.childNodes;
40
- // }
41
- // flush(): void {
42
- // Object.defineProperty(SnapshotInstance.prototype, "key", {
43
- // get: function () {
44
- // return this.values[0]["item-key"];
45
- // },
46
- // });
47
-
48
- // }
49
- // onInsertBefore(newNode: SnapshotInstance, existingNode?: SnapshotInstance | undefined): void {}
50
- // onRemoveChild(child: SnapshotInstance): void {}
51
- // onSetAttribute(child: SnapshotInstance, attr: any): void {
52
- // throw new Error("Method not implemented.");
53
- // }
54
- // }
55
-
56
- export class ListUpdateInfoRecording implements ListUpdateInfo {
57
- constructor(private list: SnapshotInstance) {
58
- this.oldChildNodes = list.childNodes;
59
- // this.oldChildNodesSet = new Set(this.oldChildNodes);
60
- }
61
-
62
- // private __commitAndReset() {
63
- // (this.__pendingAttributes ??= []).push(this.__toAttribute());
64
- // this.oldChildNodes = this.list.childNodes;
65
- // this.oldChildNodesSet = new Set(this.oldChildNodes);
66
- // this.removeChild1.clear();
67
- // this.removeChild2.clear();
68
- // this.insertBefore.clear();
69
- // this.appendChild.length = 0;
70
- // this.platformInfoUpdate.clear();
71
- // }
72
-
73
- flush(): void {
74
- const elementIndex = this.list.__snapshot_def.slot[0]![1];
75
- const listElement = this.list.__elements![elementIndex]!;
76
- // this.__pendingAttributes?.forEach(pendingAttribute => {
77
- // __SetAttribute(listElement, "update-list-info", pendingAttribute);
78
- // __FlushElementTree(listElement);
79
- // });
80
- __SetAttribute(listElement, 'update-list-info', this.__toAttribute());
81
- const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(this.list.childNodes);
82
- __UpdateListCallbacks(
83
- listElement,
84
- componentAtIndex,
85
- enqueueComponentFactory(),
86
- componentAtIndexes,
87
- );
88
- }
89
-
90
- private oldChildNodes: SnapshotInstance[];
91
- // private oldChildNodesSet: Set<SnapshotInstance>;
92
- private removeChild = new Set<SnapshotInstance>();
93
- private insertBefore = new Map<SnapshotInstance, SnapshotInstance[]>(); // insert V before K
94
- private appendChild = [] as SnapshotInstance[];
95
- private platformInfoUpdate = new Map<SnapshotInstance, any>();
96
-
97
- onInsertBefore(newNode: SnapshotInstance, existingNode?: SnapshotInstance): void {
98
- // @ts-ignore
99
- if (newNode.__parent) {
100
- // if (!this.oldChildNodesSet.has(newNode)) {
101
- // this.__commitAndReset();
102
- // }
103
- this.removeChild.add(newNode);
104
- }
105
- if (existingNode) {
106
- // if (!this.oldChildNodesSet.has(existingNode)) {
107
- // this.__commitAndReset();
108
- // }
109
- const newChildren = this.insertBefore.get(existingNode) ?? [];
110
- newChildren.push(newNode);
111
- this.insertBefore.set(existingNode, newChildren);
112
- } else {
113
- this.appendChild.push(newNode);
114
- }
115
- }
116
-
117
- onRemoveChild(child: SnapshotInstance): void {
118
- // if (!this.oldChildNodesSet.has(child)) {
119
- // this.__commitAndReset();
120
- // }
121
- this.removeChild.add(child);
122
- }
123
-
124
- onSetAttribute(child: SnapshotInstance, attr: any, _oldAttr: any): void {
125
- this.platformInfoUpdate.set(child, attr);
126
- }
127
-
128
- private __toAttribute(): ListOperations {
129
- const { removeChild, insertBefore, appendChild, platformInfoUpdate } = this;
130
-
131
- const removals: number[] = [];
132
- const insertions: InsertAction[] = [];
133
- const updates: UpdateAction[] = [];
134
-
135
- let j = 0;
136
- for (let i = 0; i < this.oldChildNodes.length; i++, j++) {
137
- const child = this.oldChildNodes[i]!;
138
- if (platformInfoUpdate.has(child)) {
139
- updates.push({
140
- ...platformInfoUpdate.get(child),
141
- from: +j,
142
- to: +j,
143
- // no flush
144
- flush: false,
145
- type: child.type,
146
- });
147
- }
148
- if (insertBefore.has(child)) {
149
- const children = insertBefore.get(child)!;
150
- children.forEach(c => {
151
- insertions.push({
152
- position: j,
153
- type: c.type,
154
- ...c.__listItemPlatformInfo,
155
- });
156
- j++;
157
- });
158
- }
159
- if (removeChild.has(child)) {
160
- removals.push(i);
161
- removeChild.delete(child);
162
- j--;
163
- }
164
- }
165
- for (let i = 0; i < appendChild.length; i++) {
166
- const child = appendChild[i]!;
167
- insertions.push({
168
- position: j + i,
169
- type: child.type,
170
- ...child.__listItemPlatformInfo,
171
- });
172
- }
173
-
174
- insertions.sort((a, b) => a.position - b.position);
175
- removals.sort((a, b) => a - b);
176
-
177
- if (
178
- SystemInfo.lynxSdkVersion === '2.14'
179
- || SystemInfo.lynxSdkVersion === '2.15'
180
- || SystemInfo.lynxSdkVersion === '2.16'
181
- || SystemInfo.lynxSdkVersion === '2.17'
182
- || SystemInfo.lynxSdkVersion === '2.18'
183
- ) {
184
- const elementIndex = this.list.__snapshot_def.slot[0]![1];
185
- const listElement = this.list.__elements![elementIndex]!;
186
-
187
- // `__GetAttributeByName` is available since Lynx 2.14
188
- if (__GetAttributeByName(listElement, 'custom-list-name') === 'list-container') {
189
- // `updateAction` must be full (not incremental) when Lynx version <= 2.18 and
190
- // when `custom-list-name` is `list-container` (available when Lynx version >= 2.14) is true,
191
- updates.length = 0;
192
- this.list.childNodes.forEach((child, index) => {
193
- updates.push({
194
- ...child.__listItemPlatformInfo,
195
- from: index,
196
- to: index,
197
- // no flush
198
- flush: false,
199
- type: child.type,
200
- });
201
- });
202
- }
203
- }
204
-
205
- return {
206
- insertAction: insertions,
207
- removeAction: removals,
208
- updateAction: updates,
209
- };
210
- }
211
-
212
- toJSON(): [ListOperations] {
213
- // if (this.__pendingAttributes) {
214
- // return [...this.__pendingAttributes, this.__toAttribute()];
215
- // } else {
216
- // return [this.__toAttribute()];
217
- // }
218
-
219
- return [this.__toAttribute()] as const;
220
- }
221
- }
222
-
223
- export const __pendingListUpdates = {
224
- values: {} as Record<number, ListUpdateInfo>,
225
- clear(): void {
226
- this.values = {};
227
- },
228
- flush(): void {
229
- Object.values(this.values).forEach(update => {
230
- update.flush();
231
- });
232
- this.clear();
233
- },
234
- };
235
-
236
7
  export const gSignMap: Record<number, Map<number, SnapshotInstance>> = {};
237
8
  export const gRecycleMap: Record<number, Map<string, Map<number, SnapshotInstance>>> = {};
9
+ const gParentWeakMap: WeakMap<SnapshotInstance, unknown> = new WeakMap();
238
10
 
239
11
  export function clearListGlobal(): void {
240
12
  for (const key in gSignMap) {
@@ -247,7 +19,25 @@ export function clearListGlobal(): void {
247
19
 
248
20
  export function componentAtIndexFactory(
249
21
  ctx: SnapshotInstance[],
22
+ hydrateFunction: (before: SnapshotInstance, after: SnapshotInstance) => void,
250
23
  ): [ComponentAtIndexCallback, ComponentAtIndexesCallback] {
24
+ // A hack workaround to ensure childCtx has no direct reference through `__parent` to list,
25
+ // to avoid memory leak.
26
+ // TODO(hzy): make `__parent` a WeakRef or `#__parent` in the future.
27
+ ctx.forEach((childCtx) => {
28
+ if (gParentWeakMap.has(childCtx)) {
29
+ // do it only once
30
+ } else {
31
+ gParentWeakMap.set(childCtx, childCtx.parentNode!);
32
+ Object.defineProperty(childCtx, '__parent', {
33
+ get: () => gParentWeakMap.get(childCtx)!,
34
+ set: (value: unknown) => {
35
+ gParentWeakMap.set(childCtx, value);
36
+ },
37
+ });
38
+ }
39
+ });
40
+
251
41
  const componentAtIndex = (
252
42
  list: FiberElement,
253
43
  listID: number,
@@ -268,7 +58,7 @@ export function componentAtIndexFactory(
268
58
  throw new Error('childCtx not found');
269
59
  }
270
60
 
271
- const platformInfo = childCtx.__listItemPlatformInfo || {};
61
+ const platformInfo = childCtx.__listItemPlatformInfo ?? {};
272
62
 
273
63
  const uniqID = childCtx.type + (platformInfo['reuse-identifier'] ?? '');
274
64
  const recycleSignMap = recycleMap.get(uniqID);
@@ -294,10 +84,9 @@ export function componentAtIndexFactory(
294
84
  __FlushElementTree(root, { triggerLayout: true, operationID, elementID: sign, listID });
295
85
  } else if (enableBatchRender && asyncFlush) {
296
86
  __FlushElementTree(root, { asyncFlush: true });
297
- } else {
298
- // enableBatchRender == true && asyncFlush == false
299
- // in this case, no need to invoke __FlushElementTree because in the end of componentAtIndexes(), the list will invoke __FlushElementTree.
300
87
  }
88
+ // enableBatchRender == true && asyncFlush == false
89
+ // in this case, no need to invoke __FlushElementTree because in the end of componentAtIndexes(), the list will invoke __FlushElementTree.
301
90
  return sign;
302
91
  } else {
303
92
  const newCtx = childCtx.takeElements();
@@ -309,9 +98,13 @@ export function componentAtIndexFactory(
309
98
  const [first] = recycleSignMap;
310
99
  const [sign, oldCtx] = first!;
311
100
  recycleSignMap.delete(sign);
312
- hydrate(oldCtx, childCtx);
101
+ hydrateFunction(oldCtx, childCtx);
313
102
  oldCtx.unRenderElements();
103
+ if (!oldCtx.__id) {
104
+ oldCtx.tearDown();
105
+ }
314
106
  const root = childCtx.__element_root!;
107
+ applyRefQueue();
315
108
  if (!enableBatchRender) {
316
109
  const flushOptions: FlushOptions = {
317
110
  triggerLayout: true,
@@ -322,7 +115,7 @@ export function componentAtIndexFactory(
322
115
  if (enableReuseNotification) {
323
116
  flushOptions.listReuseNotification = {
324
117
  listElement: list,
325
- itemKey: platformInfo['item-key'],
118
+ itemKey: platformInfo['item-key']!,
326
119
  };
327
120
  }
328
121
  __FlushElementTree(root, flushOptions);
@@ -333,13 +126,12 @@ export function componentAtIndexFactory(
333
126
  if (enableReuseNotification) {
334
127
  flushOptions.listReuseNotification = {
335
128
  listElement: list,
336
- itemKey: platformInfo['item-key'],
129
+ itemKey: platformInfo['item-key']!,
337
130
  };
338
131
  }
339
132
  __FlushElementTree(root, flushOptions);
340
133
  }
341
134
  signMap.set(sign, childCtx);
342
- commitMainThreadPatchUpdate(undefined);
343
135
  return sign;
344
136
  }
345
137
 
@@ -347,6 +139,7 @@ export function componentAtIndexFactory(
347
139
  const root = childCtx.__element_root!;
348
140
  __AppendElement(list, root);
349
141
  const sign = __GetElementUniqueID(root);
142
+ applyRefQueue();
350
143
  if (!enableBatchRender) {
351
144
  __FlushElementTree(root, {
352
145
  triggerLayout: true,
@@ -360,7 +153,6 @@ export function componentAtIndexFactory(
360
153
  });
361
154
  }
362
155
  signMap.set(sign, childCtx);
363
- commitMainThreadPatchUpdate(undefined);
364
156
  return sign;
365
157
  };
366
158
 
@@ -387,6 +179,7 @@ export function componentAtIndexFactory(
387
179
  }
388
180
 
389
181
  export function enqueueComponentFactory(): EnqueueComponentCallback {
182
+ // eslint-disable-next-line unicorn/consistent-function-scoping
390
183
  const enqueueComponent = (_: FiberElement, listID: number, sign: number) => {
391
184
  const signMap = gSignMap[listID];
392
185
  const recycleMap = gRecycleMap[listID];
@@ -399,7 +192,7 @@ export function enqueueComponentFactory(): EnqueueComponentCallback {
399
192
  return;
400
193
  }
401
194
 
402
- const platformInfo = childCtx.__listItemPlatformInfo || {};
195
+ const platformInfo = childCtx.__listItemPlatformInfo ?? {};
403
196
 
404
197
  const uniqID = childCtx.type + (platformInfo['reuse-identifier'] ?? '');
405
198
  if (!recycleMap.has(uniqID)) {
@@ -409,32 +202,3 @@ export function enqueueComponentFactory(): EnqueueComponentCallback {
409
202
  };
410
203
  return enqueueComponent;
411
204
  }
412
-
413
- export function snapshotCreateList(
414
- pageId: number,
415
- _ctx: SnapshotInstance,
416
- _expIndex: number,
417
- ): FiberElement {
418
- const signMap = new Map<number, SnapshotInstance>();
419
- const recycleMap = new Map<string, Map<number, SnapshotInstance>>();
420
- const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory([]);
421
- const list = __CreateList(
422
- pageId,
423
- componentAtIndex,
424
- enqueueComponentFactory(),
425
- {},
426
- componentAtIndexes,
427
- );
428
- const listID = __GetElementUniqueID(list);
429
- gSignMap[listID] = signMap;
430
- gRecycleMap[listID] = recycleMap;
431
- return list;
432
- }
433
-
434
- export function snapshotDestroyList(si: SnapshotInstance): void {
435
- const [, elementIndex] = si.__snapshot_def.slot[0]!;
436
- const list = si.__elements![elementIndex]!;
437
- const listID = __GetElementUniqueID(list);
438
- delete gSignMap[listID];
439
- delete gRecycleMap[listID];
440
- }