@lynx-js/react 0.109.1 → 0.110.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.
Files changed (143) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/package.json +2 -2
  3. package/refresh/.turbo/turbo-build.log +1 -1
  4. package/refresh/package.json +1 -1
  5. package/runtime/lib/backgroundSnapshot.d.ts +2 -1
  6. package/runtime/lib/backgroundSnapshot.js +65 -42
  7. package/runtime/lib/backgroundSnapshot.js.map +1 -1
  8. package/runtime/lib/hooks/react.js +3 -3
  9. package/runtime/lib/hooks/react.js.map +1 -1
  10. package/runtime/lib/lifecycle/destroy.js +1 -0
  11. package/runtime/lib/lifecycle/destroy.js.map +1 -1
  12. package/runtime/lib/lifecycle/event/delayEvents.js +3 -0
  13. package/runtime/lib/lifecycle/event/delayEvents.js.map +1 -1
  14. package/runtime/lib/lifecycle/event/delayLifecycleEvents.d.ts +3 -2
  15. package/runtime/lib/lifecycle/event/delayLifecycleEvents.js +1 -12
  16. package/runtime/lib/lifecycle/event/delayLifecycleEvents.js.map +1 -1
  17. package/runtime/lib/lifecycle/event/jsReady.d.ts +1 -1
  18. package/runtime/lib/lifecycle/event/jsReady.js +3 -2
  19. package/runtime/lib/lifecycle/event/jsReady.js.map +1 -1
  20. package/runtime/lib/lifecycle/patch/commit.d.ts +0 -2
  21. package/runtime/lib/lifecycle/patch/commit.js +8 -36
  22. package/runtime/lib/lifecycle/patch/commit.js.map +1 -1
  23. package/runtime/lib/lifecycle/patch/error.d.ts +8 -0
  24. package/runtime/lib/lifecycle/patch/error.js +47 -0
  25. package/runtime/lib/lifecycle/patch/error.js.map +1 -0
  26. package/runtime/lib/lifecycle/patch/isMainThreadHydrationFinished.js +3 -0
  27. package/runtime/lib/lifecycle/patch/isMainThreadHydrationFinished.js.map +1 -1
  28. package/runtime/lib/lifecycle/patch/snapshotPatch.d.ts +2 -2
  29. package/runtime/lib/lifecycle/patch/snapshotPatch.js.map +1 -1
  30. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js +18 -11
  31. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js.map +1 -1
  32. package/runtime/lib/lifecycle/patch/updateMainThread.d.ts +0 -1
  33. package/runtime/lib/lifecycle/patch/updateMainThread.js +6 -13
  34. package/runtime/lib/lifecycle/patch/updateMainThread.js.map +1 -1
  35. package/runtime/lib/lifecycle/ref/delay.d.ts +31 -0
  36. package/runtime/lib/lifecycle/ref/delay.js +80 -0
  37. package/runtime/lib/lifecycle/ref/delay.js.map +1 -0
  38. package/runtime/lib/lifecycle/reload.d.ts +1 -1
  39. package/runtime/lib/lifecycle/reload.js +7 -4
  40. package/runtime/lib/lifecycle/reload.js.map +1 -1
  41. package/runtime/lib/lifecycle/render.js +3 -2
  42. package/runtime/lib/lifecycle/render.js.map +1 -1
  43. package/runtime/lib/lifecycleConstant.d.ts +10 -7
  44. package/runtime/lib/lifecycleConstant.js +8 -8
  45. package/runtime/lib/lifecycleConstant.js.map +1 -1
  46. package/runtime/lib/list.d.ts +1 -1
  47. package/runtime/lib/list.js +9 -7
  48. package/runtime/lib/list.js.map +1 -1
  49. package/runtime/lib/lynx/calledByNative.js +8 -5
  50. package/runtime/lib/lynx/calledByNative.js.map +1 -1
  51. package/runtime/lib/lynx/component.js +7 -0
  52. package/runtime/lib/lynx/component.js.map +1 -1
  53. package/runtime/lib/lynx/dynamic-js.d.ts +5 -1
  54. package/runtime/lib/lynx/dynamic-js.js +1 -0
  55. package/runtime/lib/lynx/dynamic-js.js.map +1 -1
  56. package/runtime/lib/lynx/env.d.ts +1 -1
  57. package/runtime/lib/lynx/env.js +13 -13
  58. package/runtime/lib/lynx/env.js.map +1 -1
  59. package/runtime/lib/lynx/lazy-bundle.js +7 -5
  60. package/runtime/lib/lynx/lazy-bundle.js.map +1 -1
  61. package/runtime/lib/lynx/performance.js +1 -1
  62. package/runtime/lib/lynx/performance.js.map +1 -1
  63. package/runtime/lib/lynx/runWithForce.js +3 -0
  64. package/runtime/lib/lynx/runWithForce.js.map +1 -1
  65. package/runtime/lib/lynx/tt.js +12 -33
  66. package/runtime/lib/lynx/tt.js.map +1 -1
  67. package/runtime/lib/lynx-api.d.ts +1 -1
  68. package/runtime/lib/lynx.js +5 -6
  69. package/runtime/lib/lynx.js.map +1 -1
  70. package/runtime/lib/snapshot/ref.d.ts +14 -9
  71. package/runtime/lib/snapshot/ref.js +64 -73
  72. package/runtime/lib/snapshot/ref.js.map +1 -1
  73. package/runtime/lib/snapshot/spread.js +9 -8
  74. package/runtime/lib/snapshot/spread.js.map +1 -1
  75. package/runtime/lib/snapshot/workletRef.d.ts +3 -3
  76. package/runtime/lib/snapshot/workletRef.js +27 -8
  77. package/runtime/lib/snapshot/workletRef.js.map +1 -1
  78. package/runtime/lib/snapshot.d.ts +4 -4
  79. package/runtime/lib/snapshot.js +28 -13
  80. package/runtime/lib/snapshot.js.map +1 -1
  81. package/runtime/lib/snapshotInstanceHydrationMap.d.ts +9 -0
  82. package/runtime/lib/snapshotInstanceHydrationMap.js +16 -0
  83. package/runtime/lib/snapshotInstanceHydrationMap.js.map +1 -0
  84. package/runtime/lib/worklet/workletRef.js +1 -2
  85. package/runtime/lib/worklet/workletRef.js.map +1 -1
  86. package/runtime/src/backgroundSnapshot.ts +74 -47
  87. package/runtime/src/hooks/react.ts +3 -3
  88. package/runtime/src/lifecycle/destroy.ts +1 -0
  89. package/runtime/src/lifecycle/event/delayEvents.ts +3 -0
  90. package/runtime/src/lifecycle/event/delayLifecycleEvents.ts +7 -13
  91. package/runtime/src/lifecycle/event/jsReady.ts +4 -3
  92. package/runtime/src/lifecycle/patch/commit.ts +10 -41
  93. package/runtime/src/lifecycle/patch/error.ts +61 -0
  94. package/runtime/src/lifecycle/patch/isMainThreadHydrationFinished.ts +3 -0
  95. package/runtime/src/lifecycle/patch/snapshotPatch.ts +2 -2
  96. package/runtime/src/lifecycle/patch/snapshotPatchApply.ts +35 -28
  97. package/runtime/src/lifecycle/patch/updateMainThread.ts +7 -16
  98. package/runtime/src/lifecycle/ref/delay.ts +99 -0
  99. package/runtime/src/lifecycle/reload.ts +9 -6
  100. package/runtime/src/lifecycle/render.ts +3 -2
  101. package/runtime/src/lifecycleConstant.ts +11 -7
  102. package/runtime/src/list.ts +9 -7
  103. package/runtime/src/lynx/calledByNative.ts +14 -9
  104. package/runtime/src/lynx/component.ts +9 -0
  105. package/runtime/src/lynx/dynamic-js.ts +5 -1
  106. package/runtime/src/lynx/env.ts +17 -17
  107. package/runtime/src/lynx/lazy-bundle.ts +22 -14
  108. package/runtime/src/lynx/performance.ts +1 -1
  109. package/runtime/src/lynx/runWithForce.ts +9 -5
  110. package/runtime/src/lynx/tt.ts +19 -37
  111. package/runtime/src/lynx-api.ts +1 -1
  112. package/runtime/src/lynx.ts +6 -6
  113. package/runtime/src/snapshot/ref.ts +77 -87
  114. package/runtime/src/snapshot/spread.ts +8 -8
  115. package/runtime/src/snapshot/workletRef.ts +35 -9
  116. package/runtime/src/snapshot.ts +39 -20
  117. package/runtime/src/snapshotInstanceHydrationMap.ts +17 -0
  118. package/runtime/src/worklet/workletRef.ts +1 -2
  119. package/testing-library/dist/env/vitest.js +10 -8
  120. package/testing-library/dist/index.js +3 -3
  121. package/testing-library/dist/pure.js +93 -92
  122. package/testing-library/dist/vitest-global-setup.js +41 -41
  123. package/testing-library/dist/vitest.config.js +17 -14
  124. package/transform/dist/wasm.cjs +1 -1
  125. package/worklet-runtime/dist/dev.js +1 -4
  126. package/worklet-runtime/dist/main.js +0 -4
  127. package/worklet-runtime/lib/bindings/bindings.d.ts +1 -1
  128. package/worklet-runtime/lib/bindings/bindings.js +1 -26
  129. package/worklet-runtime/lib/bindings/bindings.js.map +1 -1
  130. package/worklet-runtime/lib/bindings/index.d.ts +2 -1
  131. package/worklet-runtime/lib/bindings/index.js +2 -1
  132. package/worklet-runtime/lib/bindings/index.js.map +1 -1
  133. package/worklet-runtime/lib/bindings/observers.d.ts +14 -0
  134. package/worklet-runtime/lib/bindings/observers.js +31 -0
  135. package/worklet-runtime/lib/bindings/observers.js.map +1 -0
  136. package/worklet-runtime/lib/hydrate.js +0 -5
  137. package/worklet-runtime/lib/hydrate.js.map +1 -1
  138. package/runtime/lib/lifecycle/delayUnmount.d.ts +0 -8
  139. package/runtime/lib/lifecycle/delayUnmount.js +0 -65
  140. package/runtime/lib/lifecycle/delayUnmount.js.map +0 -1
  141. package/runtime/src/lifecycle/delayUnmount.ts +0 -77
  142. package/worklet-runtime/dist/dev.js.map +0 -8
  143. package/worklet-runtime/dist/main.js.map +0 -8
