diff --git a/benchmark/react/cases/002-hello-reactLynx/RecursiveText.tsx b/benchmark/react/cases/002-hello-reactLynx/RecursiveText.tsx
new file mode 100644
index 0000000000..75d206c2ce
--- /dev/null
+++ b/benchmark/react/cases/002-hello-reactLynx/RecursiveText.tsx
@@ -0,0 +1,20 @@
+// Copyright 2025 The Lynx Authors. All rights reserved.
+// Licensed under the Apache License Version 2.0 that can be found in the
+// LICENSE file in the root directory of this source tree.
+
+function RecursiveText(props: { text: string }) {
+ const { text } = props;
+ const sliced = [...text];
+ const [first, ...rest] = sliced;
+
+ return (
+ sliced.length > 0 && (
+
+ {first}
+
+
+ )
+ );
+}
+
+export default RecursiveText;
diff --git a/benchmark/react/cases/002-hello-reactLynx/index.tsx b/benchmark/react/cases/002-hello-reactLynx/index.tsx
index 289164f480..9e241c6812 100644
--- a/benchmark/react/cases/002-hello-reactLynx/index.tsx
+++ b/benchmark/react/cases/002-hello-reactLynx/index.tsx
@@ -4,26 +4,12 @@
import { root } from '@lynx-js/react';
+import RecursiveText from './RecursiveText.js';
import { RunBenchmarkUntilHydrate } from '../../src/RunBenchmarkUntil.js';
-function F(props: { text: string }) {
- const { text } = props;
- const sliced = [...text];
- const [first, ...rest] = sliced;
-
- return (
- sliced.length > 0 && (
-
- {first}
-
-
- )
- );
-}
-
root.render(
<>
-
+
>,
);
diff --git a/benchmark/react/cases/003-hello-list/index.tsx b/benchmark/react/cases/003-hello-list/index.tsx
new file mode 100644
index 0000000000..32479814bb
--- /dev/null
+++ b/benchmark/react/cases/003-hello-list/index.tsx
@@ -0,0 +1,73 @@
+// Copyright 2025 The Lynx Authors. All rights reserved.
+// Licensed under the Apache License Version 2.0 that can be found in the
+// LICENSE file in the root directory of this source tree.
+
+import { memo, root, useEffect, useRef, useState } from '@lynx-js/react';
+import type { NodesRef } from '@lynx-js/types';
+
+import RecursiveText from '../002-hello-reactLynx/RecursiveText.js';
+
+const MemoedRecursiveText = /* @__PURE__ */ memo(RecursiveText);
+
+function BenchmarkList() {
+ const [stopBenchmark, setStopBenchmark] = useState(false);
+ const listRef = useRef(null);
+ useEffect(() => {
+ listRef.current!.invoke({
+ method: 'scrollToPosition',
+ params: {
+ position: 1,
+ smooth: false,
+ },
+ success: function() {
+ listRef.current!.invoke({
+ method: 'scrollToPosition',
+ params: {
+ position: 2,
+ smooth: false,
+ },
+ success: function() {
+ setStopBenchmark(true);
+ },
+ }).exec();
+ },
+ }).exec();
+ }, []);
+
+ return (
+
+ {Array.from(
+ { length: 5 },
+ (_, index) => (
+
+
+
+
+
+ ),
+ )}
+
+ );
+}
+
+root.render(
+ ,
+);
diff --git a/benchmark/react/lynx.config.js b/benchmark/react/lynx.config.js
index feccce3d66..80d0974327 100644
--- a/benchmark/react/lynx.config.js
+++ b/benchmark/react/lynx.config.js
@@ -32,6 +32,11 @@ export default defineConfig({
'./src/patchProfile.ts',
'./cases/002-hello-reactLynx/index.tsx',
],
+ '003-hello-list': [
+ './src/patchProfile.ts',
+ './src/patchUpdateListCallbacks.ts',
+ './cases/003-hello-list/index.tsx',
+ ],
},
},
plugins: [
diff --git a/benchmark/react/package.json b/benchmark/react/package.json
index d558bf1d0c..99ca743eeb 100644
--- a/benchmark/react/package.json
+++ b/benchmark/react/package.json
@@ -7,11 +7,14 @@
"bench": "pnpm run --sequential --stream --aggregate-output '/^bench:.*/'",
"bench:001-fib": "benchx_cli run dist/001-fib.lynx.bundle",
"bench:002-hello-reactLynx": "benchx_cli run dist/002-hello-reactLynx.lynx.bundle --wait-for-id=stop-benchmark-true",
+ "bench:003-hello-list": "benchx_cli run dist/003-hello-list.lynx.bundle --wait-for-id=stop-benchmark-true",
"build": "rspeedy build",
"dev": "rspeedy dev",
"perfetto": "pnpm run --sequential --stream --aggregate-output '/^perfetto:.*/'",
"perfetto:001-fib": "benchx_cli -o dist/001-fib.ptrace run dist/001-fib.lynx.bundle",
- "perfetto:002-hello-reactLynx": "benchx_cli -o dist/002-hello-reactLynx.ptrace run dist/002-hello-reactLynx.lynx.bundle --wait-for-id=stop-benchmark-true"
+ "perfetto:002-hello-reactLynx": "benchx_cli -o dist/002-hello-reactLynx.ptrace run dist/002-hello-reactLynx.lynx.bundle --wait-for-id=stop-benchmark-true",
+ "perfetto:003-hello-list": "benchx_cli -o dist/003-hello-list.ptrace run dist/003-hello-list.lynx.bundle --wait-for-id=stop-benchmark-true",
+ "test": "echo 'No tests specified'"
},
"dependencies": {
"@lynx-js/react": "workspace:*",
@@ -25,6 +28,7 @@
"@lynx-js/qrcode-rsbuild-plugin": "workspace:*",
"@lynx-js/react-rsbuild-plugin": "workspace:*",
"@lynx-js/rspeedy": "workspace:*",
+ "@lynx-js/type-element-api": "0.0.2",
"@lynx-js/types": "3.4.11",
"@types/react": "^18.3.23"
}
diff --git a/benchmark/react/src/hook.ts b/benchmark/react/src/hook.ts
index 1a1a896b84..492c02de1c 100644
--- a/benchmark/react/src/hook.ts
+++ b/benchmark/react/src/hook.ts
@@ -14,3 +14,5 @@ export function hook(
return fn.call(this, oldFn, ...args);
} as T[K];
}
+
+export const PREFIX = __REPO_FILEPATH__.split('/').slice(0, -2).join('/');
diff --git a/benchmark/react/src/patchProfile.ts b/benchmark/react/src/patchProfile.ts
index 1544211679..63200d8a9c 100644
--- a/benchmark/react/src/patchProfile.ts
+++ b/benchmark/react/src/patchProfile.ts
@@ -2,9 +2,8 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
-import { hook } from './hook.js';
+import { PREFIX, hook } from './hook.js';
-const PREFIX = __REPO_FILEPATH__.split('/').slice(0, -2).join('/');
const ignored: Record = {};
const stack: string[] = [];
let depth = 0;
diff --git a/benchmark/react/src/patchUpdateListCallbacks.ts b/benchmark/react/src/patchUpdateListCallbacks.ts
new file mode 100644
index 0000000000..69742b390a
--- /dev/null
+++ b/benchmark/react/src/patchUpdateListCallbacks.ts
@@ -0,0 +1,80 @@
+// Copyright 2025 The Lynx Authors. All rights reserved.
+// Licensed under the Apache License Version 2.0 that can be found in the
+// LICENSE file in the root directory of this source tree.
+
+import '@lynx-js/react';
+import type { ComponentAtIndexCallback } from '@lynx-js/type-element-api';
+
+import { PREFIX, hook } from './hook.js';
+
+if (typeof Codspeed !== 'undefined' && __MAIN_THREAD__) {
+ function withCodspeed(
+ componentAtIndex: ComponentAtIndexCallback,
+ ): ComponentAtIndexCallback {
+ return (
+ list,
+ listID,
+ cellIndex,
+ operationID,
+ enableReuseNotification,
+ ) => {
+ const ids = __GetChildren(list).map(e => __GetElementUniqueID(e));
+ Codspeed.startBenchmark();
+ const sign = componentAtIndex(
+ list,
+ listID,
+ cellIndex,
+ operationID,
+ enableReuseNotification,
+ )!;
+ Codspeed.stopBenchmark();
+ Codspeed.setExecutedBenchmark(
+ `${PREFIX}::${__webpack_chunkname__}-componentAtIndex__${
+ ids.includes(sign) ? 'reuse' : 'create'
+ } `,
+ );
+
+ return sign;
+ };
+ }
+
+ hook(
+ globalThis,
+ '__UpdateListCallbacks',
+ (
+ old,
+ list,
+ componentAtIndex,
+ enqueueComponent,
+ componentAtIndexes,
+ ) => {
+ old!(
+ list,
+ withCodspeed(componentAtIndex),
+ enqueueComponent,
+ componentAtIndexes,
+ );
+ },
+ );
+
+ hook(
+ globalThis,
+ '__CreateList',
+ (
+ old,
+ id,
+ componentAtIndex,
+ enqueueComponent,
+ info,
+ componentAtIndexes,
+ ) => {
+ return old!(
+ id,
+ withCodspeed(componentAtIndex),
+ enqueueComponent,
+ info,
+ componentAtIndexes,
+ );
+ },
+ );
+}
diff --git a/packages/lynx/benchx_cli/scripts/build.mjs b/packages/lynx/benchx_cli/scripts/build.mjs
index 55d9e0cc36..c2da49015e 100644
--- a/packages/lynx/benchx_cli/scripts/build.mjs
+++ b/packages/lynx/benchx_cli/scripts/build.mjs
@@ -30,7 +30,7 @@ console.log('noop')
}
const COMMIT = 'd6dd806293012c62e5104ad7ed2bed5c66f4f833';
-const PICK_COMMIT = '1524ac02060bfc2e354e1865c989bc6f7a9882b1';
+const PICK_COMMIT = '78493ae8581046275f6333ce16004666560ce058';
function checkCwd() {
try {
diff --git a/packages/lynx/benchx_cli/scripts/prepare.mjs b/packages/lynx/benchx_cli/scripts/prepare.mjs
index 69c721a322..cf7ef9d417 100644
--- a/packages/lynx/benchx_cli/scripts/prepare.mjs
+++ b/packages/lynx/benchx_cli/scripts/prepare.mjs
@@ -2,9 +2,16 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
-import { copyFileSync, mkdirSync, writeFileSync } from 'node:fs';
+import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
mkdirSync('./dist/bin', { recursive: true });
+
+if (existsSync('./dist/bin/benchx_cli')) {
+ // File already exists
+ // eslint-disable-next-line n/no-process-exit
+ process.exit(0);
+}
+
if (process.platform === 'win32') {
writeFileSync(
'./dist/bin/benchx_cli',
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e0c8b91611..f2a62cdc96 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -173,6 +173,9 @@ importers:
'@lynx-js/rspeedy':
specifier: workspace:*
version: link:../../packages/rspeedy/core
+ '@lynx-js/type-element-api':
+ specifier: 0.0.2
+ version: 0.0.2
'@lynx-js/types':
specifier: 3.4.11
version: 3.4.11
@@ -2234,6 +2237,9 @@ packages:
'@lynx-js/tasm@0.0.18':
resolution: {integrity: sha512-6Kl1eUxooceWcSLYnCtys38r9KPIouTam/fU81pzOrH/xIal60+WY47nj+MzDXq94i9jbUusM+IQSZAkeRistw==}
+ '@lynx-js/type-element-api@0.0.2':
+ resolution: {integrity: sha512-Unz3RHf2RM2vOWForwubjLYw9xbBP02ReuOL4KVLc3+0Vzryo8KbgqbhMUZCsJXHVmcaE2zXiE1AV/nqrBAoPA==}
+
'@lynx-js/types@3.4.11':
resolution: {integrity: sha512-k4mu4d2xmMqMIP1e7WGPdzO/k9x9W35b/OE/awalQUbcLlruFatagEOEM6TANK/VuRvEl0eLXQjhBnSwGkgt5w==}
@@ -9286,6 +9292,8 @@ snapshots:
'@lynx-js/tasm@0.0.18': {}
+ '@lynx-js/type-element-api@0.0.2': {}
+
'@lynx-js/types@3.4.11':
dependencies:
csstype: 3.1.3