@lynx-js/react 0.107.1 → 0.108.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 (135) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +4 -2
  3. package/package.json +4 -4
  4. package/refresh/.turbo/turbo-build.log +1 -1
  5. package/refresh/package.json +1 -1
  6. package/runtime/lazy/import.js +7 -1
  7. package/runtime/lepus/index.js +1 -1
  8. package/runtime/lepus/jsx-dev-runtime/index.js +1 -1
  9. package/runtime/lib/backgroundSnapshot.d.ts +1 -1
  10. package/runtime/lib/backgroundSnapshot.js +7 -4
  11. package/runtime/lib/backgroundSnapshot.js.map +1 -1
  12. package/runtime/lib/document.d.ts +33 -2
  13. package/runtime/lib/document.js +9 -1
  14. package/runtime/lib/document.js.map +1 -1
  15. package/runtime/lib/hydrate.js +2 -1
  16. package/runtime/lib/hydrate.js.map +1 -1
  17. package/runtime/lib/lifecycle/delayUnmount.d.ts +1 -1
  18. package/runtime/lib/lifecycle/delayUnmount.js +1 -4
  19. package/runtime/lib/lifecycle/delayUnmount.js.map +1 -1
  20. package/runtime/lib/lifecycle/destroy.js +2 -2
  21. package/runtime/lib/lifecycle/destroy.js.map +1 -1
  22. package/runtime/lib/lifecycle/patch/commit.d.ts +21 -3
  23. package/runtime/lib/lifecycle/patch/commit.js +44 -56
  24. package/runtime/lib/lifecycle/patch/commit.js.map +1 -1
  25. package/runtime/lib/lifecycle/patch/isMainThreadHydrationFinished.d.ts +2 -0
  26. package/runtime/lib/lifecycle/patch/isMainThreadHydrationFinished.js +9 -0
  27. package/runtime/lib/lifecycle/patch/isMainThreadHydrationFinished.js.map +1 -0
  28. package/runtime/lib/lifecycle/patch/snapshotPatch.d.ts +5 -0
  29. package/runtime/lib/lifecycle/patch/snapshotPatch.js +5 -0
  30. package/runtime/lib/lifecycle/patch/snapshotPatch.js.map +1 -1
  31. package/runtime/lib/lifecycle/patch/snapshotPatchApply.d.ts +5 -0
  32. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js +21 -5
  33. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js.map +1 -1
  34. package/runtime/lib/lifecycle/patch/updateMainThread.js +4 -3
  35. package/runtime/lib/lifecycle/patch/updateMainThread.js.map +1 -1
  36. package/runtime/lib/lifecycle/reload.js +11 -4
  37. package/runtime/lib/lifecycle/reload.js.map +1 -1
  38. package/runtime/lib/lifecycle/render.d.ts +1 -3
  39. package/runtime/lib/lifecycle/render.js +4 -6
  40. package/runtime/lib/lifecycle/render.js.map +1 -1
  41. package/runtime/lib/list.d.ts +1 -1
  42. package/runtime/lib/list.js +60 -23
  43. package/runtime/lib/list.js.map +1 -1
  44. package/runtime/lib/lynx/env.d.ts +1 -1
  45. package/runtime/lib/lynx/env.js +1 -1
  46. package/runtime/lib/lynx/env.js.map +1 -1
  47. package/runtime/lib/lynx/runWithForce.js +12 -4
  48. package/runtime/lib/lynx/runWithForce.js.map +1 -1
  49. package/runtime/lib/lynx/tt.js +6 -8
  50. package/runtime/lib/lynx/tt.js.map +1 -1
  51. package/runtime/lib/lynx-api.d.ts +4 -4
  52. package/runtime/lib/lynx-api.js +5 -5
  53. package/runtime/lib/lynx-api.js.map +1 -1
  54. package/runtime/lib/lynx.js +6 -5
  55. package/runtime/lib/lynx.js.map +1 -1
  56. package/runtime/lib/opcodes.js +2 -2
  57. package/runtime/lib/opcodes.js.map +1 -1
  58. package/runtime/lib/renderToOpcodes/index.js +5 -1
  59. package/runtime/lib/renderToOpcodes/index.js.map +1 -1
  60. package/runtime/lib/root.d.ts +4 -0
  61. package/runtime/lib/root.js +6 -2
  62. package/runtime/lib/root.js.map +1 -1
  63. package/runtime/lib/snapshot/ref.d.ts +1 -1
  64. package/runtime/lib/snapshot/ref.js +1 -1
  65. package/runtime/lib/snapshot/ref.js.map +1 -1
  66. package/runtime/lib/snapshot/spread.d.ts +2 -2
  67. package/runtime/lib/snapshot/spread.js +9 -9
  68. package/runtime/lib/snapshot/spread.js.map +1 -1
  69. package/runtime/lib/snapshot/workletEvent.d.ts +2 -1
  70. package/runtime/lib/snapshot/workletEvent.js +1 -1
  71. package/runtime/lib/snapshot/workletEvent.js.map +1 -1
  72. package/runtime/lib/snapshot/workletRef.d.ts +4 -4
  73. package/runtime/lib/snapshot/workletRef.js +8 -6
  74. package/runtime/lib/snapshot/workletRef.js.map +1 -1
  75. package/runtime/lib/snapshot.d.ts +31 -5
  76. package/runtime/lib/snapshot.js +15 -3
  77. package/runtime/lib/snapshot.js.map +1 -1
  78. package/runtime/lib/worklet/workletRef.d.ts +1 -0
  79. package/runtime/lib/worklet/workletRef.js +14 -12
  80. package/runtime/lib/worklet/workletRef.js.map +1 -1
  81. package/runtime/src/backgroundSnapshot.ts +20 -13
  82. package/runtime/src/document.ts +33 -2
  83. package/runtime/src/hydrate.ts +3 -1
  84. package/runtime/src/lifecycle/delayUnmount.ts +2 -2
  85. package/runtime/src/lifecycle/destroy.ts +3 -2
  86. package/runtime/src/lifecycle/patch/commit.ts +75 -72
  87. package/runtime/src/lifecycle/patch/isMainThreadHydrationFinished.ts +10 -0
  88. package/runtime/src/lifecycle/patch/snapshotPatch.ts +9 -0
  89. package/runtime/src/lifecycle/patch/snapshotPatchApply.ts +20 -5
  90. package/runtime/src/lifecycle/patch/updateMainThread.ts +4 -3
  91. package/runtime/src/lifecycle/reload.ts +14 -4
  92. package/runtime/src/lifecycle/render.ts +6 -8
  93. package/runtime/src/list.ts +71 -23
  94. package/runtime/src/lynx/env.ts +1 -1
  95. package/runtime/src/lynx/runWithForce.ts +17 -6
  96. package/runtime/src/lynx/tt.ts +10 -17
  97. package/runtime/src/lynx-api.ts +7 -7
  98. package/runtime/src/lynx.ts +7 -6
  99. package/runtime/src/opcodes.ts +2 -2
  100. package/runtime/src/renderToOpcodes/index.ts +6 -1
  101. package/runtime/src/root.ts +6 -2
  102. package/runtime/src/snapshot/ref.ts +7 -7
  103. package/runtime/src/snapshot/spread.ts +28 -10
  104. package/runtime/src/snapshot/workletEvent.ts +3 -2
  105. package/runtime/src/snapshot/workletRef.ts +18 -19
  106. package/runtime/src/snapshot.ts +35 -10
  107. package/runtime/src/worklet/workletRef.ts +15 -12
  108. package/testing-library/README.md +1 -1
  109. package/testing-library/dist/env/vitest.js +23 -23
  110. package/testing-library/dist/index.d.ts +24 -24
  111. package/testing-library/dist/index.js +2 -2
  112. package/testing-library/dist/pure.js +13 -15
  113. package/testing-library/dist/vitest-global-setup.js +13 -14
  114. package/testing-library/types/entry.d.ts +2 -2
  115. package/testing-library/types/index.d.ts +3 -3
  116. package/transform/dist/wasm.cjs +1 -1
  117. package/worklet-runtime/dist/dev.js +25 -13
  118. package/worklet-runtime/dist/dev.js.map +3 -3
  119. package/worklet-runtime/dist/main.js +25 -13
  120. package/worklet-runtime/dist/main.js.map +3 -3
  121. package/worklet-runtime/lib/bindings/bindings.d.ts +1 -1
  122. package/worklet-runtime/lib/bindings/bindings.js +2 -2
  123. package/worklet-runtime/lib/bindings/bindings.js.map +1 -1
  124. package/worklet-runtime/lib/bindings/index.d.ts +1 -1
  125. package/worklet-runtime/lib/bindings/index.js +1 -1
  126. package/worklet-runtime/lib/bindings/index.js.map +1 -1
  127. package/worklet-runtime/lib/bindings/types.d.ts +7 -3
  128. package/worklet-runtime/lib/bindings/types.js +3 -0
  129. package/worklet-runtime/lib/bindings/types.js.map +1 -1
  130. package/worklet-runtime/lib/ctxTrace.d.ts +8 -0
  131. package/worklet-runtime/lib/ctxTrace.js +13 -0
  132. package/worklet-runtime/lib/ctxTrace.js.map +1 -0
  133. package/worklet-runtime/lib/global.d.ts +3 -3
  134. package/worklet-runtime/lib/workletRuntime.js +11 -8
  135. package/worklet-runtime/lib/workletRuntime.js.map +1 -1
