From 7a245d808ca0e7807db4cb586143838fb3d12531 Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Wed, 24 Dec 2025 10:36:23 +0800 Subject: [PATCH 1/6] feat: upgrade preact --- .changeset/dry-results-decide.md | 3 ++ packages/react/package.json | 2 +- .../debug/hooks-invalid-args.test.jsx | 15 +++++-- .../__test__/debug/object-as-child.test.jsx | 2 +- .../__test__/lifecycle/updateData.test.jsx | 43 ++++++++++++++++--- .../react/runtime/src/backgroundSnapshot.ts | 6 +-- packages/react/runtime/src/lynx/suspense.ts | 2 - pnpm-lock.yaml | 26 +++++------ 8 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 .changeset/dry-results-decide.md diff --git a/.changeset/dry-results-decide.md b/.changeset/dry-results-decide.md new file mode 100644 index 0000000000..853d812bb3 --- /dev/null +++ b/.changeset/dry-results-decide.md @@ -0,0 +1,3 @@ +--- + +--- diff --git a/packages/react/package.json b/packages/react/package.json index cc386ca6c5..25443a89e2 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -174,7 +174,7 @@ "api-extractor": "api-extractor run --verbose" }, "dependencies": { - "preact": "npm:@hongzhiyuan/preact@10.24.0-00213bad" + "preact": "npm:@upupming/preact@10.28.0-4c6a8b49" }, "devDependencies": { "@lynx-js/types": "3.4.11", diff --git a/packages/react/runtime/__test__/debug/hooks-invalid-args.test.jsx b/packages/react/runtime/__test__/debug/hooks-invalid-args.test.jsx index 29a70518d9..5fc75ccf8b 100644 --- a/packages/react/runtime/__test__/debug/hooks-invalid-args.test.jsx +++ b/packages/react/runtime/__test__/debug/hooks-invalid-args.test.jsx @@ -3,6 +3,10 @@ import { expect, test, vi } from 'vitest'; test('preact/debug - Invalid argument passed to hook', async () => { vi.stubGlobal('__MAIN_THREAD__', false) .stubGlobal('__LEPUS__', false); + let warnLog = []; + vi.spyOn(console, 'warn').mockImplementation((...args) => { + warnLog.push(args); + }); await import('preact/debug'); const { root, useEffect, useState } = await import('../../src/index'); @@ -27,7 +31,12 @@ test('preact/debug - Invalid argument passed to hook', async () => { ); } - expect(() => root.render()).toThrowErrorMatchingInlineSnapshot( - `[Error: Invalid argument passed to hook. Hooks should not be called with NaN in the dependency array. Hook index 1 in component Bar was called with NaN.]`, - ); + root.render(); + expect(warnLog).toMatchInlineSnapshot(` + [ + [ + "Invalid argument passed to hook. Hooks should not be called with NaN in the dependency array. Hook index 1 in component Bar was called with NaN.", + ], + ] + `); }); diff --git a/packages/react/runtime/__test__/debug/object-as-child.test.jsx b/packages/react/runtime/__test__/debug/object-as-child.test.jsx index 5a5b1f8ddf..439edc45f2 100644 --- a/packages/react/runtime/__test__/debug/object-as-child.test.jsx +++ b/packages/react/runtime/__test__/debug/object-as-child.test.jsx @@ -25,7 +25,7 @@ test('preact/debug - Objects are not valid as a child', async () => { expect(() => root.render()).toThrowErrorMatchingInlineSnapshot( ` - [Error: Objects are not valid as a child. Encountered an object with the keys {foo,bar,baz,__,__b,__i,__u,__d}. + [Error: Objects are not valid as a child. Encountered an object with the keys {foo,bar,baz,__,__b,__i,__u}. in Bar in App diff --git a/packages/react/runtime/__test__/lifecycle/updateData.test.jsx b/packages/react/runtime/__test__/lifecycle/updateData.test.jsx index 6ec533412f..6e5f9cd022 100644 --- a/packages/react/runtime/__test__/lifecycle/updateData.test.jsx +++ b/packages/react/runtime/__test__/lifecycle/updateData.test.jsx @@ -471,7 +471,8 @@ describe('triggerDataUpdated', () => { lynxCoreInject.tt.updateCardData({ msg: 'update' }); await waitSchedule(); - expect(lynx.getNativeApp().callLepusMethod).toHaveBeenCalledTimes(3); + // duplicated because of https://github.com/preactjs/preact/pull/4724 + expect(lynx.getNativeApp().callLepusMethod).toHaveBeenCalledTimes(3 * 2); expect(lynx.getNativeApp().callLepusMethod.mock.calls).toMatchInlineSnapshot( ` [ @@ -491,7 +492,7 @@ describe('triggerDataUpdated', () => { [ "rLynxChange", { - "data": "{"patchList":[{"id":12,"snapshotPatch":[3,-6,0,"update"]}]}", + "data": "{"patchList":[{"id":12}]}", "patchOptions": { "flowIds": [ 666, @@ -504,7 +505,7 @@ describe('triggerDataUpdated', () => { [ "rLynxChange", { - "data": "{"patchList":[{"id":13,"snapshotPatch":[3,-7,0,"update"]}]}", + "data": "{"patchList":[{"id":13}]}", "patchOptions": { "flowIds": [ 666, @@ -514,6 +515,36 @@ describe('triggerDataUpdated', () => { }, [Function], ], + [ + "rLynxChange", + { + "data": "{"patchList":[{"id":14}]}", + "patchOptions": { + "reloadVersion": 0, + }, + }, + [Function], + ], + [ + "rLynxChange", + { + "data": "{"patchList":[{"id":15,"snapshotPatch":[3,-6,0,"update"]}]}", + "patchOptions": { + "reloadVersion": 0, + }, + }, + [Function], + ], + [ + "rLynxChange", + { + "data": "{"patchList":[{"id":16,"snapshotPatch":[3,-7,0,"update"]}]}", + "patchOptions": { + "reloadVersion": 0, + }, + }, + [Function], + ], ] `, ); @@ -648,7 +679,7 @@ describe('triggerDataUpdated', () => { [ "rLynxChange", { - "data": "{"patchList":[{"id":16,"snapshotPatch":[3,-3,0,"update"]}],"flushOptions":{"triggerDataUpdated":true}}", + "data": "{"patchList":[{"id":19,"snapshotPatch":[3,-3,0,"update"]}],"flushOptions":{"triggerDataUpdated":true}}", "patchOptions": { "flowIds": [ 666, @@ -958,7 +989,7 @@ describe('flush pending `renderComponent` before hydrate', () => { globalThis[rLynxChange[0]](rLynxChange[1]); expect(rLynxChange[1]).toMatchInlineSnapshot(` { - "data": "{"patchList":[{"snapshotPatch":[],"id":24}]}", + "data": "{"patchList":[{"snapshotPatch":[],"id":27}]}", "patchOptions": { "isHydration": true, "pipelineOptions": { @@ -1059,7 +1090,7 @@ describe('flush pending `renderComponent` before hydrate', () => { globalThis[rLynxChange[0]](rLynxChange[1]); expect(rLynxChange[1]).toMatchInlineSnapshot(` { - "data": "{"patchList":[{"snapshotPatch":[3,-3,0,"true"],"id":26}]}", + "data": "{"patchList":[{"snapshotPatch":[3,-3,0,"true"],"id":29}]}", "patchOptions": { "isHydration": true, "pipelineOptions": { diff --git a/packages/react/runtime/src/backgroundSnapshot.ts b/packages/react/runtime/src/backgroundSnapshot.ts index ca62106ba5..ebf606b18a 100644 --- a/packages/react/runtime/src/backgroundSnapshot.ts +++ b/packages/react/runtime/src/backgroundSnapshot.ts @@ -79,9 +79,9 @@ export class BackgroundSnapshotInstance { // return !!this.__parent; // } - contains(child: BackgroundSnapshotInstance): boolean { - return child.parentNode === this; - } + // contains(child: BackgroundSnapshotInstance): boolean { + // return child.parentNode === this; + // } // This will be called in `lazy`/`Suspense`. appendChild(child: BackgroundSnapshotInstance): void { diff --git a/packages/react/runtime/src/lynx/suspense.ts b/packages/react/runtime/src/lynx/suspense.ts index a1683a4418..2414c8ef9a 100644 --- a/packages/react/runtime/src/lynx/suspense.ts +++ b/packages/react/runtime/src/lynx/suspense.ts @@ -18,7 +18,6 @@ export const Suspense: FunctionComponent<{ children: VNode | VNode[]; fallback: (__MAIN_THREAD__ ? createElementMainThread : createElementBackground) as typeof createElementBackground; const childrenRef = useRef(); - // @ts-expect-error wrapper is a valid element type const newChildren = __createElement('wrapper', { ref: (bsi: BackgroundSnapshotInstance) => { if (bsi) { @@ -27,7 +26,6 @@ export const Suspense: FunctionComponent<{ children: VNode | VNode[]; fallback: }, }, children); - // @ts-expect-error wrapper is a valid element type const newFallback = __createElement('wrapper', { ref: (bsi: BackgroundSnapshotInstance) => { if (bsi && childrenRef.current) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01e9ad3237..bb98d3d4b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -429,8 +429,8 @@ importers: packages/react: dependencies: preact: - specifier: npm:@hongzhiyuan/preact@10.24.0-00213bad - version: '@hongzhiyuan/preact@10.24.0-00213bad' + specifier: npm:@upupming/preact@10.28.0-4c6a8b49 + version: '@upupming/preact@10.28.0-4c6a8b49' devDependencies: '@lynx-js/types': specifier: 3.4.11 @@ -449,7 +449,7 @@ importers: version: link:.. '@prefresh/core': specifier: ^1.5.9 - version: 1.5.9(preact@10.23.2) + version: 1.5.9(preact@10.28.0) '@prefresh/utils': specifier: ^1.2.1 version: 1.2.1 @@ -2699,9 +2699,6 @@ packages: resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@hongzhiyuan/preact@10.24.0-00213bad': - resolution: {integrity: sha512-bHWp4ZDK5ZimcY+bTWw3S3xGiB8eROpZj0RK3FClNIaTOajb0b11CsT3K+pdeakgPgq1jWN3T2e2rfrPm40JsQ==} - '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -4283,6 +4280,9 @@ packages: cpu: [x64] os: [win32] + '@upupming/preact@10.28.0-4c6a8b49': + resolution: {integrity: sha512-QLsh7fN+ucr6nMgBs8EcEb77eztWIzfdqPZNa7dFfYxmpioQaGBLPEKt6JyvSr1hp0Cx8lCT/V20GTyel7Qlog==} + '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: @@ -7768,8 +7768,8 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - preact@10.23.2: - resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==} + preact@10.28.0: + resolution: {integrity: sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==} prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -10924,8 +10924,6 @@ snapshots: '@eslint/core': 0.15.2 levn: 0.4.1 - '@hongzhiyuan/preact@10.24.0-00213bad': {} - '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -11388,9 +11386,9 @@ snapshots: '@polka/url@1.0.0-next.25': {} - '@prefresh/core@1.5.9(preact@10.23.2)': + '@prefresh/core@1.5.9(preact@10.28.0)': dependencies: - preact: 10.23.2 + preact: 10.28.0 '@prefresh/utils@1.2.1': {} @@ -12966,6 +12964,8 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@upupming/preact@10.28.0-4c6a8b49': {} + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 @@ -17117,7 +17117,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preact@10.23.2: {} + preact@10.28.0: {} prelude-ls@1.2.1: {} From c740d1c81054065e2e22ba194742f036277365d5 Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Thu, 25 Dec 2025 21:36:55 +0800 Subject: [PATCH 2/6] feat: add test about re-mounts --- .../src/__tests__/render.test.jsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/react/testing-library/src/__tests__/render.test.jsx b/packages/react/testing-library/src/__tests__/render.test.jsx index 7ae9ac935d..94acdb4552 100644 --- a/packages/react/testing-library/src/__tests__/render.test.jsx +++ b/packages/react/testing-library/src/__tests__/render.test.jsx @@ -2,6 +2,7 @@ import '@testing-library/jest-dom'; import { test, expect } from 'vitest'; import { render } from '..'; import { createRef } from '@lynx-js/react'; +import { Component } from 'preact'; test('renders view into page', async () => { const ref = createRef(); @@ -259,3 +260,37 @@ describe('dynamic key in snapshot', () => { `); }); }); + +it('should handle keyed replacements', () => { + const actions = []; + class Comp extends Component { + componentDidMount() { + actions.push('mounted ' + this.props.i); + } + render() { + return Hello; + } + } + + const App = props => { + return ( + + + {false} + + + + ); + }; + + render(); + expect(actions).to.deep.equal(['mounted 1', 'mounted 2', 'mounted 3']); + + render(); + expect(actions).to.deep.equal([ + 'mounted 1', + 'mounted 2', + 'mounted 3', + 'mounted 1', + ]); +}); From 9f4e8060d545f891ff95b2867120b70617babfaa Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Thu, 8 Jan 2026 20:03:39 +0800 Subject: [PATCH 3/6] feat: use @hongzhiyuan/preact@10.28.0-fc4af453 --- .changeset/dry-results-decide.md | 4 +++- packages/react/package.json | 2 +- pnpm-lock.yaml | 15 ++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.changeset/dry-results-decide.md b/.changeset/dry-results-decide.md index 853d812bb3..1b273f2eb2 100644 --- a/.changeset/dry-results-decide.md +++ b/.changeset/dry-results-decide.md @@ -1,3 +1,5 @@ --- - +"@lynx-js/react": patch --- + +Bump Preact to 10.28.x. diff --git a/packages/react/package.json b/packages/react/package.json index dbf6a54bc5..ef4340698c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -174,7 +174,7 @@ "api-extractor": "api-extractor run --verbose" }, "dependencies": { - "preact": "npm:@upupming/preact@10.28.0-4c6a8b49" + "preact": "npm:@hongzhiyuan/preact@10.28.0-fc4af453" }, "devDependencies": { "@lynx-js/types": "3.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f0f041ad4..960496234f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -429,8 +429,8 @@ importers: packages/react: dependencies: preact: - specifier: npm:@upupming/preact@10.28.0-4c6a8b49 - version: '@upupming/preact@10.28.0-4c6a8b49' + specifier: npm:@hongzhiyuan/preact@10.28.0-fc4af453 + version: '@hongzhiyuan/preact@10.28.0-fc4af453' devDependencies: '@lynx-js/types': specifier: 3.6.0 @@ -2821,6 +2821,9 @@ packages: '@exodus/crypto': optional: true + '@hongzhiyuan/preact@10.28.0-fc4af453': + resolution: {integrity: sha512-TrM2g079OZN1poroDnU2kQd1gNUB1DMfVoKyz23AE3hZvl9ypRgG2MdgxAJtwT5u3BmYvI4Z0EbdpJTdf428IQ==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -4421,9 +4424,6 @@ packages: cpu: [x64] os: [win32] - '@upupming/preact@10.28.0-4c6a8b49': - resolution: {integrity: sha512-QLsh7fN+ucr6nMgBs8EcEb77eztWIzfdqPZNa7dFfYxmpioQaGBLPEKt6JyvSr1hp0Cx8lCT/V20GTyel7Qlog==} - '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: @@ -8026,6 +8026,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.13.0: @@ -11191,6 +11192,8 @@ snapshots: '@exodus/bytes@1.6.0': {} + '@hongzhiyuan/preact@10.28.0-fc4af453': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -13251,8 +13254,6 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@upupming/preact@10.28.0-4c6a8b49': {} - '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 From 8ba0d1d8753be1c936a26f9032418fa87013e2bb Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Fri, 9 Jan 2026 17:08:37 +0800 Subject: [PATCH 4/6] fix: updateGlobalProps rerender issue --- .../lifecycle/updateGlobalProps.test.jsx | 107 ++++++++++++++++++ .../react/runtime/src/lynx/runWithForce.ts | 16 ++- .../runtime/src/renderToOpcodes/constants.ts | 1 + .../react/runtime/types/internal-preact.d.ts | 2 + 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx diff --git a/packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx b/packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx new file mode 100644 index 0000000000..a465e14c9f --- /dev/null +++ b/packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx @@ -0,0 +1,107 @@ +// Copyright 2026 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 { beforeEach } from 'vitest'; +import { __root } from '../../src/root'; +import { globalEnvManager } from '../utils/envManager'; +import { describe } from 'vitest'; +import { it } from 'vitest'; +import { expect } from 'vitest'; +import { render } from 'preact'; +import { waitSchedule } from '../utils/nativeMethod'; +import { beforeAll } from 'vitest'; +import { replaceCommitHook } from '../../src/lifecycle/patch/commit'; + +beforeAll(() => { + replaceCommitHook(); +}); + +beforeEach(() => { + globalEnvManager.resetEnv(); +}); + +describe('updateGlobalProps', () => { + it('should update global props', async () => { + lynx.__globalProps = { theme: 'dark' }; + const Comp = () => { + return {lynx.__globalProps.theme}; + }; + + // main thread render + { + __root.__jsx = ; + renderPage(); + expect(__root.__element_root).toMatchInlineSnapshot(` + + + + + + `); + } + + // background render + { + globalEnvManager.switchToBackground(); + __root.__jsx = ; + render(, __root); + } + + // hydrate + { + // LifecycleConstant.firstScreen + lynxCoreInject.tt.OnLifecycleEvent(...globalThis.__OnLifecycleEvent.mock.calls[0]); + } + + // rLynxChange + { + globalEnvManager.switchToMainThread(); + globalThis.__OnLifecycleEvent.mockClear(); + const rLynxChange = lynx.getNativeApp().callLepusMethod.mock.calls[0]; + globalThis[rLynxChange[0]](rLynxChange[1]); + expect(globalThis.__OnLifecycleEvent).not.toBeCalled(); + await waitSchedule(); + expect(__root.__element_root).toMatchInlineSnapshot(` + + + + + + `); + } + + // updateGlobalProps + { + globalEnvManager.switchToBackground(); + lynx.getNativeApp().callLepusMethod.mockClear(); + lynxCoreInject.tt.updateGlobalProps({ theme: 'light' }); + await waitSchedule(); + console.log('rLynxChange', lynx.getNativeApp().callLepusMethod.mock.calls[0]); + + globalEnvManager.switchToMainThread(); + const rLynxChange = lynx.getNativeApp().callLepusMethod.mock.calls[0]; + globalThis[rLynxChange[0]](rLynxChange[1]); + + expect(__root.__element_root).toMatchInlineSnapshot(` + + + + + + `); + } + }); +}); diff --git a/packages/react/runtime/src/lynx/runWithForce.ts b/packages/react/runtime/src/lynx/runWithForce.ts index f26016ec53..efa79dc970 100644 --- a/packages/react/runtime/src/lynx/runWithForce.ts +++ b/packages/react/runtime/src/lynx/runWithForce.ts @@ -4,9 +4,23 @@ import { options } from 'preact'; import type { VNode } from 'preact'; -import { COMPONENT, DIFF2, FORCE } from '../renderToOpcodes/constants.js'; +import { COMPONENT, DIFF2, FORCE, ORIGINAL } from '../renderToOpcodes/constants.js'; +import { __root } from '../root.js'; export function runWithForce(cb: () => void): void { + // In https://github.com/preactjs/preact/pull/4724, preact will + // skip render if the `vnode._original` is not changed, even if `c._force` is true + // So we need to increment `vnode._original` to make sure the `__root.__jsx` is re-rendered + // This is the same logic with: https://github.com/preactjs/preact/blob/43178581442fa0f2428e5bdbca355860b2d12e5d/src/component.js#L131 + if (__root.__jsx) { + const newVNode = Object.assign({}, __root.__jsx) as unknown as VNode; + if (newVNode[ORIGINAL] != null) { + newVNode[ORIGINAL] += 1; + // @ts-expect-error: __root.__jsx is a VNode + __root.__jsx = newVNode; + } + } + const oldDiff = options[DIFF2]; options[DIFF2] = (vnode: VNode, oldVNode: VNode) => { /* v8 ignore start */ diff --git a/packages/react/runtime/src/renderToOpcodes/constants.ts b/packages/react/runtime/src/renderToOpcodes/constants.ts index a06e824914..1c22748bd2 100644 --- a/packages/react/runtime/src/renderToOpcodes/constants.ts +++ b/packages/react/runtime/src/renderToOpcodes/constants.ts @@ -17,6 +17,7 @@ export const CHILDREN = '__k'; export const PARENT = '__'; export const MASK = '__m'; export const DOM = '__e'; +export const ORIGINAL = '__v'; // Component properties export const VNODE = '__v'; diff --git a/packages/react/runtime/types/internal-preact.d.ts b/packages/react/runtime/types/internal-preact.d.ts index 44296125b6..f1efa67045 100644 --- a/packages/react/runtime/types/internal-preact.d.ts +++ b/packages/react/runtime/types/internal-preact.d.ts @@ -25,6 +25,8 @@ declare module 'preact' { interface VNode { /** _component */ __c?: Component | null; + /** _original */ + __v?: number; } interface Component

{ From 00cd22c5db0e6b04df57d7259f03d77c23a23236 Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Fri, 9 Jan 2026 21:34:15 +0800 Subject: [PATCH 5/6] Update packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Yiming Li --- .../react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx b/packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx index a465e14c9f..e5c5eda9c3 100644 --- a/packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx +++ b/packages/react/runtime/__test__/lifecycle/updateGlobalProps.test.jsx @@ -85,7 +85,6 @@ describe('updateGlobalProps', () => { lynx.getNativeApp().callLepusMethod.mockClear(); lynxCoreInject.tt.updateGlobalProps({ theme: 'light' }); await waitSchedule(); - console.log('rLynxChange', lynx.getNativeApp().callLepusMethod.mock.calls[0]); globalEnvManager.switchToMainThread(); const rLynxChange = lynx.getNativeApp().callLepusMethod.mock.calls[0]; From 0263b2368057945b9f25a12c18b9ccdc707bc619 Mon Sep 17 00:00:00 2001 From: Yiming Li Date: Wed, 14 Jan 2026 16:26:18 +0800 Subject: [PATCH 6/6] chore: update changeset --- .changeset/dry-results-decide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/dry-results-decide.md b/.changeset/dry-results-decide.md index 1b273f2eb2..8b792f593f 100644 --- a/.changeset/dry-results-decide.md +++ b/.changeset/dry-results-decide.md @@ -1,5 +1,5 @@ --- -"@lynx-js/react": patch +"@lynx-js/react": minor --- -Bump Preact to 10.28.x. +**BREAKING CHANGE**: Bump Preact from [10.24.0](https://github.com/preactjs/preact/commit/1807173df5e18b6b1a3cd667ee2a31af37f4282b) to [10.28.0](https://github.com/preactjs/preact/commit/f7693b72ecb4a40c66e6e47f54e2d4edc374c9f0), see diffs at [hzy/preact#6](https://github.com/hzy/preact/pull/6).