@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
@@ -9,15 +9,15 @@ export function wrapWithLynxComponent(
9
9
  jsxComponent: any,
10
10
  ): ReactNode {
11
11
  const C = jsxComponent.type;
12
- if (typeof C === 'function') {
13
- if (C === ComponentFromReactRuntime || C.prototype instanceof ComponentFromReactRuntime) {
14
- if (jsxSnapshot.length === 1) {
15
- return jsxSnapshot(jsxComponent);
16
- } else {
17
- // spread
18
- if (!jsxComponent.props.removeComponentElement) {
19
- return jsxSnapshot(jsxComponent, takeComponentAttributes(jsxComponent));
20
- }
12
+ if (
13
+ typeof C === 'function' && (C === ComponentFromReactRuntime || C.prototype instanceof ComponentFromReactRuntime)
14
+ ) {
15
+ if (jsxSnapshot.length === 1) {
16
+ return jsxSnapshot(jsxComponent);
17
+ } else {
18
+ // spread
19
+ if (!jsxComponent.props.removeComponentElement) {
20
+ return jsxSnapshot(jsxComponent, takeComponentAttributes(jsxComponent));
21
21
  }
22
22
  }
23
23
  }
@@ -25,8 +25,7 @@ export function wrapWithLynxComponent(
25
25
  }
26
26
 
27
27
  // @ts-expect-error
28
- export class ComponentFromReactRuntime extends Component {
29
- }
28
+ export class ComponentFromReactRuntime extends Component {}
30
29
 
31
30
  const __COMPONENT_ATTRIBUTES__ = /* @__PURE__ */ new Set([
32
31
  'name',
@@ -68,8 +67,8 @@ function takeComponentAttributes(jsxComponent: any): Record<string, any> {
68
67
  || k === 'dataSet'
69
68
  || k === 'data-set'
70
69
  || k === 'removeComponentElement'
71
- || k.match(/^(global-bind|bind|catch|capture-bind|capture-catch)([A-Za-z]+)$/)
72
- || k.match(/^data-([A-Za-z]+)$/)
70
+ || (/^(global-bind|bind|catch|capture-bind|capture-catch)([A-Za-z]+)$/.exec(k))
71
+ || (/^data-([A-Za-z]+)$/.exec(k))
73
72
  ) {
74
73
  attributes[k] = jsxComponent.props[k];
75
74
  delete jsxComponent.props[k];
@@ -29,6 +29,7 @@ export function initProfileHook(): void {
29
29
  const oldRender = options[RENDER];
30
30
  options[RENDER] = function(vnode: VNode & { [COMPONENT]: Component }) {
31
31
  const displayName = getDisplayName(vnode.type as ComponentClass);
32
+ // eslint-disable-next-line @typescript-eslint/unbound-method
32
33
  const originalRender = vnode[COMPONENT].render;
33
34
  vnode[COMPONENT].render = function render(this, props, state, context) {
34
35
  // This __PROFILE__ is used for DCE testing
@@ -1,5 +1,9 @@
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
+ import { GestureTypeInner } from './types.js';
5
+ import type { BaseGesture, ComposedGesture, GestureKind } from './types.js';
1
6
  import { onPostWorkletCtx } from '../worklet/ctx.js';
2
- import { type GestureKind, GestureTypeInner, type ComposedGesture, type BaseGesture } from './types.js';
3
7
 
4
8
  export function processGestureBackground(gesture: GestureKind): void {
5
9
  if (gesture.type === GestureTypeInner.COMPOSED) {
@@ -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 type { Worklet } from '@lynx-js/react/worklet-runtime/bindings';
2
5
 
3
6
  export enum GestureTypeInner {
@@ -9,7 +9,7 @@ import {
9
9
  useId,
10
10
  useImperativeHandle,
11
11
  useMemo,
12
- useLayoutEffect as usePreactLayoutEffect,
12
+ useEffect as usePreactEffect,
13
13
  useReducer,
14
14
  useRef,
15
15
  useState,
@@ -29,7 +29,7 @@ import type { DependencyList, EffectCallback } from 'react';
29
29
  * @deprecated `useLayoutEffect` in the background thread cannot offer the precise timing for reading layout information and synchronously re-render, which is different from React.
30
30
  */
31
31
  function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void {
32
- return usePreactLayoutEffect(effect, deps);
32
+ return usePreactEffect(effect, deps);
33
33
  }
34
34
 
35
35
  /**
@@ -42,7 +42,7 @@ function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void {
42
42
  * @public
43
43
  */
44
44
  function useEffect(effect: EffectCallback, deps?: DependencyList): void {
45
- return usePreactLayoutEffect(effect, deps);
45
+ return usePreactEffect(effect, deps);
46
46
  }
47
47
 
48
48
  export {
@@ -31,7 +31,7 @@ import { useEffect, useMemo, useRef } from 'preact/hooks';
31
31
  * @param listener - Event handler
32
32
  * @public
33
33
  */
34
- export function useLynxGlobalEventListener<T extends (...args: unknown[]) => void>(
34
+ export function useLynxGlobalEventListener<T extends (...args: any[]) => void>(
35
35
  eventName: string,
36
36
  listener: T,
37
37
  ): void {
@@ -2,9 +2,11 @@
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 { __pendingListUpdates, componentAtIndexFactory, enqueueComponentFactory } from './list.js';
5
+ import { componentAtIndexFactory, enqueueComponentFactory } from './list.js';
6
+ import { __pendingListUpdates } from './pendingListUpdates.js';
7
+ import { DynamicPartType } from './snapshot/dynamicPartType.js';
6
8
  import { unref } from './snapshot/ref.js';
7
- import { DynamicPartType, SnapshotInstance } from './snapshot.js';
9
+ import type { SnapshotInstance } from './snapshot.js';
8
10
  import { isEmptyObject } from './utils.js';
9
11
 
10
12
  export interface DiffResult<K> {
@@ -215,7 +217,7 @@ export function hydrate(before: SnapshotInstance, after: SnapshotInstance, optio
215
217
  }
216
218
 
217
219
  let swap;
218
- if (swap = options?.swap) {
220
+ if ((swap = options?.swap)) {
219
221
  swap[before.__id] = after.__id;
220
222
  }
221
223
 
@@ -349,7 +351,7 @@ export function hydrate(before: SnapshotInstance, after: SnapshotInstance, optio
349
351
 
350
352
  const listElement = before.__elements![elementIndex]!;
351
353
  __SetAttribute(listElement, 'update-list-info', info);
352
- const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(afterChildNodes);
354
+ const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(afterChildNodes, hydrate);
353
355
  __UpdateListCallbacks(
354
356
  listElement,
355
357
  componentAtIndex,
@@ -9,10 +9,11 @@ import './lynx.js';
9
9
 
10
10
  import { factory as factory2 } from './compat/componentIs.js';
11
11
  import { useMemo } from './hooks/react.js';
12
- import { snapshotCreateList } from './list.js';
13
12
  import { loadLazyBundle } from './lynx/lazy-bundle.js';
14
13
  import { __root } from './root.js';
15
- import { DynamicPartType, SnapshotInstance, __page, __pageId, createSnapshot, snapshotManager } from './snapshot.js';
14
+ import { DynamicPartType } from './snapshot/dynamicPartType.js';
15
+ import { snapshotCreateList } from './snapshot/list.js';
16
+ import { SnapshotInstance, __page, __pageId, createSnapshot, snapshotManager } from './snapshot.js';
16
17
 
17
18
  export { __page, __pageId, __root };
18
19
 
@@ -8,7 +8,7 @@ import {
8
8
  ComponentFromReactRuntime as Component,
9
9
  ComponentFromReactRuntime as PureComponent,
10
10
  } from '../compat/lynxComponent.js';
11
- import { useCallback, useEffect, useMemo, useReducer, useState, useRef } from '../hooks/react.js';
11
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from '../hooks/react.js';
12
12
 
13
13
  /* v8 ignore next 3 */
14
14
  function __runInJS<T>(value: T): T | undefined | null {
@@ -13,6 +13,7 @@ function destroyBackground(): void {
13
13
  console.profile('destroyBackground');
14
14
  }
15
15
 
16
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
16
17
  render(null, __root as any);
17
18
 
18
19
  globalCommitTaskMap.forEach(task => {
@@ -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
  let delayedEvents: [handlerName: string, data: unknown][] | undefined;
2
5
 
3
6
  function delayedPublishEvent(handlerName: string, data: unknown): void {
@@ -1,18 +1,12 @@
1
- import { LifecycleConstant } from '../../lifecycleConstant.js';
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
+ import type { LifecycleConstant } from '../../lifecycleConstant.js';
2
5
 
3
- const delayedLifecycleEvents: [type: string, data: any][] = [];
6
+ const delayedLifecycleEvents: [type: LifecycleConstant, data: unknown][] = [];
4
7
 
5
- function delayLifecycleEvent(type: string, data: any): void {
6
- // We need to ensure that firstScreen events are executed before other events.
7
- // This is because firstScreen events are used to initialize the dom tree,
8
- // and other events depend on the dom tree being fully constructed.
9
- // There might be some edge cases where ctx cannot be found in `ref` lifecycle event,
10
- // and they should be ignored safely.
11
- if (type === LifecycleConstant.firstScreen) {
12
- delayedLifecycleEvents.unshift([type, data]);
13
- } else {
14
- delayedLifecycleEvents.push([type, data]);
15
- }
8
+ function delayLifecycleEvent(type: LifecycleConstant, data: unknown): void {
9
+ delayedLifecycleEvents.push([type, data]);
16
10
  }
17
11
 
18
12
  /**
@@ -1,9 +1,11 @@
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 { LifecycleConstant } from '../../lifecycleConstant.js';
2
5
  import { __root } from '../../root.js';
3
- import { takeGlobalRefPatchMap } from '../../snapshot/ref.js';
4
6
 
5
7
  let isJSReady: boolean;
6
- let jsReadyEventIdSwap: Record<number, number>;
8
+ let jsReadyEventIdSwap: Record<string | number, number>;
7
9
 
8
10
  function jsReady(): void {
9
11
  isJSReady = true;
@@ -11,7 +13,6 @@ function jsReady(): void {
11
13
  LifecycleConstant.firstScreen, /* FIRST_SCREEN */
12
14
  {
13
15
  root: JSON.stringify(__root),
14
- refPatch: JSON.stringify(takeGlobalRefPatchMap()),
15
16
  jsReadyEventIdSwap,
16
17
  },
17
18
  ]);
@@ -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,10 @@
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';
19
+ import type { DynamicPartType } from '../../snapshot/dynamicPartType.js';
16
20
  import {
17
21
  SnapshotInstance,
18
22
  createSnapshot,
@@ -20,12 +24,6 @@ import {
20
24
  snapshotInstanceManager,
21
25
  snapshotManager,
22
26
  } 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
- }
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
  }
@@ -138,7 +138,7 @@ function evaluate<T>(code: string): T {
138
138
  if (__DEV__) {
139
139
  // We are using `eval` here to make the updated snapshot to access variables like `__webpack_require__`.
140
140
  // See: https://github.com/lynx-family/lynx-stack/issues/983.
141
- return eval(`(() => ${code})()`);
141
+ return eval(`(() => ${code})()`) as T;
142
142
  }
143
143
  throw new Error('unreachable: evaluate is not supported in production');
144
144
  }
@@ -5,15 +5,14 @@
5
5
  import { updateWorkletRefInitValueChanges } from '@lynx-js/react/worklet-runtime/bindings';
6
6
 
7
7
  import type { PatchList, PatchOptions } from './commit.js';
8
+ import { setMainThreadHydrationFinished } from './isMainThreadHydrationFinished.js';
8
9
  import { snapshotPatchApply } from './snapshotPatchApply.js';
9
10
  import { LifecycleConstant } from '../../lifecycleConstant.js';
10
- import { __pendingListUpdates } from '../../list.js';
11
- import { markTiming, PerformanceTimingKeys, setPipeline } from '../../lynx/performance.js';
12
- import { takeGlobalRefPatchMap } from '../../snapshot/ref.js';
11
+ import { PerformanceTimingKeys, markTiming, setPipeline } from '../../lynx/performance.js';
12
+ import { __pendingListUpdates } from '../../pendingListUpdates.js';
13
+ import { applyRefQueue } from '../../snapshot/workletRef.js';
13
14
  import { __page } from '../../snapshot.js';
14
- import { isEmptyObject } from '../../utils.js';
15
15
  import { getReloadVersion } from '../pass.js';
16
- import { setMainThreadHydrationFinished } from './isMainThreadHydrationFinished.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 };