diff --git a/.changeset/clear-cobras-work.md b/.changeset/clear-cobras-work.md new file mode 100644 index 0000000000..bfcf1f119a --- /dev/null +++ b/.changeset/clear-cobras-work.md @@ -0,0 +1,22 @@ +--- +"@lynx-js/web-mainthread-apis": patch +"@lynx-js/web-core": patch +--- + +fix: some inline style properties cause crash + +add support for the following css properties + +- mask +- mask-repeat +- mask-position +- mask-clip +- mask-origin +- mask-size +- gap +- column-gap +- row-gap +- image-rendering +- hyphens +- offset-path +- offset-distance diff --git a/.changeset/easy-regions-say.md b/.changeset/easy-regions-say.md new file mode 100644 index 0000000000..0d5e0fca74 --- /dev/null +++ b/.changeset/easy-regions-say.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/web-elements": patch +--- + +fix(web): x-swiper-item threshold updated to 20 diff --git a/.changeset/five-frogs-report.md b/.changeset/five-frogs-report.md new file mode 100644 index 0000000000..8028b00c3f --- /dev/null +++ b/.changeset/five-frogs-report.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/web-elements": patch +--- + +fix: In React19, setter and getter functions are treated as properties, making it impossible to retrieve the current value via attributes. diff --git a/.changeset/modern-peaches-marry.md b/.changeset/modern-peaches-marry.md new file mode 100644 index 0000000000..1994b45796 --- /dev/null +++ b/.changeset/modern-peaches-marry.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/web-platform-rsbuild-plugin": minor +--- + +feat: Provides Rsbuild plugin for Web projects in Lynx Web Platform, currently supports polyfill about lynx. diff --git a/.changeset/silly-rocks-guess.md b/.changeset/silly-rocks-guess.md new file mode 100644 index 0000000000..d1845455b0 --- /dev/null +++ b/.changeset/silly-rocks-guess.md @@ -0,0 +1,7 @@ +--- +"@lynx-js/react": minor +--- + +Reverts #239: "batch multiple patches for main thread communication" + +This reverts the change that batched updates sent to the main thread in a single render pass. diff --git a/.changeset/strong-rockets-itch.md b/.changeset/strong-rockets-itch.md new file mode 100644 index 0000000000..7d675e7aba --- /dev/null +++ b/.changeset/strong-rockets-itch.md @@ -0,0 +1,6 @@ +--- +"@lynx-js/react-rsbuild-plugin": patch +"@lynx-js/react-webpack-plugin": patch +--- + +Support `@lynx-js/react` v0.108.0. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78f7a0f6b5..e3dc9d2c12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -236,6 +236,27 @@ jobs: export ALL_ON_UI=true pnpm --filter @lynx-js/web-tests run test --reporter='github,dot,junit,html' pnpm --filter @lynx-js/web-tests run coverage:ci + test-web-platform: + needs: build + uses: ./.github/workflows/workflow-test.yml + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + runs-on: lynx-ubuntu-24.04-medium + run: > + pnpm + --filter @lynx-js/web-platform-rsbuild-plugin + run test + --reporter=github-actions + --reporter=dot + --reporter=junit + --outputFile=test-report.junit.xml + --coverage.reporter='json' + --coverage.reporter='text' + --test-timeout=50000 + --no-cache + --logHeapUsage + --silent lighthouse: needs: build uses: ./.github/workflows/workflow-test.yml @@ -286,6 +307,7 @@ jobs: - code-style-check - playwright-linux - playwright-linux-all-on-ui + - test-web-platform - test-plugins - test-publish - test-react diff --git a/.gitignore b/.gitignore index 6b2ffb578a..4056f6442c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ target/ .pnpm-store # lighthouse-ci -.lighthouseci \ No newline at end of file +.lighthouseci + +# rslib +.rslib diff --git a/package.json b/package.json index 5b586f4dd3..e0a619088b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "typescript-eslint": "^8.31.0", "vitest": "^3.1.2" }, - "packageManager": "pnpm@10.9.0", + "packageManager": "pnpm@10.10.0", "engines": { "node": ">=22" } diff --git a/packages/react/runtime/__test__/lifecycle.test.jsx b/packages/react/runtime/__test__/lifecycle.test.jsx index 95087ac645..bfb64ad1e5 100644 --- a/packages/react/runtime/__test__/lifecycle.test.jsx +++ b/packages/react/runtime/__test__/lifecycle.test.jsx @@ -1,20 +1,18 @@ -import { Component, options } from 'preact'; +import { Component, options, render } from 'preact'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { useEffect, useLayoutEffect, useState } from '../src/index'; import { globalEnvManager } from './utils/envManager'; +import { waitSchedule } from './utils/nativeMethod'; import { initDelayUnmount } from '../src/lifecycle/delayUnmount'; import { globalCommitTaskMap, replaceCommitHook, replaceRequestAnimationFrame } from '../src/lifecycle/patch/commit'; import { deinitGlobalSnapshotPatch, initGlobalSnapshotPatch } from '../src/lifecycle/patch/snapshotPatch'; -import { renderBackground as render } from '../src/lifecycle/render'; import { LifecycleConstant } from '../src/lifecycleConstant'; import { CATCH_ERROR } from '../src/renderToOpcodes/constants'; import { __root } from '../src/root'; import { backgroundSnapshotInstanceManager, setupPage } from '../src/snapshot'; -import { elementTree, waitSchedule } from './utils/nativeMethod'; beforeAll(() => { - options.debounceRendering = Promise.prototype.then.bind(Promise.resolve()); setupPage(__CreatePage('0', 0)); replaceCommitHook(); initDelayUnmount(); @@ -30,7 +28,6 @@ afterEach(() => { globalEnvManager.resetEnv(); deinitGlobalSnapshotPatch(); vi.restoreAllMocks(); - elementTree.clear(); }); describe('useEffect', () => { @@ -804,118 +801,4 @@ describe('useState', () => { ); } }); - - it('should batch multiple updates', async function() { - let _setCount; - let _setCount2; - - const Child1 = () => { - const [count, setCount] = useState(0); - _setCount = setCount; - return ( - - - - ); - }; - - const Child2 = () => { - const [count, setCount] = useState(0); - _setCount2 = setCount; - return ( - - - - ); - }; - - const Comp = () => { - return ( - - - - - ); - }; - - // main thread render - { - __root.__jsx = ; - renderPage(); - } - - // background render - { - globalEnvManager.switchToBackground(); - 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(); - } - - // insert node - { - globalEnvManager.switchToBackground(); - lynx.getNativeApp().callLepusMethod.mockClear(); - _setCount(1); - _setCount2(2); - await waitSchedule(); - - expect(lynx.getNativeApp().callLepusMethod).toHaveBeenCalledTimes(1); - expect(lynx.getNativeApp().callLepusMethod.mock.calls).toMatchInlineSnapshot( - ` - [ - [ - "rLynxChange", - { - "data": "{"patchList":[{"id":42,"snapshotPatch":[3,-3,0,1]},{"id":43,"snapshotPatch":[3,-4,0,2]}]}", - "patchOptions": { - "reloadVersion": 0, - }, - }, - [Function], - ], - ] - `, - ); - } - - { - globalEnvManager.switchToMainThread(); - globalThis.__OnLifecycleEvent.mockClear(); - const rLynxChange = lynx.getNativeApp().callLepusMethod.mock.calls[0]; - globalThis[rLynxChange[0]](rLynxChange[1]); - rLynxChange[2]; - lynx.getNativeApp().callLepusMethod.mockClear(); - await waitSchedule(); - expect(__root.__element_root).toMatchInlineSnapshot(` - - - - - - - - - - - `); - } - }); }); diff --git a/packages/react/runtime/__test__/lynx/timing.test.jsx b/packages/react/runtime/__test__/lynx/timing.test.jsx index 04601e217a..d4a575c0b7 100644 --- a/packages/react/runtime/__test__/lynx/timing.test.jsx +++ b/packages/react/runtime/__test__/lynx/timing.test.jsx @@ -3,12 +3,11 @@ // 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 { Component, options } from 'preact'; +import { Component, options, render } from 'preact'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { replaceCommitHook } from '../../src/lifecycle/patch/commit'; import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread'; -import { renderBackground as render } from '../../src/lifecycle/render'; import '../../src/lynx/component'; import { initTimingAPI } from '../../src/lynx/performance'; import { __root } from '../../src/root'; diff --git a/packages/react/runtime/__test__/snapshot/gesture.test.jsx b/packages/react/runtime/__test__/snapshot/gesture.test.jsx index a90dee9e38..9de53ab92e 100644 --- a/packages/react/runtime/__test__/snapshot/gesture.test.jsx +++ b/packages/react/runtime/__test__/snapshot/gesture.test.jsx @@ -1,8 +1,8 @@ +import { render } from 'preact'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { replaceCommitHook } from '../../src/lifecycle/patch/commit'; import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread'; -import { renderBackground as render } from '../../src/lifecycle/render'; import { __root } from '../../src/root'; import { setupPage } from '../../src/snapshot'; import { globalEnvManager } from '../utils/envManager'; diff --git a/packages/react/runtime/__test__/snapshot/ref.test.jsx b/packages/react/runtime/__test__/snapshot/ref.test.jsx index c0421f3848..66e1251f8c 100644 --- a/packages/react/runtime/__test__/snapshot/ref.test.jsx +++ b/packages/react/runtime/__test__/snapshot/ref.test.jsx @@ -3,13 +3,13 @@ // 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 { render } from 'preact'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { Component, createRef, root, useState } from '../../src/index'; import { delayedLifecycleEvents } from '../../src/lifecycle/event/delayLifecycleEvents'; import { clearCommitTaskId, replaceCommitHook } from '../../src/lifecycle/patch/commit'; import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread'; -import { renderBackground as render } from '../../src/lifecycle/render'; import { __pendingListUpdates } from '../../src/list'; import { __root } from '../../src/root'; import { setupPage } from '../../src/snapshot'; diff --git a/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx b/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx index fb2202bbab..54712065ff 100644 --- a/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx +++ b/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx @@ -3,12 +3,12 @@ // 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 { render } from 'preact'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { Component, useState } from '../../src/index'; import { replaceCommitHook } from '../../src/lifecycle/patch/commit'; import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread'; -import { renderBackground as render } from '../../src/lifecycle/render'; import { __root } from '../../src/root'; import { setupPage } from '../../src/snapshot'; import { globalEnvManager } from '../utils/envManager'; diff --git a/packages/react/runtime/__test__/snapshot/workletRef.test.jsx b/packages/react/runtime/__test__/snapshot/workletRef.test.jsx index b7655c49a6..a4e78f0f2a 100644 --- a/packages/react/runtime/__test__/snapshot/workletRef.test.jsx +++ b/packages/react/runtime/__test__/snapshot/workletRef.test.jsx @@ -3,13 +3,13 @@ // 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 { render } from 'preact'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { createCompBG1, createCompBGList, createCompBGSpread } from './workletRefBG'; import { createCompMT1, createCompMTList, createCompMTSpread } from './workletRefMT'; import { replaceCommitHook } from '../../src/lifecycle/patch/commit'; import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread'; -import { renderBackground as render } from '../../src/lifecycle/render'; import { __root } from '../../src/root'; import { setupPage } from '../../src/snapshot'; import { globalEnvManager } from '../utils/envManager'; diff --git a/packages/react/runtime/__test__/worklet/workletRef.test.jsx b/packages/react/runtime/__test__/worklet/workletRef.test.jsx index 9eb2079f3e..97b6fcc7d7 100644 --- a/packages/react/runtime/__test__/worklet/workletRef.test.jsx +++ b/packages/react/runtime/__test__/worklet/workletRef.test.jsx @@ -3,11 +3,11 @@ // 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 { render } from 'preact'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { replaceCommitHook } from '../../src/lifecycle/patch/commit'; import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread'; -import { renderBackground as render } from '../../src/lifecycle/render'; import { __root } from '../../src/root'; import { setupPage } from '../../src/snapshot'; import { destroyWorklet } from '../../src/worklet/destroy'; diff --git a/packages/react/runtime/src/lifecycle/destroy.ts b/packages/react/runtime/src/lifecycle/destroy.ts index bdbf4fa6c9..c37368685e 100644 --- a/packages/react/runtime/src/lifecycle/destroy.ts +++ b/packages/react/runtime/src/lifecycle/destroy.ts @@ -1,18 +1,19 @@ // Copyright 2024 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 { render } from 'preact'; + import { __root } from '../root.js'; import { delayedEvents } from './event/delayEvents.js'; import { delayedLifecycleEvents } from './event/delayLifecycleEvents.js'; import { globalCommitTaskMap } from './patch/commit.js'; -import { renderBackground } from './render.js'; function destroyBackground(): void { if (__PROFILE__) { console.profile('destroyBackground'); } - renderBackground(null, __root as any); + render(null, __root as any); globalCommitTaskMap.forEach(task => { task(); diff --git a/packages/react/runtime/src/lifecycle/patch/commit.ts b/packages/react/runtime/src/lifecycle/patch/commit.ts index 296bd00ae0..2a50f4a768 100644 --- a/packages/react/runtime/src/lifecycle/patch/commit.ts +++ b/packages/react/runtime/src/lifecycle/patch/commit.ts @@ -30,11 +30,6 @@ let nextCommitTaskId = 1; let globalBackgroundSnapshotInstancesToRemove: number[] = []; -let patchesToCommit: Patch[] = []; -function clearPatchesToCommit(): void { - patchesToCommit = []; -} - interface Patch { id: number; snapshotPatch?: SnapshotPatch; @@ -53,28 +48,6 @@ interface PatchOptions { } function replaceCommitHook(): void { - // use our own `options.debounceRendering` to insert a timing flag before render - type DebounceRendering = (f: () => void) => void; - const injectDebounceRendering = (debounceRendering: DebounceRendering): DebounceRendering => { - return (f: () => void) => { - debounceRendering(() => { - f(); - void commitToMainThread(); - }); - }; - }; - const defaultDebounceRendering = options.debounceRendering?.bind(options) - ?? (Promise.prototype.then.bind(Promise.resolve()) as DebounceRendering); - let _debounceRendering = injectDebounceRendering(defaultDebounceRendering); - Object.defineProperty(options, 'debounceRendering', { - get() { - return _debounceRendering; - }, - set(debounceRendering: DebounceRendering) { - _debounceRendering = injectDebounceRendering(debounceRendering); - }, - }); - const oldCommit = options[COMMIT]; const commit = async (vnode: VNode, commitQueue: any[]) => { if (__LEPUS__) { @@ -82,6 +55,9 @@ function replaceCommitHook(): void { commitQueue.length = 0; return; } + + markTimingLegacy(PerformanceTimingKeys.updateDiffVdomEnd); + markTiming(PerformanceTimingKeys.diffVdomEnd); const renderCallbacks = commitQueue.map((component: Component) => { const ret = { component, @@ -121,7 +97,9 @@ function replaceCommitHook(): void { }); const snapshotPatch = takeGlobalSnapshotPatch(); + const flushOptions = globalFlushOptions; const workletRefInitValuePatch = takeWorkletRefInitValuePatch(); + globalFlushOptions = {}; if (!snapshotPatch && workletRefInitValuePatch.length === 0) { // before hydration, skip patch return; @@ -137,44 +115,23 @@ function replaceCommitHook(): void { if (workletRefInitValuePatch.length) { patch.workletRefInitValuePatch = workletRefInitValuePatch; } + const patchList: PatchList = { + patchList: [patch], + }; + if (!isEmptyObject(flushOptions)) { + patchList.flushOptions = flushOptions; + } + const obj = commitPatchUpdate(patchList, {}); - patchesToCommit.push(patch); - }; - options[COMMIT] = commit as ((...args: Parameters) => void); -} - -async function commitToMainThread(): Promise { - if (patchesToCommit.length === 0) { - return; - } - - markTimingLegacy(PerformanceTimingKeys.updateDiffVdomEnd); - markTiming(PerformanceTimingKeys.diffVdomEnd); - - const flushOptions = globalFlushOptions; - globalFlushOptions = {}; - - const patchList: PatchList = { - patchList: patchesToCommit, - }; - patchesToCommit = []; - - if (!isEmptyObject(flushOptions)) { - patchList.flushOptions = flushOptions; - } - - const obj = commitPatchUpdate(patchList, {}); - - lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => { - for (let i = 0; i < patchList.patchList.length; i++) { - const patch = patchList.patchList[i]!; - const commitTask = globalCommitTaskMap.get(patch.id); + lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => { + const commitTask = globalCommitTaskMap.get(commitTaskId); if (commitTask) { commitTask(); - globalCommitTaskMap.delete(patch.id); + globalCommitTaskMap.delete(commitTaskId); } - } - }); + }); + }; + options[COMMIT] = commit as ((...args: Parameters) => void); } function commitPatchUpdate(patchList: PatchList, patchOptions: Omit): { @@ -232,15 +189,12 @@ function replaceRequestAnimationFrame(): void { */ export { commitPatchUpdate, - commitToMainThread, genCommitTaskId, clearCommitTaskId, globalBackgroundSnapshotInstancesToRemove, globalCommitTaskMap, globalFlushOptions, nextCommitTaskId, - patchesToCommit, - clearPatchesToCommit, replaceCommitHook, replaceRequestAnimationFrame, type PatchOptions, diff --git a/packages/react/runtime/src/lifecycle/reload.ts b/packages/react/runtime/src/lifecycle/reload.ts index 9f7afb7ee2..b8d1bf9f03 100644 --- a/packages/react/runtime/src/lifecycle/reload.ts +++ b/packages/react/runtime/src/lifecycle/reload.ts @@ -1,6 +1,8 @@ // Copyright 2024 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 { render } from 'preact'; + import { hydrate } from '../hydrate.js'; import { LifecycleConstant } from '../lifecycleConstant.js'; import { __pendingListUpdates } from '../list.js'; @@ -13,7 +15,7 @@ import { destroyWorklet } from '../worklet/destroy.js'; import { clearJSReadyEventIdSwap, isJSReady } from './event/jsReady.js'; import { increaseReloadVersion } from './pass.js'; import { deinitGlobalSnapshotPatch } from './patch/snapshotPatch.js'; -import { renderBackground, renderMainThread } from './render.js'; +import { renderMainThread } from './render.js'; function reloadMainThread(data: any, options: UpdatePageOption): void { if (__PROFILE__) { @@ -74,7 +76,7 @@ function reloadBackground(updateData: Record): void { // COW when modify `lynx.__initData` to make sure Provider & Consumer works lynx.__initData = Object.assign({}, lynx.__initData, updateData); - renderBackground(__root.__jsx, __root as any); + render(__root.__jsx, __root as any); if (__PROFILE__) { console.profileEnd(); diff --git a/packages/react/runtime/src/lifecycle/render.ts b/packages/react/runtime/src/lifecycle/render.ts index 1c67d0e547..c7823a72fa 100644 --- a/packages/react/runtime/src/lifecycle/render.ts +++ b/packages/react/runtime/src/lifecycle/render.ts @@ -2,12 +2,10 @@ // 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 { render } from 'preact'; -import type { ComponentChild, ContainerNode } from 'preact'; import { renderOpcodesInto } from '../opcodes.js'; import { render as renderToString } from '../renderToOpcodes/index.js'; import { __root } from '../root.js'; -import { commitToMainThread } from './patch/commit.js'; function renderMainThread(): void { /* v8 ignore start */ @@ -47,9 +45,4 @@ function renderMainThread(): void { /* v8 ignore stop */ } -function renderBackground(vnode: ComponentChild, parent: ContainerNode): void { - render(vnode, parent); - void commitToMainThread(); -} - -export { renderMainThread, renderBackground }; +export { renderMainThread }; diff --git a/packages/react/runtime/src/lynx-api.ts b/packages/react/runtime/src/lynx-api.ts index 25b274c0bd..3c369b795a 100644 --- a/packages/react/runtime/src/lynx-api.ts +++ b/packages/react/runtime/src/lynx-api.ts @@ -1,13 +1,13 @@ // Copyright 2024 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 { render } from 'preact'; import { createContext, createElement } from 'preact/compat'; import { useState } from 'preact/hooks'; import type { Consumer, FC, ReactNode } from 'react'; import { factory, withInitDataInState } from './compat/initData.js'; import { useLynxGlobalEventListener } from './hooks/useLynxGlobalEventListener.js'; -import { renderBackground } from './lifecycle/render.js'; import { LifecycleConstant } from './lifecycleConstant.js'; import { flushDelayedLifecycleEvents } from './lynx/tt.js'; import { __root } from './root.js'; @@ -86,7 +86,7 @@ export const root: Root = { __root.__jsx = jsx; } else { __root.__jsx = jsx; - renderBackground(jsx, __root as any); + render(jsx, __root as any); if (__FIRST_SCREEN_SYNC_TIMING__ === 'immediately') { // This is for cases where `root.render()` is called asynchronously, // `firstScreen` message might have been reached. diff --git a/packages/react/runtime/src/lynx/tt.ts b/packages/react/runtime/src/lynx/tt.ts index 85306bb958..22679d9302 100644 --- a/packages/react/runtime/src/lynx/tt.ts +++ b/packages/react/runtime/src/lynx/tt.ts @@ -1,34 +1,30 @@ // Copyright 2024 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 { render } from 'preact'; + import { LifecycleConstant, NativeUpdateDataType } from '../lifecycleConstant.js'; import { - PerformanceTimingKeys, PerformanceTimingFlags, + PerformanceTimingKeys, PipelineOrigins, beginPipeline, markTiming, } from './performance.js'; import { BackgroundSnapshotInstance, hydrate } from '../backgroundSnapshot.js'; +import { runWithForce } from './runWithForce.js'; import { destroyBackground } from '../lifecycle/destroy.js'; import { delayedEvents, delayedPublishEvent } from '../lifecycle/event/delayEvents.js'; import { delayLifecycleEvent, delayedLifecycleEvents } from '../lifecycle/event/delayLifecycleEvents.js'; -import { - clearPatchesToCommit, - commitPatchUpdate, - genCommitTaskId, - globalCommitTaskMap, - patchesToCommit, - type PatchList, -} from '../lifecycle/patch/commit.js'; +import { commitPatchUpdate, genCommitTaskId, globalCommitTaskMap } from '../lifecycle/patch/commit.js'; +import type { PatchList } from '../lifecycle/patch/commit.js'; import { reloadBackground } from '../lifecycle/reload.js'; -import { renderBackground } from '../lifecycle/render.js'; import { CHILDREN } from '../renderToOpcodes/constants.js'; import { __root } from '../root.js'; import { globalRefsToSet, updateBackgroundRefs } from '../snapshot/ref.js'; import { backgroundSnapshotInstanceManager } from '../snapshot.js'; import { destroyWorklet } from '../worklet/destroy.js'; -import { runWithForce } from './runWithForce.js'; + export { runWithForce }; function injectTt(): void { @@ -129,14 +125,11 @@ function onLifecycleEventImpl(type: string, data: any): void { console.profile('commitChanges'); } const commitTaskId = genCommitTaskId(); - patchesToCommit.push( - { snapshotPatch, id: commitTaskId }, - ); const patchList: PatchList = { - patchList: patchesToCommit, + patchList: [{ snapshotPatch, id: commitTaskId }], }; - clearPatchesToCommit(); const obj = commitPatchUpdate(patchList, { isHydration: true }); + lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => { updateBackgroundRefs(commitTaskId); globalCommitTaskMap.forEach((commitTask, id) => { @@ -212,7 +205,7 @@ function updateGlobalProps(newData: Record): void { // This is already done because updateFromRoot will consume all dirty flags marked by // the setState, and setState's flush will be a noop. No extra diffs will be needed. Promise.resolve().then(() => { - runWithForce(() => renderBackground(__root.__jsx, __root as any)); + runWithForce(() => render(__root.__jsx, __root as any)); }); lynxCoreInject.tt.GlobalEventEmitter.emit('onGlobalPropsChanged'); } diff --git a/packages/react/testing-library/src/__tests__/worklet.test.jsx b/packages/react/testing-library/src/__tests__/worklet.test.jsx index 221a73a413..b17ffeda7f 100644 --- a/packages/react/testing-library/src/__tests__/worklet.test.jsx +++ b/packages/react/testing-library/src/__tests__/worklet.test.jsx @@ -441,7 +441,24 @@ describe('worklet', () => { [ "rLynxChange", { - "data": "{"patchList":[{"id":1,"workletRefInitValuePatch":[[1,null],[2,0]]},{"snapshotPatch":[3,-2,0,{"_wvid":1},3,-2,1,{"_c":{"ref":{"_wvid":1},"num":{"_wvid":2}},"_wkltId":"a45f:test:9","_execId":1}],"id":2}]}", + "data": "{"patchList":[{"id":1,"workletRefInitValuePatch":[[1,null],[2,0]]}]}", + "patchOptions": { + "pipelineOptions": { + "dsl": "reactLynx", + "needTimestamps": true, + "pipelineID": "pipelineID", + "pipelineOrigin": "reactLynxHydrate", + "stage": "hydrate", + }, + "reloadVersion": 0, + }, + }, + [Function], + ], + [ + "rLynxChange", + { + "data": "{"patchList":[{"snapshotPatch":[3,-2,0,{"_wvid":1},3,-2,1,{"_c":{"ref":{"_wvid":1},"num":{"_wvid":2}},"_wkltId":"a45f:test:9","_execId":1}],"id":2}]}", "patchOptions": { "isHydration": true, "pipelineOptions": { diff --git a/packages/react/testing-library/src/pure.jsx b/packages/react/testing-library/src/pure.jsx index cad285ac72..92064e9346 100644 --- a/packages/react/testing-library/src/pure.jsx +++ b/packages/react/testing-library/src/pure.jsx @@ -8,9 +8,9 @@ import { act } from 'preact/test-utils'; import { __root } from '@lynx-js/react/internal'; +import { commitToMainThread } from '../../runtime/lib/lifecycle/patch/commit.js'; import { flushDelayedLifecycleEvents } from '../../runtime/lib/lynx/tt.js'; import { clearPage } from '../../runtime/lib/snapshot.js'; -import { commitToMainThread } from '../../runtime/lib/lifecycle/patch/commit.js'; export function waitSchedule() { return new Promise(resolve => { @@ -93,8 +93,6 @@ export function cleanup() { globalThis.lynxEnv.switchToBackgroundThread(); act(() => { preactRender(null, __root); - // This is needed to ensure that the ui updates are sent to the main thread - commitToMainThread(); }); lynxEnv.mainThread.elementTree.root = undefined; diff --git a/packages/react/testing-library/src/vitest-global-setup.js b/packages/react/testing-library/src/vitest-global-setup.js index d8dc92bb52..3cf3f44542 100644 --- a/packages/react/testing-library/src/vitest-global-setup.js +++ b/packages/react/testing-library/src/vitest-global-setup.js @@ -1,23 +1,21 @@ import { options } from 'preact'; -import { SnapshotInstance } from '../../runtime/lib/snapshot.js'; -import { snapshotInstanceManager } from '../../runtime/lib/snapshot.js'; + import { BackgroundSnapshotInstance } from '../../runtime/lib/backgroundSnapshot.js'; -import { backgroundSnapshotInstanceManager } from '../../runtime/lib/snapshot.js'; -import { injectCalledByNative } from '../../runtime/lib/lynx/calledByNative.js'; +import { clearCommitTaskId, replaceCommitHook } from '../../runtime/lib/lifecycle/patch/commit.js'; +import { deinitGlobalSnapshotPatch } from '../../runtime/lib/lifecycle/patch/snapshotPatch.js'; import { injectUpdateMainThread } from '../../runtime/lib/lifecycle/patch/updateMainThread.js'; -import { - replaceCommitHook, - clearPatchesToCommit, - clearCommitTaskId, -} from '../../runtime/lib/lifecycle/patch/commit.js'; -import { injectTt } from '../../runtime/lib/lynx/tt.js'; +import { injectCalledByNative } from '../../runtime/lib/lynx/calledByNative.js'; +import { flushDelayedLifecycleEvents, injectTt } from '../../runtime/lib/lynx/tt.js'; import { setRoot } from '../../runtime/lib/root.js'; -import { deinitGlobalSnapshotPatch } from '../../runtime/lib/lifecycle/patch/snapshotPatch.js'; +import { + SnapshotInstance, + backgroundSnapshotInstanceManager, + snapshotInstanceManager, +} from '../../runtime/lib/snapshot.js'; +import { destroyWorklet } from '../../runtime/lib/worklet/destroy.js'; import { initApiEnv } from '../../worklet-runtime/lib/api/lynxApi.js'; import { initEventListeners } from '../../worklet-runtime/lib/listeners.js'; import { initWorklet } from '../../worklet-runtime/lib/workletRuntime.js'; -import { destroyWorklet } from '../../runtime/lib/worklet/destroy.js'; -import { flushDelayedLifecycleEvents } from '../../runtime/lib/lynx/tt.js'; const { onInjectMainThreadGlobals, @@ -133,7 +131,6 @@ globalThis.onInjectBackgroundThreadGlobals = (target) => { // re-init global snapshot patch to undefined deinitGlobalSnapshotPatch(); - clearPatchesToCommit(); clearCommitTaskId(); }; globalThis.onResetLynxEnv = () => { diff --git a/packages/rspeedy/plugin-react/package.json b/packages/rspeedy/plugin-react/package.json index 0540869dd7..b046f6cd44 100644 --- a/packages/rspeedy/plugin-react/package.json +++ b/packages/rspeedy/plugin-react/package.json @@ -64,7 +64,7 @@ "typia-rspack-plugin": "2.0.1" }, "peerDependencies": { - "@lynx-js/react": "^0.103.0 || ^0.104.0 || ^0.105.0 || ^0.106.0 || ^0.107.0" + "@lynx-js/react": "^0.103.0 || ^0.104.0 || ^0.105.0 || ^0.106.0 || ^0.107.0 || ^0.108.0" }, "peerDependenciesMeta": { "@lynx-js/react": { diff --git a/packages/web-platform/web-elements/src/XSwiper/XSwiper.ts b/packages/web-platform/web-elements/src/XSwiper/XSwiper.ts index d964266744..e03a7fc912 100644 --- a/packages/web-platform/web-elements/src/XSwiper/XSwiper.ts +++ b/packages/web-platform/web-elements/src/XSwiper/XSwiper.ts @@ -134,12 +134,12 @@ export class XSwiper extends HTMLElement { minOffsetToMid, }; } - get current() { + get currentIndex() { return this.#getNeatestElementIndexAndDistanceToMid().current; } - set current(newval: number) { + set currentIndex(newval: number) { // When current is specified and current is updated in bindchange, there is no need to respond to the update of current - if (this.current === newval) { + if (this.currentIndex === newval) { return; } @@ -185,28 +185,28 @@ export class XSwiper extends HTMLElement { } scrollToNext() { - const current = this.current; + const current = this.currentIndex; const count = this.childElementCount; if (current === count - 1) { const circularPlay = this.circularPlay; if (circularPlay) { - this.current = 0; + this.currentIndex = 0; } } else { - this.current += 1; + this.currentIndex += 1; } } scrollToPrevious() { - const current = this.current; + const current = this.currentIndex; const count = this.childElementCount; if (current === 0) { const circularPlay = this.circularPlay; if (circularPlay) { - this.current = count - 1; + this.currentIndex = count - 1; } } else { - this.current == count - 1; + this.currentIndex = count - 1; } } diff --git a/packages/web-platform/web-elements/src/XSwiper/XSwiperAutoScroll.ts b/packages/web-platform/web-elements/src/XSwiper/XSwiperAutoScroll.ts index 7e4b0c0670..489b475576 100644 --- a/packages/web-platform/web-elements/src/XSwiper/XSwiperAutoScroll.ts +++ b/packages/web-platform/web-elements/src/XSwiper/XSwiperAutoScroll.ts @@ -23,7 +23,7 @@ export class XSwiperAutoScroll #handleCurrentChange(newVal: string | null) { const newval = Number(newVal); if (!Number.isNaN(newval)) { - this.#dom.current = newval; + this.#dom.currentIndex = newval; } } diff --git a/packages/web-platform/web-elements/src/XSwiper/XSwiperCircular.ts b/packages/web-platform/web-elements/src/XSwiper/XSwiperCircular.ts index 8e8f06f3b5..a6b1e5413d 100644 --- a/packages/web-platform/web-elements/src/XSwiper/XSwiperCircular.ts +++ b/packages/web-platform/web-elements/src/XSwiper/XSwiperCircular.ts @@ -163,7 +163,7 @@ export class XSwiperCircular if (newVal !== null) { this.#changeEventHandler({ detail: { - current: this.#dom.current, + current: this.#dom.currentIndex, isDragged: false, __isFirstLayout: true, }, diff --git a/packages/web-platform/web-elements/src/XSwiper/XSwiperEvents.ts b/packages/web-platform/web-elements/src/XSwiper/XSwiperEvents.ts index 2a323df741..4e19051e78 100644 --- a/packages/web-platform/web-elements/src/XSwiper/XSwiperEvents.ts +++ b/packages/web-platform/web-elements/src/XSwiper/XSwiperEvents.ts @@ -77,7 +77,7 @@ export class XSwipeEvents || currentScrollDistance < 10 || Math.abs(currentScrollDistance - totalScrollDistance) <= pageLength ) { - const current = this.#dom.current; + const current = this.#dom.currentIndex; if (current !== this.#current) { this.#dom.dispatchEvent( new CustomEvent('change', { diff --git a/packages/web-platform/web-elements/src/XSwiper/XSwiperIndicator.ts b/packages/web-platform/web-elements/src/XSwiper/XSwiperIndicator.ts index a547f2cb05..e992003319 100644 --- a/packages/web-platform/web-elements/src/XSwiper/XSwiperIndicator.ts +++ b/packages/web-platform/web-elements/src/XSwiper/XSwiperIndicator.ts @@ -139,7 +139,7 @@ export class XSwiperIndicator boostedQueueMicrotask(() => { ( this.#getIndicatorContainer().children[ - this.#dom.current + this.#dom.currentIndex ] as HTMLElement )?.style.setProperty( 'background-color', diff --git a/packages/web-platform/web-elements/src/XSwiper/x-swiper.css b/packages/web-platform/web-elements/src/XSwiper/x-swiper.css index 9b32e9fae3..8bc00978f7 100644 --- a/packages/web-platform/web-elements/src/XSwiper/x-swiper.css +++ b/packages/web-platform/web-elements/src/XSwiper/x-swiper.css @@ -74,7 +74,7 @@ x-swiper-item { flex-basis: var(--lynx-linear-weight-basis) !important; } -x-swiper-item:nth-child(n+5) { +x-swiper-item:nth-child(n+20) { contain: strict; content-visibility: auto; } diff --git a/packages/web-platform/web-explorer/package.json b/packages/web-platform/web-explorer/package.json index 2b7f49d1dd..893b92caa7 100644 --- a/packages/web-platform/web-explorer/package.json +++ b/packages/web-platform/web-explorer/package.json @@ -20,7 +20,8 @@ ], "scripts": { "build": "rsbuild build", - "dev": "rsbuild dev" + "dev": "rsbuild dev", + "build:rsdoctor": "RSDOCTOR=true rsbuild build" }, "dependencies": { "qr-scanner": "^1.4.2" @@ -30,7 +31,9 @@ "@lynx-js/lynx-core": "0.1.2", "@lynx-js/web-core": "workspace:*", "@lynx-js/web-elements": "workspace:*", + "@lynx-js/web-platform-rsbuild-plugin": "workspace:*", "@rsbuild/core": "catalog:rsbuild", + "@rsdoctor/rspack-plugin": "1.0.2", "tslib": "^2.8.1" } } diff --git a/packages/web-platform/web-explorer/rsbuild.config.ts b/packages/web-platform/web-explorer/rsbuild.config.ts index cf391fe5ad..d1949b6d62 100644 --- a/packages/web-platform/web-explorer/rsbuild.config.ts +++ b/packages/web-platform/web-explorer/rsbuild.config.ts @@ -1,5 +1,7 @@ import { defineConfig } from '@rsbuild/core'; import { codecovWebpackPlugin } from '@codecov/webpack-plugin'; +import { pluginWebPlatform } from '@lynx-js/web-platform-rsbuild-plugin'; +import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin'; const codecovEnabled = !!process.env.CI; console.info('codecov enabled:', codecovEnabled); export default defineConfig({ @@ -7,6 +9,7 @@ export default defineConfig({ entry: { index: './index.ts', }, + include: [/web/], }, output: { target: 'web', @@ -14,6 +17,7 @@ export default defineConfig({ root: 'dist', }, filenameHash: false, + overrideBrowserslist: ['Chrome > 118'], }, dev: { writeToDisk: true, @@ -37,6 +41,12 @@ export default defineConfig({ sha: process.env.GITHUB_SHA, }, }), + process.env.RSDOCTOR === 'true' + && new RsdoctorRspackPlugin({ + supports: { + generateTileGraph: true, + }, + }), ], }, }, @@ -46,4 +56,5 @@ export default defineConfig({ }, profile: true, }, + plugins: [pluginWebPlatform({ polyfill: false })], }); diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/style/cssPropertyMap.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/style/cssPropertyMap.ts index ab02f63e92..242ac1189c 100644 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/style/cssPropertyMap.ts +++ b/packages/web-platform/web-mainthread-apis/src/elementAPI/style/cssPropertyMap.ts @@ -18,218 +18,238 @@ export function camelize(str: string): string { let index = 1; const cssPropertyMap: Record< number, - { name: string; dashName: string; defaultValue: string } + { name: string; dashName: string; isX: boolean } > = {}; const cssPropertyReverseMap: Record = {}; -const V = (name: string, defaultValue: string) => { +const V = (name: string) => { const k = index++; - cssPropertyMap[k] = { name: camelize(name), dashName: name, defaultValue }; + const isX = name.startsWith('-x-'); + cssPropertyMap[k] = { name: camelize(name), dashName: name, isX }; cssPropertyReverseMap[name] = k; }; -V('top', 'auto'); -V('left', 'auto'); -V('right', 'auto'); -V('bottom', 'auto'); -V('position', 'relative'); -V('box-sizing', 'auto'); -V('background-color', 'transparent'); -V('border-left-color', 'black'); -V('border-right-color', 'black'); -V('border-top-color', 'black'); -V('border-bottom-color', 'black'); -V('border-radius', '0px'); -V('border-top-left-radius', '0px'); -V('border-bottom-left-radius', '0px'); -V('border-top-right-radius', '0px'); -V('border-bottom-right-radius', '0px'); -V('border-width', '0px'); -V('border-left-width', '0px'); -V('border-right-width', '0px'); -V('border-top-width', '0px'); -V('border-bottom-width', '0px'); -V('color', 'black'); -V('opacity', '1'); -V('display', 'auto'); -V('overflow', 'hidden'); -V('height', 'auto'); -V('width', 'auto'); -V('max-width', 'auto'); -V('min-width', 'auto'); -V('max-height', 'auto'); -V('min-height', 'auto'); -V('padding', '0px'); -V('padding-left', '0px'); -V('padding-right', '0px'); -V('padding-top', '0px'); -V('padding-bottom', '0px'); -V('margin', '0px'); -V('margin-left', '0px'); -V('margin-right', '0px'); -V('margin-top', '0px'); -V('margin-bottom', '0px'); -V('white-space', 'normal'); -V('letter-spacing', '0px'); -V('text-align', 'start'); -V('line-height', ''); -V('text-overflow', 'clip'); -V('font-size', 'medium'); -V('font-weight', 'normal'); -V('flex', '0'); -V('flex-grow', '0'); -V('flex-shrink', '1'); -V('flex-basis', 'auto'); -V('flex-direction', 'row'); -V('flex-wrap', 'nowrap'); -V('align-items', 'stretch'); -V('align-self', 'stretch'); -V('align-content', 'stretch'); -V('justify-content', 'stretch'); -V('background', 'transparent, transparent'); -V('border-color', 'black'); -V('font-family', ''); -V('font-style', 'normal'); -V('transform', ''); -V('animation', ''); -V('animation-name', ''); -V('animation-duration', ''); -V('animation-timing-function', 'linear'); -V('animation-delay', '0s'); -V('animation-iteration-count', '1'); -V('animation-direction', 'normal'); -V('animation-fill-mode', 'none'); -V('animation-play-state', 'running'); -V('line-spacing', '0px'); -V('border-style', 'solid'); -V('order', '0'); -V('box-shadow', ''); -V('transform-origin', ''); -V('linear-orientation', 'vertical'); -V('linear-weight-sum', '0'); -V('linear-weight', '0'); -V('linear-gravity', 'none'); -V('linear-layout-gravity', 'none'); -V('layout-animation-create-duration', '0s'); -V('layout-animation-create-timing-function', 'linear'); -V('layout-animation-create-delay', '0s'); -V('layout-animation-create-property', 'opacity'); -V('layout-animation-delete-duration', '0s'); -V('layout-animation-delete-timing-function', 'linear'); -V('layout-animation-delete-delay', '0s'); -V('layout-animation-delete-property', 'opacity'); -V('layout-animation-update-duration', '0s'); -V('layout-animation-update-timing-function', 'linear'); -V('layout-animation-update-delay', '0s'); -V('adapt-font-size', '0'); -V('aspect-ratio', ''); -V('text-decoration', ''); -V('text-shadow', ''); -V('background-image', ''); -V('background-position', ''); -V('background-origin', 'border-box'); -V('background-repeat', 'no-repeat'); -V('background-size', ''); -V('border', ''); -V('visibility', 'visible'); -V('border-right', ''); -V('border-left', ''); -V('border-top', ''); -V('border-bottom', ''); -V('transition', ''); -V('transition-property', ''); -V('transition-duration', ''); -V('transition-delay', ''); -V('transition-timing-function', ''); -V('content', ''); -V('border-left-style', ''); -V('border-right-style', ''); -V('border-top-style', ''); -V('border-bottom-style', ''); -V('implicit-animation', 'true'); -V('overflow-x', 'hidden'); -V('overflow-y', 'hidden'); -V('word-break', 'normal'); -V('background-clip', 'border-box'); -V('outline', 'medium none black'); -V('outline-color', 'black'); -V('outline-style', 'black'); -V('outline-width', 'medium'); -V('vertical-align', 'default'); -V('caret-color', 'auto'); -V('direction', 'normal'); -V('relative-id', '-1'); -V('relative-align-top', '-1'); -V('relative-align-right', '-1'); -V('relative-align-bottom', '-1'); -V('relative-align-left', '-1'); -V('relative-top-of', '-1'); -V('relative-right-of', '-1'); -V('relative-bottom-of', '-1'); -V('relative-left-of', '-1'); -V('relative-layout-once', 'true'); -V('relative-center', 'none'); -V('enter-transition-name', ''); -V('exit-transition-name', ''); -V('pause-transition-name', ''); -V('resume-transition-name', ''); -V('flex-flow', 'row nowrap'); -V('z-index', '0'); -V('text-decoration-color', 'black'); -V('linear-cross-gravity', 'none'); -V('margin-inline-start', '0px'); -V('margin-inline-end', '0px'); -V('padding-inline-start', '0px'); -V('padding-inline-end', '0px'); -V('border-inline-start-color', 'black'); -V('border-inline-end-color', 'black'); -V('border-inline-start-width', '0px'); -V('border-inline-end-width', '0px'); -V('border-inline-start-style', ''); -V('border-inline-end-style', ''); -V('border-start-start-radius', '0px'); -V('border-end-start-radius', '0px'); -V('border-start-end-radius', '0px'); -V('border-end-end-radius', '0px'); -V('relative-align-inline-start', '-1'); -V('relative-align-inline-end', '-1'); -V('relative-inline-start-of', '-1'); -V('relative-inline-end-of', '-1'); -V('inset-inline-start', '0px'); -V('inset-inline-end', '0px'); -V('mask-image', ''); -V('grid-template-columns', ''); -V('grid-template-rows', ''); -V('grid-auto-columns', ''); -V('grid-auto-rows', ''); -V('grid-column-span', ''); -V('grid-row-span', ''); -V('grid-column-start', ''); -V('grid-column-end', ''); -V('grid-row-start', ''); -V('grid-row-end', ''); -V('grid-column-gap', ''); -V('grid-row-gap', ''); -V('justify-items', 'stretch'); -V('justify-self', 'auto'); -V('grid-auto-flow', 'row'); -V('filter', ''); -V('list-main-axis-gap', '0px'); -V('list-cross-axis-gap', '0px'); -V('linear-direction', 'column'); -V('perspective', 'none'); -V('cursor', 'default'); -V('text-indent', '0px'); -V('clip-path', ''); -V('text-stroke', '0px transparent'); -V('text-stroke-width', '0px'); -V('text-stroke-color', 'transparent'); -V('-x-auto-font-size', 'false'); -V('-x-auto-font-size-preset-sizes', ''); +V('top'); +V('left'); +V('right'); +V('bottom'); +V('position'); +V('box-sizing'); +V('background-color'); +V('border-left-color'); +V('border-right-color'); +V('border-top-color'); +V('border-bottom-color'); +V('border-radius'); +V('border-top-left-radius'); +V('border-bottom-left-radius'); +V('border-top-right-radius'); +V('border-bottom-right-radius'); +V('border-width'); +V('border-left-width'); +V('border-right-width'); +V('border-top-width'); +V('border-bottom-width'); +V('color'); +V('opacity'); +V('display'); +V('overflow'); +V('height'); +V('width'); +V('max-width'); +V('min-width'); +V('max-height'); +V('min-height'); +V('padding'); +V('padding-left'); +V('padding-right'); +V('padding-top'); +V('padding-bottom'); +V('margin'); +V('margin-left'); +V('margin-right'); +V('margin-top'); +V('margin-bottom'); +V('white-space'); +V('letter-spacing'); +V('text-align'); +V('line-height'); +V('text-overflow'); +V('font-size'); +V('font-weight'); +V('flex'); +V('flex-grow'); +V('flex-shrink'); +V('flex-basis'); +V('flex-direction'); +V('flex-wrap'); +V('align-items'); +V('align-self'); +V('align-content'); +V('justify-content'); +V('background'); +V('border-color'); +V('font-family'); +V('font-style'); +V('transform'); +V('animation'); +V('animation-name'); +V('animation-duration'); +V('animation-timing-function'); +V('animation-delay'); +V('animation-iteration-count'); +V('animation-direction'); +V('animation-fill-mode'); +V('animation-play-state'); +V('line-spacing'); +V('border-style'); +V('order'); +V('box-shadow'); +V('transform-origin'); +V('linear-orientation'); +V('linear-weight-sum'); +V('linear-weight'); +V('linear-gravity'); +V('linear-layout-gravity'); +V('layout-animation-create-duration'); +V('layout-animation-create-timing-function'); +V('layout-animation-create-delay'); +V('layout-animation-create-property'); +V('layout-animation-delete-duration'); +V('layout-animation-delete-timing-function'); +V('layout-animation-delete-delay'); +V('layout-animation-delete-property'); +V('layout-animation-update-duration'); +V('layout-animation-update-timing-function'); +V('layout-animation-update-delay'); +V('adapt-font-size'); +V('aspect-ratio'); +V('text-decoration'); +V('text-shadow'); +V('background-image'); +V('background-position'); +V('background-origin'); +V('background-repeat'); +V('background-size'); +V('border'); +V('visibility'); +V('border-right'); +V('border-left'); +V('border-top'); +V('border-bottom'); +V('transition'); +V('transition-property'); +V('transition-duration'); +V('transition-delay'); +V('transition-timing-function'); +V('content'); +V('border-left-style'); +V('border-right-style'); +V('border-top-style'); +V('border-bottom-style'); +V('implicit-animation'); +V('overflow-x'); +V('overflow-y'); +V('word-break'); +V('background-clip'); +V('outline'); +V('outline-color'); +V('outline-style'); +V('outline-width'); +V('vertical-align'); +V('caret-color'); +V('direction'); +V('relative-id'); +V('relative-align-top'); +V('relative-align-right'); +V('relative-align-bottom'); +V('relative-align-left'); +V('relative-top-of'); +V('relative-right-of'); +V('relative-bottom-of'); +V('relative-left-of'); +V('relative-layout-once'); +V('relative-center'); +V('enter-transition-name'); +V('exit-transition-name'); +V('pause-transition-name'); +V('resume-transition-name'); +V('flex-flow'); +V('z-index'); +V('text-decoration-color'); +V('linear-cross-gravity'); +V('margin-inline-start'); +V('margin-inline-end'); +V('padding-inline-start'); +V('padding-inline-end'); +V('border-inline-start-color'); +V('border-inline-end-color'); +V('border-inline-start-width'); +V('border-inline-end-width'); +V('border-inline-start-style'); +V('border-inline-end-style'); +V('border-start-start-radius'); +V('border-end-start-radius'); +V('border-start-end-radius'); +V('border-end-end-radius'); +V('relative-align-inline-start'); +V('relative-align-inline-end'); +V('relative-inline-start-of'); +V('relative-inline-end-of'); +V('inset-inline-start'); +V('inset-inline-end'); +V('mask-image'); +V('grid-template-columns'); +V('grid-template-rows'); +V('grid-auto-columns'); +V('grid-auto-rows'); +V('grid-column-span'); +V('grid-row-span'); +V('grid-column-start'); +V('grid-column-end'); +V('grid-row-start'); +V('grid-row-end'); +V('grid-column-gap'); +V('grid-row-gap'); +V('justify-items'); +V('justify-self'); +V('grid-auto-flow'); +V('filter'); +V('list-main-axis-gap'); +V('list-cross-axis-gap'); +V('linear-direction'); +V('perspective'); +V('cursor'); +V('text-indent'); +V('clip-path'); +V('text-stroke'); +V('text-stroke-width'); +V('text-stroke-color'); +V('-x-auto-font-size'); +V('-x-auto-font-size-preset-sizes'); +V('mask'); +V('mask-repeat'); +V('mask-position'); +V('mask-clip'); +V('mask-origin'); +V('mask-size'); +V('gap'); +V('column-gap'); +V('row-gap'); +V('image-rendering'); +V('hyphens'); +V('-x-app-region'); +V( + '-x-animation-color-interpolation', +); +V('-x-handle-color'); +V('-x-handle-size'); +V('offset-path'); +V('offset-distance'); export function queryCSSProperty(index: number): { name: string; - defaultValue: string; dashName: string; + isX: boolean; } { return cssPropertyMap[index]!; } diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/style/styleFunctions.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/style/styleFunctions.ts index 5840a4da30..cf1810e06d 100644 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/style/styleFunctions.ts +++ b/packages/web-platform/web-mainthread-apis/src/elementAPI/style/styleFunctions.ts @@ -68,7 +68,13 @@ export function createStyleFunctions( ): void { let dashName: string | undefined; if (typeof key === 'number') { - dashName = queryCSSProperty(key).dashName; + const queryResult = queryCSSProperty(key); + dashName = queryResult.dashName; + if (queryResult.isX) { + console.error( + `[lynx-web] css property: ${dashName} is not supported.`, + ); + } } else { dashName = key; } diff --git a/packages/web-platform/web-rsbuild-plugin/README.md b/packages/web-platform/web-rsbuild-plugin/README.md new file mode 100644 index 0000000000..ee619412c2 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/README.md @@ -0,0 +1,14 @@ +# @lynx-js/web-platform-rsbuild-plugin + +Lynx3 Web Platform rsbuild plugin + +## Usage + +```javascript +import { pluginWebPlatform } from '@lynx-js/web-platform-rsbuild-plugin'; +import { defineConfig } from '@rsbuild/core'; + +export default defineConfig({ + plugins: [pluginWebPlatform()], +}); +``` diff --git a/packages/web-platform/web-rsbuild-plugin/api-extractor.json b/packages/web-platform/web-rsbuild-plugin/api-extractor.json new file mode 100644 index 0000000000..65b0c69ca6 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/api-extractor.json @@ -0,0 +1,6 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "extends": "../../../api-extractor.json", +} diff --git a/packages/web-platform/web-rsbuild-plugin/package.json b/packages/web-platform/web-rsbuild-plugin/package.json new file mode 100644 index 0000000000..318b30b659 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/package.json @@ -0,0 +1,40 @@ +{ + "name": "@lynx-js/web-platform-rsbuild-plugin", + "version": "0.0.0", + "private": false, + "description": "A rsbuild plugin for Web projects in Lynx Web Platform", + "keywords": [ + "rsbuild", + "Lynx", + "Web Platform" + ], + "repository": { + "type": "git", + "url": "https://github.com/lynx-family/lynx-stack.git", + "directory": "packages/web-platform/web-rsbuild-plugin" + }, + "license": "Apache-2.0", + "type": "module", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "files": [ + "dist", + "CHANGELOG.md", + "README.md" + ], + "scripts": { + "build": "rslib build", + "dev": "rslib build --watch", + "test": "vitest" + }, + "devDependencies": { + "@microsoft/api-extractor": "catalog:", + "@rsbuild/core": "catalog:rsbuild", + "@rslib/core": "^0.6.7", + "typescript": "^5.8.3", + "vitest": "^3.1.2" + }, + "peerDependencies": { + "@rsbuild/core": "*" + } +} diff --git a/packages/web-platform/web-rsbuild-plugin/rslib.config.ts b/packages/web-platform/web-rsbuild-plugin/rslib.config.ts new file mode 100644 index 0000000000..92a6b11025 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/rslib.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from '@rslib/core'; + +export default defineConfig({ + lib: [ + { format: 'esm', syntax: 'es2022', dts: { bundle: true } }, + ], +}); diff --git a/packages/web-platform/web-rsbuild-plugin/src/index.ts b/packages/web-platform/web-rsbuild-plugin/src/index.ts new file mode 100644 index 0000000000..9fbecff720 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/src/index.ts @@ -0,0 +1,11 @@ +// Copyright 2024 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. + +/** + * @packageDocumentation + * + * A rsbuild plugin that integrates with Web Platform. + */ + +export { pluginWebPlatform } from './pluginWebPlatform.js'; diff --git a/packages/web-platform/web-rsbuild-plugin/src/pluginWebPlatform.ts b/packages/web-platform/web-rsbuild-plugin/src/pluginWebPlatform.ts new file mode 100644 index 0000000000..3aab4ad959 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/src/pluginWebPlatform.ts @@ -0,0 +1,67 @@ +// Copyright 2024 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 type { RsbuildPlugin } from '@rsbuild/core'; + +/** + * The options for {@link pluginWebPlatform}. + * + * @public + */ +export interface PluginWebPlatformOptions { + /** + * Whether to polyfill the packages about Lynx Web Platform. + * + * If it is true, @lynx-js will be compiled and polyfills will be added + * + * @default true + */ + polyfill?: boolean; +} + +/** + * Create a rsbuild plugin for Lynx Web Platform. + * + * @example + * ```ts + * // rsbuild.config.ts + * import { pluginWebPlatform } from '@lynx-js/web-platform-rsbuild-plugin' + * import { defineConfig } from '@rsbuild/core'; + * + * export default defineConfig({ + * plugins: [pluginWebPlatform()], + * }) + * ``` + * + * @public + */ +export function pluginWebPlatform( + userOptions?: PluginWebPlatformOptions, +): RsbuildPlugin { + return { + name: 'lynx:web-platform', + async setup(api) { + const defaultPluginOptions = { + polyfill: true, + }; + const options = Object.assign({}, defaultPluginOptions, userOptions); + + api.modifyRsbuildConfig(config => { + if (options.polyfill === true) { + config.source = { + ...config.source, + include: [ + ...(config.source?.include ?? []), + /node_modules[\\/]@lynx-js[\\/]/, + ], + }; + config.output = { + ...config.output, + polyfill: 'usage', + }; + } + }); + }, + }; +} diff --git a/packages/web-platform/web-rsbuild-plugin/tests/__snapshots__/config.test.ts.snap b/packages/web-platform/web-rsbuild-plugin/tests/__snapshots__/config.test.ts.snap new file mode 100644 index 0000000000..0079db6849 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/tests/__snapshots__/config.test.ts.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Config > basic config 1`] = ` +[ + /node_modules\\[\\\\\\\\/\\]@lynx-js\\[\\\\\\\\/\\]/, +] +`; diff --git a/packages/web-platform/web-rsbuild-plugin/tests/config.test.ts b/packages/web-platform/web-rsbuild-plugin/tests/config.test.ts new file mode 100644 index 0000000000..ced15c0de1 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/tests/config.test.ts @@ -0,0 +1,21 @@ +import { createRsbuild } from '@rsbuild/core'; +import { describe, expect, test, vi } from 'vitest'; +import { pluginWebPlatform } from '../src/index.js'; + +describe('Config', () => { + test('basic config', async () => { + const rsbuild = await createRsbuild({ + rsbuildConfig: { + plugins: [ + pluginWebPlatform(), + ], + }, + }); + + await rsbuild.initConfigs(); + const rsbuildConfig = rsbuild.getRsbuildConfig(); + + expect(rsbuildConfig.source?.include).toMatchSnapshot(); + expect(rsbuildConfig.output?.polyfill).toBe('usage'); + }); +}); diff --git a/packages/web-platform/web-rsbuild-plugin/tsconfig.json b/packages/web-platform/web-rsbuild-plugin/tsconfig.json new file mode 100644 index 0000000000..cffb9f9080 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "outDir": "./dist", + "lib": ["ESNext"], + "noUnusedParameters": false, + "noImplicitReturns": false, + }, + "include": ["src"], +} diff --git a/packages/web-platform/web-rsbuild-plugin/turbo.json b/packages/web-platform/web-rsbuild-plugin/turbo.json new file mode 100644 index 0000000000..c73c3199b8 --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/turbo.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": [ + "//" + ], + "tasks": { + "build": { + "dependsOn": [ + "^build" + ], + "inputs": [ + "src" + ], + "outputs": [ + "dist" + ] + } + } +} diff --git a/packages/web-platform/web-rsbuild-plugin/vitest.config.ts b/packages/web-platform/web-rsbuild-plugin/vitest.config.ts new file mode 100644 index 0000000000..0658fa4d5c --- /dev/null +++ b/packages/web-platform/web-rsbuild-plugin/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineProject } from 'vitest/config'; +import type { UserWorkspaceConfig } from 'vitest/config'; + +const config: UserWorkspaceConfig = defineProject({ + test: { + name: 'web-platform', + expect: { + poll: { + timeout: 3000, + }, + }, + }, +}); + +export default config; diff --git a/packages/webpack/react-webpack-plugin/package.json b/packages/webpack/react-webpack-plugin/package.json index b74dbced79..bb16121c16 100644 --- a/packages/webpack/react-webpack-plugin/package.json +++ b/packages/webpack/react-webpack-plugin/package.json @@ -53,7 +53,7 @@ "webpack": "^5.99.6" }, "peerDependencies": { - "@lynx-js/react": "^0.103.0 || ^0.104.0 || ^0.105.0 || ^0.106.0 || ^0.107.0", + "@lynx-js/react": "^0.103.0 || ^0.104.0 || ^0.105.0 || ^0.106.0 || ^0.107.0 || ^0.108.0", "@lynx-js/template-webpack-plugin": "^0.4.0 || ^0.5.0 || ^0.6.0" }, "peerDependenciesMeta": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a98551b5ba..f8a02320ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -699,9 +699,15 @@ importers: '@lynx-js/web-elements': specifier: workspace:* version: link:../web-elements + '@lynx-js/web-platform-rsbuild-plugin': + specifier: workspace:* + version: link:../web-rsbuild-plugin '@rsbuild/core': specifier: catalog:rsbuild version: 1.3.11 + '@rsdoctor/rspack-plugin': + specifier: 1.0.2 + version: 1.0.2(@rsbuild/core@1.3.11)(@rspack/core@1.3.6(@swc/helpers@0.5.17))(webpack@5.99.6) tslib: specifier: ^2.8.1 version: 2.8.1 @@ -721,6 +727,24 @@ importers: specifier: ^1.1.0 version: 1.1.0 + packages/web-platform/web-rsbuild-plugin: + devDependencies: + '@microsoft/api-extractor': + specifier: 'catalog:' + version: 7.52.5(@types/node@22.15.2) + '@rsbuild/core': + specifier: catalog:rsbuild + version: 1.3.11 + '@rslib/core': + specifier: ^0.6.7 + version: 0.6.7(@microsoft/api-extractor@7.52.5(@types/node@22.15.2))(typescript@5.8.3) + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vitest: + specifier: ^3.1.2 + version: 3.1.2(@types/debug@4.1.12)(@types/node@22.15.2)(@vitest/ui@3.1.2)(jsdom@26.1.0)(sass-embedded@1.86.0)(terser@5.31.6) + packages/web-platform/web-style-transformer: {} packages/web-platform/web-tests: