@lynx-js/react 0.107.1 → 0.108.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 (97) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +4 -2
  3. package/package.json +3 -3
  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/snapshotPatch.d.ts +5 -0
  26. package/runtime/lib/lifecycle/patch/snapshotPatch.js +5 -0
  27. package/runtime/lib/lifecycle/patch/snapshotPatch.js.map +1 -1
  28. package/runtime/lib/lifecycle/patch/snapshotPatchApply.d.ts +5 -0
  29. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js +21 -5
  30. package/runtime/lib/lifecycle/patch/snapshotPatchApply.js.map +1 -1
  31. package/runtime/lib/lifecycle/reload.js +9 -4
  32. package/runtime/lib/lifecycle/reload.js.map +1 -1
  33. package/runtime/lib/lifecycle/render.d.ts +1 -3
  34. package/runtime/lib/lifecycle/render.js +4 -6
  35. package/runtime/lib/lifecycle/render.js.map +1 -1
  36. package/runtime/lib/list.d.ts +1 -1
  37. package/runtime/lib/list.js +60 -23
  38. package/runtime/lib/list.js.map +1 -1
  39. package/runtime/lib/lynx/env.d.ts +1 -1
  40. package/runtime/lib/lynx/env.js +1 -1
  41. package/runtime/lib/lynx/env.js.map +1 -1
  42. package/runtime/lib/lynx/runWithForce.js +12 -4
  43. package/runtime/lib/lynx/runWithForce.js.map +1 -1
  44. package/runtime/lib/lynx/tt.js +6 -8
  45. package/runtime/lib/lynx/tt.js.map +1 -1
  46. package/runtime/lib/lynx-api.d.ts +4 -4
  47. package/runtime/lib/lynx-api.js +5 -5
  48. package/runtime/lib/lynx-api.js.map +1 -1
  49. package/runtime/lib/lynx.js +6 -5
  50. package/runtime/lib/lynx.js.map +1 -1
  51. package/runtime/lib/opcodes.js +2 -2
  52. package/runtime/lib/opcodes.js.map +1 -1
  53. package/runtime/lib/renderToOpcodes/index.js +5 -1
  54. package/runtime/lib/renderToOpcodes/index.js.map +1 -1
  55. package/runtime/lib/root.d.ts +4 -0
  56. package/runtime/lib/root.js +6 -2
  57. package/runtime/lib/root.js.map +1 -1
  58. package/runtime/lib/snapshot/ref.d.ts +1 -1
  59. package/runtime/lib/snapshot/ref.js +1 -1
  60. package/runtime/lib/snapshot/ref.js.map +1 -1
  61. package/runtime/lib/snapshot/spread.d.ts +8 -2
  62. package/runtime/lib/snapshot/spread.js +12 -6
  63. package/runtime/lib/snapshot/spread.js.map +1 -1
  64. package/runtime/lib/snapshot.d.ts +31 -5
  65. package/runtime/lib/snapshot.js +15 -3
  66. package/runtime/lib/snapshot.js.map +1 -1
  67. package/runtime/src/backgroundSnapshot.ts +20 -13
  68. package/runtime/src/document.ts +33 -2
  69. package/runtime/src/hydrate.ts +3 -1
  70. package/runtime/src/lifecycle/delayUnmount.ts +2 -2
  71. package/runtime/src/lifecycle/destroy.ts +3 -2
  72. package/runtime/src/lifecycle/patch/commit.ts +75 -72
  73. package/runtime/src/lifecycle/patch/snapshotPatch.ts +9 -0
  74. package/runtime/src/lifecycle/patch/snapshotPatchApply.ts +20 -5
  75. package/runtime/src/lifecycle/reload.ts +12 -4
  76. package/runtime/src/lifecycle/render.ts +6 -8
  77. package/runtime/src/list.ts +71 -23
  78. package/runtime/src/lynx/env.ts +1 -1
  79. package/runtime/src/lynx/runWithForce.ts +17 -6
  80. package/runtime/src/lynx/tt.ts +10 -17
  81. package/runtime/src/lynx-api.ts +7 -7
  82. package/runtime/src/lynx.ts +7 -6
  83. package/runtime/src/opcodes.ts +2 -2
  84. package/runtime/src/renderToOpcodes/index.ts +6 -1
  85. package/runtime/src/root.ts +6 -2
  86. package/runtime/src/snapshot/ref.ts +5 -5
  87. package/runtime/src/snapshot/spread.ts +14 -6
  88. package/runtime/src/snapshot.ts +35 -10
  89. package/testing-library/README.md +1 -1
  90. package/testing-library/dist/env/vitest.js +23 -23
  91. package/testing-library/dist/index.d.ts +24 -24
  92. package/testing-library/dist/index.js +2 -2
  93. package/testing-library/dist/pure.js +13 -15
  94. package/testing-library/dist/vitest-global-setup.js +13 -14
  95. package/testing-library/types/entry.d.ts +2 -2
  96. package/testing-library/types/index.d.ts +3 -3
  97. package/transform/dist/wasm.cjs +1 -1
@@ -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 {
@@ -1,34 +1,30 @@
1
1
  // Copyright 2024 The Lynx Authors. All rights reserved.
2
2
  // Licensed under the Apache License Version 2.0 that can be found in the
3
3
  // LICENSE file in the root directory of this source tree.
4
+ import { render } from 'preact';
5
+
4
6
  import { LifecycleConstant, NativeUpdateDataType } from '../lifecycleConstant.js';
5
7
  import {
6
- PerformanceTimingKeys,
7
8
  PerformanceTimingFlags,
9
+ PerformanceTimingKeys,
8
10
  PipelineOrigins,
9
11
  beginPipeline,
10
12
  markTiming,
11
13
  } from './performance.js';
12
14
  import { BackgroundSnapshotInstance, hydrate } from '../backgroundSnapshot.js';
15
+ import { runWithForce } from './runWithForce.js';
13
16
  import { destroyBackground } from '../lifecycle/destroy.js';
14
17
  import { delayedEvents, delayedPublishEvent } from '../lifecycle/event/delayEvents.js';
15
18
  import { delayLifecycleEvent, delayedLifecycleEvents } from '../lifecycle/event/delayLifecycleEvents.js';
16
- import {
17
- clearPatchesToCommit,
18
- commitPatchUpdate,
19
- genCommitTaskId,
20
- globalCommitTaskMap,
21
- patchesToCommit,
22
- type PatchList,
23
- } from '../lifecycle/patch/commit.js';
19
+ import { commitPatchUpdate, genCommitTaskId, globalCommitTaskMap } from '../lifecycle/patch/commit.js';
20
+ import type { PatchList } from '../lifecycle/patch/commit.js';
24
21
  import { reloadBackground } from '../lifecycle/reload.js';
25
- import { renderBackground } from '../lifecycle/render.js';
26
22
  import { CHILDREN } from '../renderToOpcodes/constants.js';
27
23
  import { __root } from '../root.js';
28
24
  import { globalRefsToSet, updateBackgroundRefs } from '../snapshot/ref.js';
29
25
  import { backgroundSnapshotInstanceManager } from '../snapshot.js';
30
26
  import { destroyWorklet } from '../worklet/destroy.js';
31
- import { runWithForce } from './runWithForce.js';
27
+
32
28
  export { runWithForce };
33
29
 
34
30
  function injectTt(): void {
@@ -129,14 +125,11 @@ function onLifecycleEventImpl(type: string, data: any): void {
129
125
  console.profile('commitChanges');
130
126
  }
131
127
  const commitTaskId = genCommitTaskId();
132
- patchesToCommit.push(
133
- { snapshotPatch, id: commitTaskId },
134
- );
135
128
  const patchList: PatchList = {
136
- patchList: patchesToCommit,
129
+ patchList: [{ snapshotPatch, id: commitTaskId }],
137
130
  };
138
- clearPatchesToCommit();
139
131
  const obj = commitPatchUpdate(patchList, { isHydration: true });
132
+
140
133
  lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => {
141
134
  updateBackgroundRefs(commitTaskId);
142
135
  globalCommitTaskMap.forEach((commitTask, id) => {
@@ -212,7 +205,7 @@ function updateGlobalProps(newData: Record<string, any>): void {
212
205
  // This is already done because updateFromRoot will consume all dirty flags marked by
213
206
  // the setState, and setState's flush will be a noop. No extra diffs will be needed.
214
207
  Promise.resolve().then(() => {
215
- runWithForce(() => renderBackground(__root.__jsx, __root as any));
208
+ runWithForce(() => render(__root.__jsx, __root as any));
216
209
  });
217
210
  lynxCoreInject.tt.GlobalEventEmitter.emit('onGlobalPropsChanged');
218
211
  }
@@ -1,13 +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
+ import { render } from 'preact';
4
5
  import { createContext, createElement } from 'preact/compat';
5
6
  import { useState } from 'preact/hooks';
6
7
  import type { Consumer, FC, ReactNode } from 'react';
7
8
 
8
9
  import { factory, withInitDataInState } from './compat/initData.js';
9
10
  import { useLynxGlobalEventListener } from './hooks/useLynxGlobalEventListener.js';
10
- import { renderBackground } from './lifecycle/render.js';
11
11
  import { LifecycleConstant } from './lifecycleConstant.js';
12
12
  import { flushDelayedLifecycleEvents } from './lynx/tt.js';
13
13
  import { __root } from './root.js';
@@ -43,13 +43,13 @@ export interface Root {
43
43
  * return <view>...</view>
44
44
  * }
45
45
  *
46
- * if (__LEPUS__) {
46
+ * if (__MAIN_THREAD__) {
47
47
  * root.render(
48
48
  * <DataProvider data={DEFAULT_DATA}>
49
49
  * <App/>
50
50
  * </DataProvider>
51
51
  * );
52
- * } else if (__JS__) {
52
+ * } else if (__BACKGROUND__) {
53
53
  * fetchData().then((data) => {
54
54
  * root.render(
55
55
  * <DataProvider data={data}>
@@ -82,11 +82,11 @@ export interface Root {
82
82
  */
83
83
  export const root: Root = {
84
84
  render: (jsx: ReactNode): void => {
85
- if (__LEPUS__) {
85
+ if (__MAIN_THREAD__) {
86
86
  __root.__jsx = jsx;
87
87
  } else {
88
88
  __root.__jsx = jsx;
89
- renderBackground(jsx, __root as any);
89
+ render(jsx, __root as any);
90
90
  if (__FIRST_SCREEN_SYNC_TIMING__ === 'immediately') {
91
91
  // This is for cases where `root.render()` is called asynchronously,
92
92
  // `firstScreen` message might have been reached.
@@ -370,7 +370,7 @@ export interface Lynx {
370
370
  registerDataProcessors: (dataProcessorDefinition?: DataProcessorDefinition) => void;
371
371
  }
372
372
 
373
- export { runOnMainThread } from './worklet/runOnMainThread.js';
373
+ export { useLynxGlobalEventListener } from './hooks/useLynxGlobalEventListener.js';
374
374
  export { runOnBackground } from './worklet/runOnBackground.js';
375
+ export { runOnMainThread } from './worklet/runOnMainThread.js';
375
376
  export { MainThreadRef, useMainThreadRef } from './worklet/workletRef.js';
376
- export { useLynxGlobalEventListener } from './hooks/useLynxGlobalEventListener.js';
@@ -11,21 +11,21 @@ import { initDelayUnmount } from './lifecycle/delayUnmount.js';
11
11
  import { replaceCommitHook, replaceRequestAnimationFrame } from './lifecycle/patch/commit.js';
12
12
  import { injectUpdateMainThread } from './lifecycle/patch/updateMainThread.js';
13
13
  import { injectCalledByNative } from './lynx/calledByNative.js';
14
- import { setupLynxEnv } from './lynx/env.js';
14
+ import { setupLynxTestingEnv } from './lynx/env.js';
15
15
  import { injectLepusMethods } from './lynx/injectLepusMethods.js';
16
16
  import { initTimingAPI } from './lynx/performance.js';
17
17
  import { injectTt } from './lynx/tt.js';
18
18
  export { runWithForce } from './lynx/runWithForce.js';
19
19
 
20
20
  // @ts-expect-error Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature
21
- if (__LEPUS__ && typeof globalThis.processEvalResult === 'undefined') {
21
+ if (__MAIN_THREAD__ && typeof globalThis.processEvalResult === 'undefined') {
22
22
  // @ts-expect-error Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature
23
23
  globalThis.processEvalResult = <T>(result: ((schema: string) => T) | undefined, schema: string) => {
24
24
  return result?.(schema);
25
25
  };
26
26
  }
27
27
 
28
- if (__LEPUS__) {
28
+ if (__MAIN_THREAD__) {
29
29
  injectCalledByNative();
30
30
  injectUpdateMainThread();
31
31
  if (__DEV__) {
@@ -39,8 +39,9 @@ if (__PROFILE__) {
39
39
  initProfileHook();
40
40
  }
41
41
 
42
- if (__JS__) {
43
- options.document = document;
42
+ if (__BACKGROUND__) {
43
+ // Trick Preact and TypeScript to accept our custom document adapter.
44
+ options.document = document as any;
44
45
  setupBackgroundDocument();
45
46
  injectTt();
46
47
 
@@ -53,4 +54,4 @@ if (__JS__) {
53
54
  }
54
55
  }
55
56
 
56
- setupLynxEnv();
57
+ setupLynxTestingEnv();
@@ -52,7 +52,7 @@ export function ssrHydrateByOpcodes(
52
52
  const signMap = gSignMap[listElementUniqueID] = new Map();
53
53
  gRecycleMap[listElementUniqueID] = new Map();
54
54
  const enqueueFunc = enqueueComponentFactory();
55
- const componentAtIndex = componentAtIndexFactory(top.childNodes);
55
+ const [componentAtIndex, componentAtIndexes] = componentAtIndexFactory(top.childNodes);
56
56
  for (const child of top.childNodes) {
57
57
  if (child.__element_root) {
58
58
  const childElementUniqueID = __GetElementUniqueID(child.__element_root);
@@ -64,7 +64,7 @@ export function ssrHydrateByOpcodes(
64
64
  );
65
65
  }
66
66
  }
67
- __UpdateListCallbacks(listElement, componentAtIndex, enqueueFunc);
67
+ __UpdateListCallbacks(listElement, componentAtIndex, enqueueFunc, componentAtIndexes);
68
68
  }
69
69
 
70
70
  stack.pop();
@@ -2,7 +2,12 @@
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
- // modified from preact-render-to-string@6.0.3
5
+ /**
6
+ * Implements rendering to opcodes.
7
+ * This module is modified from preact-render-to-string@6.0.3 to generate
8
+ * opcodes instead of HTML strings for Lynx.
9
+ */
10
+
6
11
  // @ts-nocheck
7
12
 
8
13
  import { Fragment, h, options } from 'preact';
@@ -4,15 +4,19 @@
4
4
  import { BackgroundSnapshotInstance } from './backgroundSnapshot.js';
5
5
  import { SnapshotInstance } from './snapshot.js';
6
6
 
7
+ /**
8
+ * The internal ReactLynx's root.
9
+ * {@link @lynx-js/react!Root | root}.
10
+ */
7
11
  let __root: (SnapshotInstance | BackgroundSnapshotInstance) & { __jsx?: React.ReactNode; __opcodes?: any[] };
8
12
 
9
13
  function setRoot(root: typeof __root): void {
10
14
  __root = root;
11
15
  }
12
16
 
13
- if (__LEPUS__) {
17
+ if (__MAIN_THREAD__) {
14
18
  setRoot(new SnapshotInstance('root'));
15
- } else if (__JS__) {
19
+ } else if (__BACKGROUND__) {
16
20
  setRoot(new BackgroundSnapshotInstance('root'));
17
21
  }
18
22
 
@@ -137,12 +137,12 @@ function markRefToRemove(sign: string, ref: unknown): void {
137
137
  }
138
138
 
139
139
  export {
140
- updateRef,
141
- takeGlobalRefPatchMap,
142
- updateBackgroundRefs,
143
- unref,
144
- transformRef,
145
140
  globalRefsToRemove,
146
141
  globalRefsToSet,
147
142
  markRefToRemove,
143
+ takeGlobalRefPatchMap,
144
+ transformRef,
145
+ unref,
146
+ updateBackgroundRefs,
147
+ updateRef,
148
148
  };
@@ -1,16 +1,24 @@
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
+ * Handles JSX spread operator in the snapshot system.
7
+ *
8
+ * Spread operators in JSX (e.g., <div {...props}>) are transformed into
9
+ * optimized attribute updates at compile time, avoiding runtime object spreads.
10
+ */
11
+
12
+ import { BackgroundSnapshotInstance } from '../backgroundSnapshot.js';
13
+ import { __pendingListUpdates, ListUpdateInfoRecording } from '../list.js';
4
14
  import { SnapshotInstance } from '../snapshot.js';
15
+ import { isDirectOrDeepEqual, isEmptyObject, pick } from '../utils.js';
5
16
  import { updateEvent } from './event.js';
6
- import { BackgroundSnapshotInstance } from '../backgroundSnapshot.js';
17
+ import { updateGesture } from './gesture.js';
18
+ import { platformInfoAttributes, updateListItemPlatformInfo } from './platformInfo.js';
7
19
  import { transformRef, updateRef } from './ref.js';
8
20
  import { updateWorkletEvent } from './workletEvent.js';
9
21
  import { updateWorkletRef } from './workletRef.js';
10
- import { updateGesture } from './gesture.js';
11
- import { platformInfoAttributes, updateListItemPlatformInfo } from './platformInfo.js';
12
- import { isDirectOrDeepEqual, isEmptyObject, pick } from '../utils.js';
13
- import { __pendingListUpdates, ListUpdateInfoRecording } from '../list.js';
14
22
 
15
23
  const eventRegExp = /^(([A-Za-z-]*):)?(bind|catch|capture-bind|capture-catch|global-bind)([A-Za-z]+)$/;
16
24
  const eventTypeMap: Record<string, string> = {
@@ -284,4 +292,4 @@ function transformSpread(
284
292
  return result;
285
293
  }
286
294
 
287
- export { updateSpread, transformSpread };
295
+ export { transformSpread, updateSpread };
@@ -1,26 +1,44 @@
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
+ * Core snapshot system that implements a compiler-hinted virtual DOM.
7
+ *
8
+ * Key components:
9
+ * 1. {@link Snapshot}: Template definition generated at compile time
10
+ * 2. {@link SnapshotInstance}: Runtime instance in the main thread
11
+ * 3. {@link BackgroundSnapshotInstance}: Runtime instance in the background thread
12
+ *
13
+ * The system uses static analysis to identify dynamic parts and generate
14
+ * optimized update instructions, avoiding full virtual DOM diffing.
15
+ */
16
+
4
17
  import type { Worklet, WorkletRefImpl } from '@lynx-js/react/worklet-runtime/bindings';
5
18
 
6
19
  import type { BackgroundSnapshotInstance } from './backgroundSnapshot.js';
20
+ import { SnapshotOperation, __globalSnapshotPatch } from './lifecycle/patch/snapshotPatch.js';
7
21
  import { ListUpdateInfoRecording, __pendingListUpdates, snapshotDestroyList } from './list.js';
8
22
  import { unref } from './snapshot/ref.js';
9
- import { SnapshotOperation, __globalSnapshotPatch } from './lifecycle/patch/snapshotPatch.js';
10
23
  import { isDirectOrDeepEqual } from './utils.js';
11
24
 
25
+ /**
26
+ * Types of dynamic parts that can be updated in a snapshot
27
+ * These are determined at compile time through static analysis
28
+ */
12
29
  export const enum DynamicPartType {
13
- Attr = 0,
14
- Spread,
15
- Slot,
16
- // Component,
17
- Children,
18
- ListChildren,
19
-
20
- // Used by compat layer
21
- MultiChildren,
30
+ Attr = 0, // Regular attribute updates
31
+ Spread, // Spread operator in JSX
32
+ Slot, // Slot for component children
33
+ Children, // Regular children updates
34
+ ListChildren, // List/array children updates
35
+ MultiChildren, // Multiple children updates (compat layer)
22
36
  }
23
37
 
38
+ /**
39
+ * A snapshot definition that contains all the information needed to create and update elements
40
+ * This is generated at compile time through static analysis of the JSX
41
+ */
24
42
  interface Snapshot {
25
43
  create: null | ((ctx: SnapshotInstance) => FiberElement[]);
26
44
  update: null | ((ctx: SnapshotInstance, index: number, oldValue: any) => void)[];
@@ -234,6 +252,13 @@ export interface SerializedSnapshotInstance {
234
252
  const DEFAULT_ENTRY_NAME = '__Card__';
235
253
  const DEFAULT_CSS_ID = 0;
236
254
 
255
+ /**
256
+ * The runtime instance of a {@link Snapshot} on the main thread that manages
257
+ * the actual elements and handles updates to dynamic parts.
258
+ *
259
+ * This class is designed to be compatible with Preact's {@link ContainerNode}
260
+ * interface for Preact's renderer to operate upon.
261
+ */
237
262
  export class SnapshotInstance {
238
263
  __id: number;
239
264
  __snapshot_def: Snapshot;
@@ -58,7 +58,7 @@ test('renders options.wrapper around node', async () => {
58
58
  });
59
59
  ```
60
60
 
61
- 💡 Since our testing environment (`@lynx-js/test-environment`) is based on jsdom, You may also be interested in installing `@testing-library/jest-dom` so you can use
61
+ 💡 Since our testing environment (`@lynx-js/testing-environment`) is based on jsdom, You may also be interested in installing `@testing-library/jest-dom` so you can use
62
62
  [the custom jest matchers](https://github.com/testing-library/jest-dom).
63
63
 
64
64
  ## Examples