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