@@ -1,12 +1,28 @@
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
+
5
+ /**
6
+ * Implements the commit phase of the rendering lifecycle.
7
+ * This module patches Preact's commit phase to integrate with the snapshot system,
8
+ * handling the collection and transmission of patches between threads.
9
+ *
10
+ * The commit phase is responsible for:
11
+ * - Collecting patches from the snapshot system
12
+ * - Managing commit tasks and their execution
13
+ * - Coordinating with the native layer for updates
14
+ * - Handling performance timing and pipeline options
15
+ */
16
+
17
+ /**
18
+ * This module patches Preact's commit phase by hacking into the internal of
19
+ * its [options](https://preactjs.com/guide/v10/options/) API
20
+ */
21
+
4
22
  import type { VNode } from 'preact';
5
23
  import { options } from 'preact';
6
24
  import type { Component } from 'preact/compat';
7
25
 
8
- import type { SnapshotPatch } from './snapshotPatch.js';
9
- import { takeGlobalSnapshotPatch } from './snapshotPatch.js';
10
26
  import { LifecycleConstant } from '../../lifecycleConstant.js';
11
27
  import {
12
28
  PerformanceTimingKeys,
@@ -16,12 +32,14 @@ import {
16
32
  setPipeline,
17
33
  } from '../../lynx/performance.js';
18
34
  import { CATCH_ERROR, COMMIT, RENDER_CALLBACKS, VNODE } from '../../renderToOpcodes/constants.js';
19
- import { updateBackgroundRefs } from '../../snapshot/ref.js';
20
35
  import { backgroundSnapshotInstanceManager } from '../../snapshot.js';
36
+ import { updateBackgroundRefs } from '../../snapshot/ref.js';
21
37
  import { isEmptyObject } from '../../utils.js';
22
38
  import { takeWorkletRefInitValuePatch } from '../../worklet/workletRefPool.js';
23
39
  import { runDelayedUnmounts, takeDelayedUnmounts } from '../delayUnmount.js';
24
40
  import { getReloadVersion } from '../pass.js';
41
+ import type { SnapshotPatch } from './snapshotPatch.js';
42
+ import { takeGlobalSnapshotPatch } from './snapshotPatch.js';
25
43
 
26
44
  let globalFlushOptions: FlushOptions = {};
27
45
 
@@ -30,58 +48,50 @@ let nextCommitTaskId = 1;
30
48
 
31
49
  let globalBackgroundSnapshotInstancesToRemove: number[] = [];
32
50
 
33
- let patchesToCommit: Patch[] = [];
34
- function clearPatchesToCommit(): void {
35
- patchesToCommit = [];
36
- }
37
-
51
+ /**
52
+ * A single patch operation.
53
+ */
38
54
  interface Patch {
39
55
  id: number;
40
56
  snapshotPatch?: SnapshotPatch;
41
57
  workletRefInitValuePatch?: [id: number, value: unknown][];
42
58
  }
43
59
 
60
+ /**
61
+ * List of patches to be applied in a single update cycle with flush options.
62
+ */
44
63
  interface PatchList {
45
64
  patchList: Patch[];
46
65
  flushOptions?: FlushOptions;
47
66
  }
48
67
 
68
+ /**
69
+ * Configuration options for patch operations
70
+ */
49
71
  interface PatchOptions {
50
72
  pipelineOptions?: PipelineOptions;
51
73
  reloadVersion: number;
52
74
  isHydration?: boolean;
53
75
  }
54
76
 
77
+ /**
78
+ * Replaces Preact's default commit hook with our custom implementation
79
+ */
55
80
  function replaceCommitHook(): void {
56
- // use our own `options.debounceRendering` to insert a timing flag before render
57
- type DebounceRendering = (f: () => void) => void;
58
- const injectDebounceRendering = (debounceRendering: DebounceRendering): DebounceRendering => {
59
- return (f: () => void) => {
60
- debounceRendering(() => {
61
- f();
62
- void commitToMainThread();
63
- });
64
- };
65
- };
66
- const defaultDebounceRendering = options.debounceRendering?.bind(options)
67
- ?? (Promise.prototype.then.bind(Promise.resolve()) as DebounceRendering);
68
- let _debounceRendering = injectDebounceRendering(defaultDebounceRendering);
69
- Object.defineProperty(options, 'debounceRendering', {
70
- get() {
71
- return _debounceRendering;
72
- },
73
- set(debounceRendering: DebounceRendering) {
74
- _debounceRendering = injectDebounceRendering(debounceRendering);
75
- },
76
- });
77
-
78
- const oldCommit = options[COMMIT];
81
+ const originalPreactCommit = options[COMMIT];
79
82
  const commit = async (vnode: VNode, commitQueue: any[]) => {
80
- if (__LEPUS__) {
83
+ // Skip commit phase for MT runtime
84
+ if (__MAIN_THREAD__) {
81
85
  // for testing only
82
86
  commitQueue.length = 0;
83
87
  return;
84
88
  }
89
+
90
+ // Mark the end of virtual DOM diffing phase for performance tracking
91
+ markTimingLegacy(PerformanceTimingKeys.updateDiffVdomEnd);
92
+ markTiming(PerformanceTimingKeys.diffVdomEnd);
93
+
94
+ // The callback functions to be called after components are rendered.
85
95
  const renderCallbacks = commitQueue.map((component: Component<any>) => {
86
96
  const ret = {
87
97
  component,
@@ -92,16 +102,19 @@ function replaceCommitHook(): void {
92
102
  return ret;
93
103
  });
94
104
  commitQueue.length = 0;
105
+
95
106
  const delayedUnmounts = takeDelayedUnmounts();
96
107
 
97
108
  const backgroundSnapshotInstancesToRemove = globalBackgroundSnapshotInstancesToRemove;
98
109
  globalBackgroundSnapshotInstancesToRemove = [];
99
110
 
100
111
  const commitTaskId = genCommitTaskId();
112
+
113
+ // Register the commit task
101
114
  globalCommitTaskMap.set(commitTaskId, () => {
102
115
  updateBackgroundRefs(commitTaskId);
103
116
  runDelayedUnmounts(delayedUnmounts);
104
- oldCommit?.(vnode, renderCallbacks);
117
+ originalPreactCommit?.(vnode, renderCallbacks);
105
118
  renderCallbacks.some(wrapper => {
106
119
  try {
107
120
  wrapper[RENDER_CALLBACKS].some((cb: (this: Component) => void) => {
@@ -120,8 +133,11 @@ function replaceCommitHook(): void {
120
133
  }
121
134
  });
122
135
 
136
+ // Collect patches for this update
123
137
  const snapshotPatch = takeGlobalSnapshotPatch();
138
+ const flushOptions = globalFlushOptions;
124
139
  const workletRefInitValuePatch = takeWorkletRefInitValuePatch();
140
+ globalFlushOptions = {};
125
141
  if (!snapshotPatch && workletRefInitValuePatch.length === 0) {
126
142
  // before hydration, skip patch
127
143
  return;
@@ -137,46 +153,29 @@ function replaceCommitHook(): void {
137
153
  if (workletRefInitValuePatch.length) {
138
154
  patch.workletRefInitValuePatch = workletRefInitValuePatch;
139
155
  }
156
+ const patchList: PatchList = {
157
+ patchList: [patch],
158
+ };
159
+ if (!isEmptyObject(flushOptions)) {
160
+ patchList.flushOptions = flushOptions;
161
+ }
162
+ const obj = commitPatchUpdate(patchList, {});
140
163
 
141
- patchesToCommit.push(patch);
142
- };
143
- options[COMMIT] = commit as ((...args: Parameters<typeof commit>) => void);
144
- }
145
-
146
- async function commitToMainThread(): Promise<void> {
147
- if (patchesToCommit.length === 0) {
148
- return;
149
- }
150
-
151
- markTimingLegacy(PerformanceTimingKeys.updateDiffVdomEnd);
152
- markTiming(PerformanceTimingKeys.diffVdomEnd);
153
-
154
- const flushOptions = globalFlushOptions;
155
- globalFlushOptions = {};
156
-
157
- const patchList: PatchList = {
158
- patchList: patchesToCommit,
159
- };
160
- patchesToCommit = [];
161
-
162
- if (!isEmptyObject(flushOptions)) {
163
- patchList.flushOptions = flushOptions;
164
- }
165
-
166
- const obj = commitPatchUpdate(patchList, {});
167
-
168
- lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => {
169
- for (let i = 0; i < patchList.patchList.length; i++) {
170
- const patch = patchList.patchList[i]!;
171
- const commitTask = globalCommitTaskMap.get(patch.id);
164
+ // Send the update to the native layer
165
+ lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => {
166
+ const commitTask = globalCommitTaskMap.get(commitTaskId);
172
167
  if (commitTask) {
173
168
  commitTask();
174
- globalCommitTaskMap.delete(patch.id);
169
+ globalCommitTaskMap.delete(commitTaskId);
175
170
  }
176
- }
177
- });
171
+ });
172
+ };
173
+ options[COMMIT] = commit as ((...args: Parameters<typeof commit>) => void);
178
174
  }
179
175
 
176
+ /**
177
+ * Prepares the patch update for transmission to the native layer
178
+ */
180
179
  function commitPatchUpdate(patchList: PatchList, patchOptions: Omit<PatchOptions, 'reloadVersion'>): {
181
180
  data: string;
182
181
  patchOptions: PatchOptions;
@@ -212,9 +211,16 @@ function commitPatchUpdate(patchList: PatchList, patchOptions: Omit<PatchOptions
212
211
  return obj;
213
212
  }
214
213
 
214
+ /**
215
+ * Generates a unique ID for commit tasks
216
+ */
215
217
  function genCommitTaskId(): number {
216
218
  return nextCommitTaskId++;
217
219
  }
220
+
221
+ /**
222
+ * Resets the commit task ID counter
223
+ */
218
224
  function clearCommitTaskId(): void {
219
225
  nextCommitTaskId = 1;
220
226
  }
@@ -231,18 +237,15 @@ function replaceRequestAnimationFrame(): void {
231
237
  * @internal
232
238
  */
233
239
  export {
240
+ clearCommitTaskId,
234
241
  commitPatchUpdate,
235
- commitToMainThread,
236
242
  genCommitTaskId,
237
- clearCommitTaskId,
238
243
  globalBackgroundSnapshotInstancesToRemove,
239
244
  globalCommitTaskMap,
240
245
  globalFlushOptions,
241
246
  nextCommitTaskId,
242
- patchesToCommit,
243
- clearPatchesToCommit,
244
247
  replaceCommitHook,
245
248
  replaceRequestAnimationFrame,
246
- type PatchOptions,
247
249
  type PatchList,
250
+ type PatchOptions,
248
251
  };
@@ -0,0 +1,10 @@
1
+ import { onHydrationFinished } from '@lynx-js/react/worklet-runtime/bindings';
2
+
3
+ export let isMainThreadHydrationFinished = false;
4
+
5
+ export function setMainThreadHydrationFinished(isFinished: boolean): void {
6
+ if (isFinished && !isMainThreadHydrationFinished) {
7
+ onHydrationFinished();
8
+ }
9
+ isMainThreadHydrationFinished = isFinished;
10
+ }
@@ -1,6 +1,13 @@
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
+
5
+ /**
6
+ * Defines the core patch operations for the snapshot system.
7
+ * The patch operations are designed to be serializable and minimal, allowing
8
+ * efficient transmission between threads and application to element tree.
9
+ */
10
+
4
11
  export const enum SnapshotOperation {
5
12
  CreateElement,
6
13
  InsertBefore,
@@ -12,6 +19,8 @@ export const enum SnapshotOperation {
12
19
  DEV_ONLY_RegisterWorklet = 101,
13
20
  }
14
21
 
22
+ // Operation format definitions:
23
+ //
15
24
  // [opcode: SnapshotOperation.CreateElement, type: string, id: number]
16
25
  // [opcode: SnapshotOperation.InsertBefore, parentId: number, id: number, beforeId: number | undefined]
17
26
  // [opcode: SnapshotOperation.RemoveChild, parentId: number, childId: number]
@@ -1,14 +1,31 @@
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
+
5
+ /**
6
+ * Implements the patch application logic for the snapshot system.
7
+ * This module is responsible for interpreting and executing patch operations
8
+ * that were generated in the background thread, applying them to the DOM
9
+ * in the main thread.
10
+ *
11
+ * The module handles various operations like element creation, insertion,
12
+ * removal, and attribute updates, ensuring they are applied in the correct
13
+ * order and with proper error handling.
14
+ */
15
+
16
+ import { SnapshotInstance, createSnapshot, snapshotInstanceManager, snapshotManager } from '../../snapshot.js';
4
17
  import type { SnapshotPatch } from './snapshotPatch.js';
5
18
  import { SnapshotOperation } from './snapshotPatch.js';
6
- import { SnapshotInstance, createSnapshot, snapshotInstanceManager, snapshotManager } from '../../snapshot.js';
7
19
 
8
20
  function reportCtxNotFound(): void {
9
21
  lynx.reportError(new Error(`snapshotPatchApply failed: ctx not found`));
10
22
  }
11
23
 
24
+ /**
25
+ * Applies a patch of snapshot operations to the main thread.
26
+ * This is the counterpart to the patch generation in the background thread.
27
+ * Each operation in the patch is processed sequentially to update the DOM.
28
+ */
12
29
  export function snapshotPatchApply(snapshotPatch: SnapshotPatch): void {
13
30
  const length = snapshotPatch.length;
14
31
  for (let i = 0; i < length; ++i) {
@@ -107,10 +124,8 @@ export function snapshotPatchApply(snapshotPatch: SnapshotPatch): void {
107
124
  }
108
125
 
109
126
  /**
110
- * Given an expression string, return the evaluated result with ReactLynx runtime injected.
111
- *
112
- * @param code - The code to be evaluated
113
- * @returns the evaluated expression
127
+ * Evaluates a string as code with ReactLynx runtime injected.
128
+ * Used for HMR (Hot Module Replacement) to update snapshot definitions.
114
129
  */
115
130
  function evaluate<T>(code: string): T {
116
131
  return new Function(`return ${code}`)();
@@ -2,17 +2,18 @@
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
 
5
- import { clearDelayedWorklets, updateWorkletRefInitValueChanges } from '@lynx-js/react/worklet-runtime/bindings';
5
+ import { updateWorkletRefInitValueChanges } from '@lynx-js/react/worklet-runtime/bindings';
6
6
 
7
7
  import type { PatchList, PatchOptions } from './commit.js';
8
8
  import { snapshotPatchApply } from './snapshotPatchApply.js';
9
9
  import { LifecycleConstant } from '../../lifecycleConstant.js';
10
10
  import { __pendingListUpdates } from '../../list.js';
11
- import { PerformanceTimingKeys, markTiming, setPipeline } from '../../lynx/performance.js';
11
+ import { markTiming, PerformanceTimingKeys, setPipeline } from '../../lynx/performance.js';
12
12
  import { takeGlobalRefPatchMap } from '../../snapshot/ref.js';
13
13
  import { __page } from '../../snapshot.js';
14
14
  import { isEmptyObject } from '../../utils.js';
15
15
  import { getReloadVersion } from '../pass.js';
16
+ import { setMainThreadHydrationFinished } from './isMainThreadHydrationFinished.js';
16
17
 
17
18
  function updateMainThread(
18
19
  { data, patchOptions }: {
@@ -47,7 +48,7 @@ function updateMainThread(
47
48
  markTiming(PerformanceTimingKeys.patchChangesEnd);
48
49
  markTiming(PerformanceTimingKeys.mtsRenderEnd);
49
50
  if (patchOptions.isHydration) {
50
- clearDelayedWorklets();
51
+ setMainThreadHydrationFinished(true);
51
52
  }
52
53
  if (patchOptions.pipelineOptions) {
53
54
  flushOptions.pipelineOptions = patchOptions.pipelineOptions;
@@ -1,19 +1,28 @@
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
+
5
+ /**
6
+ * Implements the reload (thinking of "refresh" in browser) for both main thread
7
+ * and background thread.
8
+ */
9
+
10
+ import { render } from 'preact';
11
+
4
12
  import { hydrate } from '../hydrate.js';
5
13
  import { LifecycleConstant } from '../lifecycleConstant.js';
6
14
  import { __pendingListUpdates } from '../list.js';
7
15
  import { __root, setRoot } from '../root.js';
8
- import { takeGlobalRefPatchMap } from '../snapshot/ref.js';
9
16
  import { SnapshotInstance, __page, snapshotInstanceManager } from '../snapshot.js';
17
+ import { takeGlobalRefPatchMap } from '../snapshot/ref.js';
10
18
  import { isEmptyObject } from '../utils.js';
11
- import { destroyBackground } from './destroy.js';
12
19
  import { destroyWorklet } from '../worklet/destroy.js';
20
+ import { destroyBackground } from './destroy.js';
13
21
  import { clearJSReadyEventIdSwap, isJSReady } from './event/jsReady.js';
14
22
  import { increaseReloadVersion } from './pass.js';
15
23
  import { deinitGlobalSnapshotPatch } from './patch/snapshotPatch.js';
16
- import { renderBackground, renderMainThread } from './render.js';
24
+ import { renderMainThread } from './render.js';
25
+ import { setMainThreadHydrationFinished } from './patch/isMainThreadHydrationFinished.js';
17
26
 
18
27
  function reloadMainThread(data: any, options: UpdatePageOption): void {
19
28
  if (__PROFILE__) {
@@ -30,6 +39,7 @@ function reloadMainThread(data: any, options: UpdatePageOption): void {
30
39
  snapshotInstanceManager.clear();
31
40
  __pendingListUpdates.clear();
32
41
  clearJSReadyEventIdSwap();
42
+ setMainThreadHydrationFinished(false);
33
43
 
34
44
  const oldRoot = __root;
35
45
  setRoot(new SnapshotInstance('root'));
@@ -74,7 +84,7 @@ function reloadBackground(updateData: Record<string, any>): void {
74
84
  // COW when modify `lynx.__initData` to make sure Provider & Consumer works
75
85
  lynx.__initData = Object.assign({}, lynx.__initData, updateData);
76
86
 
77
- renderBackground(__root.__jsx, __root as any);
87
+ render(__root.__jsx, __root as any);
78
88
 
79
89
  if (__PROFILE__) {
80
90
  console.profileEnd();
@@ -1,13 +1,16 @@
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
+
5
+ /**
6
+ * Implements the IFR (Instant First-Frame Rendering) on main thread.
7
+ */
8
+
4
9
  import { render } from 'preact';
5
- import type { ComponentChild, ContainerNode } from 'preact';
6
10
 
7
11
  import { renderOpcodesInto } from '../opcodes.js';
8
12
  import { render as renderToString } from '../renderToOpcodes/index.js';
9
13
  import { __root } from '../root.js';
10
- import { commitToMainThread } from './patch/commit.js';
11
14
 
12
15
  function renderMainThread(): void {
13
16
  /* v8 ignore start */
@@ -47,9 +50,4 @@ function renderMainThread(): void {
47
50
  /* v8 ignore stop */
48
51
  }
49
52
 
50
- function renderBackground(vnode: ComponentChild, parent: ContainerNode): void {
51
- render(vnode, parent);
52
- void commitToMainThread();
53
- }
54
-
55
- export { renderMainThread, renderBackground };
53
+ export { renderMainThread };
@@ -78,10 +78,12 @@ export class ListUpdateInfoRecording implements ListUpdateInfo {
78
78
  // __FlushElementTree(listElement);
79
79
  // });
80
80
  __SetAttribute(listElement, 'update-list-info', this.__toAttribute());
81
+ const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(this.list.childNodes);
81
82
  __UpdateListCallbacks(
82
83
  listElement,
83
- componentAtIndexFactory(this.list.childNodes),
84
+ componentAtIndex,
84
85
  enqueueComponentFactory(),
86
+ componentAtIndexes,
85
87
  );
86
88
  }
87
89
 
@@ -243,13 +245,17 @@ export function clearListGlobal(): void {
243
245
  }
244
246
  }
245
247
 
246
- export function componentAtIndexFactory(ctx: SnapshotInstance[]): ComponentAtIndexCallback {
248
+ export function componentAtIndexFactory(
249
+ ctx: SnapshotInstance[],
250
+ ): [ComponentAtIndexCallback, ComponentAtIndexesCallback] {
247
251
  const componentAtIndex = (
248
252
  list: FiberElement,
249
253
  listID: number,
250
254
  cellIndex: number,
251
255
  operationID: number,
252
256
  enableReuseNotification: boolean,
257
+ enableBatchRender: boolean = false,
258
+ asyncFlush: boolean = false,
253
259
  ) => {
254
260
  const signMap = gSignMap[listID];
255
261
  const recycleMap = gRecycleMap[listID];
@@ -284,7 +290,14 @@ export function componentAtIndexFactory(ctx: SnapshotInstance[]): ComponentAtInd
284
290
  if (recycleSignMap?.has(sign)) {
285
291
  signMap.set(sign, childCtx);
286
292
  recycleSignMap.delete(sign);
287
- __FlushElementTree(root, { triggerLayout: true, operationID, elementID: sign, listID });
293
+ if (!enableBatchRender) {
294
+ __FlushElementTree(root, { triggerLayout: true, operationID, elementID: sign, listID });
295
+ } else if (enableBatchRender && asyncFlush) {
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
+ }
288
301
  return sign;
289
302
  } else {
290
303
  const newCtx = childCtx.takeElements();
@@ -299,24 +312,31 @@ export function componentAtIndexFactory(ctx: SnapshotInstance[]): ComponentAtInd
299
312
  hydrate(oldCtx, childCtx);
300
313
  oldCtx.unRenderElements();
301
314
  const root = childCtx.__element_root!;
302
- if (enableReuseNotification) {
303
- __FlushElementTree(root, {
315
+ if (!enableBatchRender) {
316
+ const flushOptions: FlushOptions = {
304
317
  triggerLayout: true,
305
318
  operationID,
306
319
  elementID: sign,
307
320
  listID,
308
- listReuseNotification: {
321
+ };
322
+ if (enableReuseNotification) {
323
+ flushOptions.listReuseNotification = {
309
324
  listElement: list,
310
325
  itemKey: platformInfo['item-key'],
311
- },
312
- });
313
- } else {
314
- __FlushElementTree(root, {
315
- triggerLayout: true,
316
- operationID,
317
- elementID: sign,
318
- listID,
319
- });
326
+ };
327
+ }
328
+ __FlushElementTree(root, flushOptions);
329
+ } else if (enableBatchRender && asyncFlush) {
330
+ const flushOptions: FlushOptions = {
331
+ asyncFlush: true,
332
+ };
333
+ if (enableReuseNotification) {
334
+ flushOptions.listReuseNotification = {
335
+ listElement: list,
336
+ itemKey: platformInfo['item-key'],
337
+ };
338
+ }
339
+ __FlushElementTree(root, flushOptions);
320
340
  }
321
341
  signMap.set(sign, childCtx);
322
342
  commitMainThreadPatchUpdate(undefined);
@@ -327,17 +347,43 @@ export function componentAtIndexFactory(ctx: SnapshotInstance[]): ComponentAtInd
327
347
  const root = childCtx.__element_root!;
328
348
  __AppendElement(list, root);
329
349
  const sign = __GetElementUniqueID(root);
330
- __FlushElementTree(root, {
331
- triggerLayout: true,
332
- operationID,
333
- elementID: sign,
334
- listID,
335
- });
350
+ if (!enableBatchRender) {
351
+ __FlushElementTree(root, {
352
+ triggerLayout: true,
353
+ operationID,
354
+ elementID: sign,
355
+ listID,
356
+ });
357
+ } else if (enableBatchRender && asyncFlush) {
358
+ __FlushElementTree(root, {
359
+ asyncFlush: true,
360
+ });
361
+ }
336
362
  signMap.set(sign, childCtx);
337
363
  commitMainThreadPatchUpdate(undefined);
338
364
  return sign;
339
365
  };
340
- return componentAtIndex;
366
+
367
+ const componentAtIndexes = (
368
+ list: FiberElement,
369
+ listID: number,
370
+ cellIndexes: number[],
371
+ operationIDs: number[],
372
+ enableReuseNotification: boolean,
373
+ asyncFlush: boolean,
374
+ ) => {
375
+ const uiSigns = cellIndexes.map((cellIndex, index) => {
376
+ const operationID = operationIDs[index] ?? 0;
377
+ return componentAtIndex(list, listID, cellIndex, operationID, enableReuseNotification, true, asyncFlush);
378
+ });
379
+ __FlushElementTree(list, {
380
+ triggerLayout: true,
381
+ operationIDs: operationIDs,
382
+ elementIDs: uiSigns,
383
+ listID,
384
+ });
385
+ };
386
+ return [componentAtIndex, componentAtIndexes] as const;
341
387
  }
342
388
 
343
389
  export function enqueueComponentFactory(): EnqueueComponentCallback {
@@ -371,11 +417,13 @@ export function snapshotCreateList(
371
417
  ): FiberElement {
372
418
  const signMap = new Map<number, SnapshotInstance>();
373
419
  const recycleMap = new Map<string, Map<number, SnapshotInstance>>();
420
+ const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory([]);
374
421
  const list = __CreateList(
375
422
  pageId,
376
- componentAtIndexFactory([]),
423
+ componentAtIndex,
377
424
  enqueueComponentFactory(),
378
425
  {},
426
+ componentAtIndexes,
379
427
  );
380
428
  const listID = __GetElementUniqueID(list);
381
429
  gSignMap[listID] = signMap;
@@ -3,7 +3,7 @@
3
3
  // LICENSE file in the root directory of this source tree.
4
4
  import type { DataProcessorDefinition } from '../lynx-api.js';
5
5
 
6
- export function setupLynxEnv(): void {
6
+ export function setupLynxTestingEnv(): void {
7
7
  if (!__LEPUS__) {
8
8
  const { initData, updateData } = lynxCoreInject.tt._params;
9
9
  // @ts-ignore
@@ -2,13 +2,17 @@ import { options } from 'preact';
2
2
  import type { VNode } from 'preact';
3
3
  import { COMPONENT, DIFF, DIFFED, FORCE } from '../renderToOpcodes/constants.js';
4
4
 
5
+ const sForcedVNode = Symbol('FORCE');
6
+
7
+ type PatchedVNode = VNode & { [sForcedVNode]?: true };
8
+
5
9
  export function runWithForce(cb: () => void): void {
6
10
  // save vnode and its `_component` in WeakMap
7
11
  const m = new WeakMap<VNode, any>();
8
12
 
9
13
  const oldDiff = options[DIFF];
10
14
 
11
- options[DIFF] = (vnode: VNode) => {
15
+ options[DIFF] = (vnode: PatchedVNode) => {
12
16
  if (oldDiff) {
13
17
  oldDiff(vnode);
14
18
  }
@@ -28,19 +32,26 @@ export function runWithForce(cb: () => void): void {
28
32
  return m.get(vnode);
29
33
  },
30
34
  });
35
+ vnode[sForcedVNode] = true;
31
36
  };
32
37
 
33
38
  const oldDiffed = options[DIFFED];
34
39
 
35
- options[DIFFED] = (vnode: VNode) => {
40
+ options[DIFFED] = (vnode: PatchedVNode) => {
36
41
  if (oldDiffed) {
37
42
  oldDiffed(vnode);
38
43
  }
39
44
 
40
- // delete is a reverse operation of previous `Object.defineProperty`
41
- delete vnode[COMPONENT];
42
- // restore
43
- vnode[COMPONENT] = m.get(vnode);
45
+ // There would be cases when `options[DIFF]` has been reset while options[DIFFED] is not,
46
+ // so we need to check if `vnode` is patched by `options[DIFF]`.
47
+ // We only want to change the patched vnode
48
+ if (vnode[sForcedVNode]) {
49
+ // delete is a reverse operation of previous `Object.defineProperty`
50
+ delete vnode[COMPONENT];
51
+ delete vnode[sForcedVNode];
52
+ // restore
53
+ vnode[COMPONENT] = m.get(vnode);
54
+ }
44
55
  };
45
56
 
46
57
  try {