@@ -21,7 +21,6 @@
21
21
 
22
22
  import type { VNode } from 'preact';
23
23
  import { options } from 'preact';
24
- import type { Component } from 'preact/compat';
25
24
 
26
25
  import { LifecycleConstant } from '../../lifecycleConstant.js';
27
26
  import {
@@ -31,19 +30,18 @@ import {
31
30
  markTimingLegacy,
32
31
  setPipeline,
33
32
  } from '../../lynx/performance.js';
34
- import { CATCH_ERROR, COMMIT, RENDER_CALLBACKS, VNODE } from '../../renderToOpcodes/constants.js';
33
+ import { COMMIT } from '../../renderToOpcodes/constants.js';
34
+ import { applyQueuedRefs } from '../../snapshot/ref.js';
35
35
  import { backgroundSnapshotInstanceManager } from '../../snapshot.js';
36
- import { updateBackgroundRefs } from '../../snapshot/ref.js';
37
36
  import { isEmptyObject } from '../../utils.js';
38
37
  import { takeWorkletRefInitValuePatch } from '../../worklet/workletRefPool.js';
39
- import { runDelayedUnmounts, takeDelayedUnmounts } from '../delayUnmount.js';
40
38
  import { getReloadVersion } from '../pass.js';
41
39
  import type { SnapshotPatch } from './snapshotPatch.js';
42
40
  import { takeGlobalSnapshotPatch } from './snapshotPatch.js';
43
41
 
44
42
  let globalFlushOptions: FlushOptions = {};
45
43
 
46
- const globalCommitTaskMap: Map<number, () => void> = /*@__PURE__*/ new Map();
44
+ const globalCommitTaskMap: Map<number, () => void> = /*@__PURE__*/ new Map<number, () => void>();
47
45
  let nextCommitTaskId = 1;
48
46
 
49
47
  let globalBackgroundSnapshotInstancesToRemove: number[] = [];
@@ -52,6 +50,7 @@ let globalBackgroundSnapshotInstancesToRemove: number[] = [];
52
50
  * A single patch operation.
53
51
  */
54
52
  interface Patch {
53
+ // TODO: ref: do we need `id`?
55
54
  id: number;
56
55
  snapshotPatch?: SnapshotPatch;
57
56
  workletRefInitValuePatch?: [id: number, value: unknown][];
@@ -78,6 +77,7 @@ interface PatchOptions {
78
77
  * Replaces Preact's default commit hook with our custom implementation
79
78
  */
80
79
  function replaceCommitHook(): void {
80
+ // This is actually not used since Preact use `hooks._commit` for callbacks of `useLayoutEffect`.
81
81
  const originalPreactCommit = options[COMMIT];
82
82
  const commit = async (vnode: VNode, commitQueue: any[]) => {
83
83
  // Skip commit phase for MT runtime
@@ -91,20 +91,6 @@ function replaceCommitHook(): void {
91
91
  markTimingLegacy(PerformanceTimingKeys.updateDiffVdomEnd);
92
92
  markTiming(PerformanceTimingKeys.diffVdomEnd);
93
93
 
94
- // The callback functions to be called after components are rendered.
95
- const renderCallbacks = commitQueue.map((component: Component<any>) => {
96
- const ret = {
97
- component,
98
- [RENDER_CALLBACKS]: component[RENDER_CALLBACKS],
99
- [VNODE]: component[VNODE],
100
- };
101
- component[RENDER_CALLBACKS] = [];
102
- return ret;
103
- });
104
- commitQueue.length = 0;
105
-
106
- const delayedUnmounts = takeDelayedUnmounts();
107
-
108
94
  const backgroundSnapshotInstancesToRemove = globalBackgroundSnapshotInstancesToRemove;
109
95
  globalBackgroundSnapshotInstancesToRemove = [];
110
96
 
@@ -112,18 +98,6 @@ function replaceCommitHook(): void {
112
98
 
113
99
  // Register the commit task
114
100
  globalCommitTaskMap.set(commitTaskId, () => {
115
- updateBackgroundRefs(commitTaskId);
116
- runDelayedUnmounts(delayedUnmounts);
117
- originalPreactCommit?.(vnode, renderCallbacks);
118
- renderCallbacks.some(wrapper => {
119
- try {
120
- wrapper[RENDER_CALLBACKS].some((cb: (this: Component) => void) => {
121
- cb.call(wrapper.component);
122
- });
123
- } catch (e) {
124
- options[CATCH_ERROR](e, wrapper[VNODE]!);
125
- }
126
- });
127
101
  if (backgroundSnapshotInstancesToRemove.length) {
128
102
  setTimeout(() => {
129
103
  backgroundSnapshotInstancesToRemove.forEach(id => {
@@ -140,6 +114,8 @@ function replaceCommitHook(): void {
140
114
  globalFlushOptions = {};
141
115
  if (!snapshotPatch && workletRefInitValuePatch.length === 0) {
142
116
  // before hydration, skip patch
117
+ applyQueuedRefs();
118
+ originalPreactCommit?.(vnode, commitQueue);
143
119
  return;
144
120
  }
145
121
 
@@ -169,6 +145,9 @@ function replaceCommitHook(): void {
169
145
  globalCommitTaskMap.delete(commitTaskId);
170
146
  }
171
147
  });
148
+
149
+ applyQueuedRefs();
150
+ originalPreactCommit?.(vnode, commitQueue);
172
151
  };
173
152
  options[COMMIT] = commit as ((...args: Parameters<typeof commit>) => void);
174
153
  }
@@ -225,14 +204,6 @@ function clearCommitTaskId(): void {
225
204
  nextCommitTaskId = 1;
226
205
  }
227
206
 
228
- function replaceRequestAnimationFrame(): void {
229
- // to make afterPaintEffects run faster
230
- const resolvedPromise = Promise.resolve();
231
- options.requestAnimationFrame = (cb: () => void) => {
232
- void resolvedPromise.then(cb);
233
- };
234
- }
235
-
236
207
  /**
237
208
  * @internal
238
209
  */
@@ -243,9 +214,7 @@ export {
243
214
  globalBackgroundSnapshotInstancesToRemove,
244
215
  globalCommitTaskMap,
245
216
  globalFlushOptions,
246
- nextCommitTaskId,
247
217
  replaceCommitHook,
248
- replaceRequestAnimationFrame,
249
218
  type PatchList,
250
219
  type PatchOptions,
251
220
  };
@@ -0,0 +1,61 @@
1
+ // Copyright 2025 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 { backgroundSnapshotInstanceManager, snapshotManager } from '../../snapshot.js';
6
+
7
+ export const ctxNotFoundType = 'Lynx.Error.CtxNotFound';
8
+
9
+ const errorMsg = 'snapshotPatchApply failed: ctx not found';
10
+
11
+ let ctxNotFoundEventListener: ((e: RuntimeProxy.Event) => void) | null = null;
12
+
13
+ export interface CtxNotFoundData {
14
+ id: number;
15
+ }
16
+
17
+ export function sendCtxNotFoundEventToBackground(id: number): void {
18
+ /* v8 ignore next 3 */
19
+ if (!lynx.getJSContext) {
20
+ throw new Error(errorMsg);
21
+ }
22
+ lynx.getJSContext().dispatchEvent({
23
+ type: ctxNotFoundType,
24
+ data: {
25
+ id,
26
+ } as CtxNotFoundData,
27
+ });
28
+ }
29
+
30
+ export function reportCtxNotFound(data: CtxNotFoundData): void {
31
+ const id = data.id;
32
+ const instance = backgroundSnapshotInstanceManager.values.get(id);
33
+
34
+ let snapshotType = 'null';
35
+
36
+ if (instance && instance.__snapshot_def) {
37
+ for (const [snapshotId, snapshot] of snapshotManager.values.entries()) {
38
+ if (snapshot === instance.__snapshot_def) {
39
+ snapshotType = snapshotId;
40
+ break;
41
+ }
42
+ }
43
+ }
44
+
45
+ lynx.reportError(new Error(`${errorMsg}, snapshot type: '${snapshotType}'`));
46
+ }
47
+
48
+ export function addCtxNotFoundEventListener(): void {
49
+ ctxNotFoundEventListener = (e) => {
50
+ reportCtxNotFound(e.data as CtxNotFoundData);
51
+ };
52
+ lynx.getCoreContext?.().addEventListener(ctxNotFoundType, ctxNotFoundEventListener);
53
+ }
54
+
55
+ export function removeCtxNotFoundEventListener(): void {
56
+ const coreContext = lynx.getCoreContext?.();
57
+ if (coreContext && ctxNotFoundEventListener) {
58
+ coreContext.removeEventListener(ctxNotFoundType, ctxNotFoundEventListener);
59
+ ctxNotFoundEventListener = null;
60
+ }
61
+ }
@@ -1,3 +1,6 @@
1
+ // Copyright 2025 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.
1
4
  import { onHydrationFinished } from '@lynx-js/react/worklet-runtime/bindings';
2
5
 
3
6
  export let isMainThreadHydrationFinished = false;
@@ -42,9 +42,9 @@ export const enum SnapshotOperation {
42
42
  // fn: string,
43
43
  // ]
44
44
 
45
- export type SnapshotPatch = any[];
45
+ export type SnapshotPatch = unknown[];
46
46
 
47
- export let __globalSnapshotPatch: any;
47
+ export let __globalSnapshotPatch: SnapshotPatch | undefined;
48
48
 
49
49
  export function takeGlobalSnapshotPatch(): SnapshotPatch | undefined {
50
50
  if (__globalSnapshotPatch) {
@@ -13,6 +13,9 @@
13
13
  * order and with proper error handling.
14
14
  */
15
15
 
16
+ import { sendCtxNotFoundEventToBackground } from './error.js';
17
+ import type { SnapshotPatch } from './snapshotPatch.js';
18
+ import { SnapshotOperation } from './snapshotPatch.js';
16
19
  import {
17
20
  SnapshotInstance,
18
21
  createSnapshot,
@@ -20,12 +23,7 @@ import {
20
23
  snapshotInstanceManager,
21
24
  snapshotManager,
22
25
  } from '../../snapshot.js';
23
- import type { SnapshotPatch } from './snapshotPatch.js';
24
- import { SnapshotOperation } from './snapshotPatch.js';
25
-
26
- function reportCtxNotFound(): void {
27
- lynx.reportError(new Error(`snapshotPatchApply failed: ctx not found`));
28
- }
26
+ import type { DynamicPartType } from '../../snapshot.js';
29
27
 
30
28
  /**
31
29
  * Applies a patch of snapshot operations to the main thread.
@@ -37,68 +35,68 @@ export function snapshotPatchApply(snapshotPatch: SnapshotPatch): void {
37
35
  for (let i = 0; i < length; ++i) {
38
36
  switch (snapshotPatch[i]) {
39
37
  case SnapshotOperation.CreateElement: {
40
- const type = snapshotPatch[++i];
41
- const id = snapshotPatch[++i];
38
+ const type = snapshotPatch[++i] as string;
39
+ const id = snapshotPatch[++i] as number;
42
40
  new SnapshotInstance(type, id);
43
41
  break;
44
42
  }
45
43
  case SnapshotOperation.InsertBefore: {
46
- const parentId = snapshotPatch[++i];
47
- const childId = snapshotPatch[++i];
48
- const beforeId = snapshotPatch[++i];
44
+ const parentId = snapshotPatch[++i] as number;
45
+ const childId = snapshotPatch[++i] as number;
46
+ const beforeId = snapshotPatch[++i] as number | undefined;
49
47
  const parent = snapshotInstanceManager.values.get(parentId);
50
48
  const child = snapshotInstanceManager.values.get(childId);
51
- const existingNode = snapshotInstanceManager.values.get(beforeId);
49
+ const existingNode = snapshotInstanceManager.values.get(beforeId!);
52
50
  if (!parent || !child) {
53
- reportCtxNotFound();
51
+ sendCtxNotFoundEventToBackground(parent ? childId : parentId);
54
52
  } else {
55
53
  parent.insertBefore(child, existingNode);
56
54
  }
57
55
  break;
58
56
  }
59
57
  case SnapshotOperation.RemoveChild: {
60
- const parentId = snapshotPatch[++i];
61
- const childId = snapshotPatch[++i];
58
+ const parentId = snapshotPatch[++i] as number;
59
+ const childId = snapshotPatch[++i] as number;
62
60
  const parent = snapshotInstanceManager.values.get(parentId);
63
61
  const child = snapshotInstanceManager.values.get(childId);
64
62
  if (!parent || !child) {
65
- reportCtxNotFound();
63
+ sendCtxNotFoundEventToBackground(parent ? childId : parentId);
66
64
  } else {
67
65
  parent.removeChild(child);
68
66
  }
69
67
  break;
70
68
  }
71
69
  case SnapshotOperation.SetAttribute: {
72
- const id = snapshotPatch[++i];
73
- const dynamicPartIndex = snapshotPatch[++i];
70
+ const id = snapshotPatch[++i] as number;
71
+ const dynamicPartIndex = snapshotPatch[++i] as number;
74
72
  const value = snapshotPatch[++i];
75
73
  const si = snapshotInstanceManager.values.get(id);
76
74
  if (si) {
77
75
  si.setAttribute(dynamicPartIndex, value);
78
76
  } else {
79
- reportCtxNotFound();
77
+ sendCtxNotFoundEventToBackground(id);
80
78
  }
81
79
  break;
82
80
  }
83
81
  case SnapshotOperation.SetAttributes: {
84
- const id = snapshotPatch[++i];
82
+ const id = snapshotPatch[++i] as number;
85
83
  const values = snapshotPatch[++i];
86
84
  const si = snapshotInstanceManager.values.get(id);
87
85
  if (si) {
88
86
  si.setAttribute('values', values);
89
87
  } else {
90
- reportCtxNotFound();
88
+ sendCtxNotFoundEventToBackground(id);
91
89
  }
92
90
  break;
93
91
  }
94
92
  case SnapshotOperation.DEV_ONLY_AddSnapshot: {
95
93
  if (__DEV__) {
96
- const uniqID: string = snapshotPatch[++i];
97
- const create: string = snapshotPatch[++i];
98
- const update: string[] = snapshotPatch[++i];
99
- const slot = snapshotPatch[++i];
100
- const cssId: number = snapshotPatch[++i] ?? 0;
101
- const entryName: string | undefined = snapshotPatch[++i];
94
+ const uniqID = snapshotPatch[++i] as string;
95
+ const create = snapshotPatch[++i] as string;
96
+ const update = snapshotPatch[++i] as string[];
97
+ const slot = snapshotPatch[++i] as [DynamicPartType, number][];
98
+ const cssId = (snapshotPatch[++i] ?? 0) as number;
99
+ const entryName = snapshotPatch[++i] as string | undefined;
102
100
 
103
101
  if (!snapshotManager.values.has(entryUniqID(uniqID, entryName))) {
104
102
  // HMR-related
@@ -106,10 +104,12 @@ export function snapshotPatchApply(snapshotPatch: SnapshotPatch): void {
106
104
  createSnapshot(
107
105
  uniqID,
108
106
  evaluate<(ctx: SnapshotInstance) => FiberElement[]>(create),
107
+ // eslint-disable-next-line unicorn/no-array-callback-reference
109
108
  update.map<(ctx: SnapshotInstance, index: number, oldValue: any) => void>(evaluate),
110
109
  slot,
111
110
  cssId,
112
111
  entryName,
112
+ null,
113
113
  );
114
114
  }
115
115
  }
@@ -129,10 +129,17 @@ export function snapshotPatchApply(snapshotPatch: SnapshotPatch): void {
129
129
  }
130
130
  }
131
131
 
132
+ /* v8 ignore start */
132
133
  /**
133
134
  * Evaluates a string as code with ReactLynx runtime injected.
134
135
  * Used for HMR (Hot Module Replacement) to update snapshot definitions.
135
136
  */
136
137
  function evaluate<T>(code: string): T {
137
- return new Function(`return ${code}`)();
138
+ if (__DEV__) {
139
+ // We are using `eval` here to make the updated snapshot to access variables like `__webpack_require__`.
140
+ // See: https://github.com/lynx-family/lynx-stack/issues/983.
141
+ return eval(`(() => ${code})()`) as T;
142
+ }
143
+ throw new Error('unreachable: evaluate is not supported in production');
138
144
  }
145
+ /* v8 ignore stop */
@@ -4,16 +4,15 @@
4
4
 
5
5
  import { updateWorkletRefInitValueChanges } from '@lynx-js/react/worklet-runtime/bindings';
6
6
 
7
- import type { PatchList, PatchOptions } from './commit.js';
8
- import { snapshotPatchApply } from './snapshotPatchApply.js';
9
7
  import { LifecycleConstant } from '../../lifecycleConstant.js';
10
8
  import { __pendingListUpdates } from '../../list.js';
11
- import { markTiming, PerformanceTimingKeys, setPipeline } from '../../lynx/performance.js';
12
- import { takeGlobalRefPatchMap } from '../../snapshot/ref.js';
9
+ import { PerformanceTimingKeys, markTiming, setPipeline } from '../../lynx/performance.js';
13
10
  import { __page } from '../../snapshot.js';
14
- import { isEmptyObject } from '../../utils.js';
15
11
  import { getReloadVersion } from '../pass.js';
12
+ import type { PatchList, PatchOptions } from './commit.js';
16
13
  import { setMainThreadHydrationFinished } from './isMainThreadHydrationFinished.js';
14
+ import { snapshotPatchApply } from './snapshotPatchApply.js';
15
+ import { applyRefQueue } from '../../snapshot/workletRef.js';
17
16
 
18
17
  function updateMainThread(
19
18
  { data, patchOptions }: {
@@ -33,7 +32,7 @@ function updateMainThread(
33
32
  markTiming(PerformanceTimingKeys.parseChangesEnd);
34
33
  markTiming(PerformanceTimingKeys.patchChangesStart);
35
34
 
36
- for (const { snapshotPatch, workletRefInitValuePatch, id } of patchList) {
35
+ for (const { snapshotPatch, workletRefInitValuePatch } of patchList) {
37
36
  updateWorkletRefInitValueChanges(workletRefInitValuePatch);
38
37
  __pendingListUpdates.clear();
39
38
  if (snapshotPatch) {
@@ -42,14 +41,13 @@ function updateMainThread(
42
41
  __pendingListUpdates.flush();
43
42
  // console.debug('********** Lepus updatePatch:');
44
43
  // printSnapshotInstance(snapshotInstanceManager.values.get(-1)!);
45
-
46
- commitMainThreadPatchUpdate(id);
47
44
  }
48
45
  markTiming(PerformanceTimingKeys.patchChangesEnd);
49
46
  markTiming(PerformanceTimingKeys.mtsRenderEnd);
50
47
  if (patchOptions.isHydration) {
51
48
  setMainThreadHydrationFinished(true);
52
49
  }
50
+ applyRefQueue();
53
51
  if (patchOptions.pipelineOptions) {
54
52
  flushOptions.pipelineOptions = patchOptions.pipelineOptions;
55
53
  }
@@ -61,14 +59,7 @@ function injectUpdateMainThread(): void {
61
59
  Object.assign(globalThis, { [LifecycleConstant.patchUpdate]: updateMainThread });
62
60
  }
63
61
 
64
- function commitMainThreadPatchUpdate(commitTaskId?: number): void {
65
- const refPatch = takeGlobalRefPatchMap();
66
- if (!isEmptyObject(refPatch)) {
67
- __OnLifecycleEvent([LifecycleConstant.ref, { commitTaskId, refPatch: JSON.stringify(refPatch) }]);
68
- }
69
- }
70
-
71
62
  /**
72
63
  * @internal
73
64
  */
74
- export { commitMainThreadPatchUpdate, injectUpdateMainThread };
65
+ export { injectUpdateMainThread };
@@ -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 };
@@ -13,25 +13,26 @@ import { hydrate } from '../hydrate.js';
13
13
  import { LifecycleConstant } from '../lifecycleConstant.js';
14
14
  import { __pendingListUpdates } from '../list.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 {
@@ -2,8 +2,8 @@
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
4
  import { hydrate } from './hydrate.js';
5
- import { commitMainThreadPatchUpdate } from './lifecycle/patch/updateMainThread.js';
6
- import type { SnapshotInstance } from './snapshot.js';
5
+ import { applyRefQueue } from './snapshot/workletRef.js';
6
+ import { type SnapshotInstance } from './snapshot.js';
7
7
 
8
8
  export interface ListUpdateInfo {
9
9
  flush(): void;
@@ -294,10 +294,9 @@ export function componentAtIndexFactory(
294
294
  __FlushElementTree(root, { triggerLayout: true, operationID, elementID: sign, listID });
295
295
  } else if (enableBatchRender && asyncFlush) {
296
296
  __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
297
  }
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.
301
300
  return sign;
302
301
  } else {
303
302
  const newCtx = childCtx.takeElements();
@@ -311,7 +310,11 @@ export function componentAtIndexFactory(
311
310
  recycleSignMap.delete(sign);
312
311
  hydrate(oldCtx, childCtx);
313
312
  oldCtx.unRenderElements();
313
+ if (!oldCtx.__id) {
314
+ oldCtx.tearDown();
315
+ }
314
316
  const root = childCtx.__element_root!;
317
+ applyRefQueue();
315
318
  if (!enableBatchRender) {
316
319
  const flushOptions: FlushOptions = {
317
320
  triggerLayout: true,
@@ -339,7 +342,6 @@ export function componentAtIndexFactory(
339
342
  __FlushElementTree(root, flushOptions);
340
343
  }
341
344
  signMap.set(sign, childCtx);
342
- commitMainThreadPatchUpdate(undefined);
343
345
  return sign;
344
346
  }
345
347
 
@@ -347,6 +349,7 @@ export function componentAtIndexFactory(
347
349
  const root = childCtx.__element_root!;
348
350
  __AppendElement(list, root);
349
351
  const sign = __GetElementUniqueID(root);
352
+ applyRefQueue();
350
353
  if (!enableBatchRender) {
351
354
  __FlushElementTree(root, {
352
355
  triggerLayout: true,
@@ -360,7 +363,6 @@ export function componentAtIndexFactory(
360
363
  });
361
364
  }
362
365
  signMap.set(sign, childCtx);
363
- commitMainThreadPatchUpdate(undefined);
364
366
  return sign;
365
367
  };
366
368