diff --git a/.changeset/giant-seas-leave.md b/.changeset/giant-seas-leave.md new file mode 100644 index 0000000000..e664237fe3 --- /dev/null +++ b/.changeset/giant-seas-leave.md @@ -0,0 +1,15 @@ +--- +"@lynx-js/web-mainthread-apis": patch +"@lynx-js/web-worker-runtime": patch +"@lynx-js/web-core": patch +--- + +feat: support thread strategy `all-on-ui` + +```html + +``` + +This will make the lynx's main-thread run on the UA's main thread. + +Note that the `all-on-ui` does not support the HMR & chunk splitting yet. diff --git a/.changeset/slow-tools-ring.md b/.changeset/slow-tools-ring.md new file mode 100644 index 0000000000..444b16c5ca --- /dev/null +++ b/.changeset/slow-tools-ring.md @@ -0,0 +1,5 @@ +--- +"@lynx-js/offscreen-document": patch +--- + +feat: support parentNode diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40444334cc..78f7a0f6b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -221,6 +221,21 @@ jobs: export PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-report.junit.xml pnpm --filter @lynx-js/web-tests run test --reporter='github,dot,junit,html' pnpm --filter @lynx-js/web-tests run coverage:ci + playwright-linux-all-on-ui: + needs: build + uses: ./.github/workflows/workflow-test.yml + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + runs-on: lynx-ubuntu-24.04-xlarge + is-web: true + codecov-flags: "e2e" + run: | + export NODE_OPTIONS="--max-old-space-size=32768" + export PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-report.junit.xml + 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 lighthouse: needs: build uses: ./.github/workflows/workflow-test.yml @@ -270,6 +285,7 @@ jobs: needs: - code-style-check - playwright-linux + - playwright-linux-all-on-ui - test-plugins - test-publish - test-react diff --git a/packages/web-platform/offscreen-document/src/webworker/OffscreenNode.ts b/packages/web-platform/offscreen-document/src/webworker/OffscreenNode.ts index 1b4d7cebfd..0ee66e1d47 100644 --- a/packages/web-platform/offscreen-document/src/webworker/OffscreenNode.ts +++ b/packages/web-platform/offscreen-document/src/webworker/OffscreenNode.ts @@ -28,6 +28,10 @@ export class OffscreenNode extends EventTarget { return this._parentElement; } + get parentNode(): OffscreenNode | null { + return this._parentElement; + } + get firstElementChild(): OffscreenNode | null { return this._children[0] ?? null; } diff --git a/packages/web-platform/web-core/package.json b/packages/web-platform/web-core/package.json index 09537f6acc..28b1c59a1e 100644 --- a/packages/web-platform/web-core/package.json +++ b/packages/web-platform/web-core/package.json @@ -26,6 +26,7 @@ "dependencies": { "@lynx-js/offscreen-document": "workspace:*", "@lynx-js/web-constants": "workspace:*", + "@lynx-js/web-mainthread-apis": "workspace:*", "@lynx-js/web-worker-rpc": "workspace:*", "@lynx-js/web-worker-runtime": "workspace:*" }, diff --git a/packages/web-platform/web-core/src/apis/LynxView.ts b/packages/web-platform/web-core/src/apis/LynxView.ts index 43020fee2d..f8eb5b97df 100644 --- a/packages/web-platform/web-core/src/apis/LynxView.ts +++ b/packages/web-platform/web-core/src/apis/LynxView.ts @@ -45,6 +45,7 @@ export type INapiModulesCall = ( * @property {INapiModulesCall} onNapiModulesCall [optional] the NapiModule value handler. * @property {"false" | "true" | null} injectHeadLinks [optional] @default true set it to "false" to disable injecting the styles into shadowroot * @property {number} lynxGroupId [optional] (attribute: "lynx-group-id") the background shared context id, which is used to share webworker between different lynx cards + * @property {"all-on-ui" | "multi-thread"} threadStrategy [optional] @default "multi-thread" (attribute: "thread-strategy") controls the thread strategy for current lynx view * @property {(string)=>Promise} customTemplateLoader [optional] the custom template loader, which is used to load the template * * @event error lynx card fired an error @@ -284,6 +285,22 @@ export class LynxView extends HTMLElement { } } + /** + * @param + * @property + */ + get threadStrategy(): 'all-on-ui' | 'multi-thread' { + // @ts-expect-error + return this.getAttribute('thread-strategy'); + } + set threadStrategy(val: 'all-on-ui' | 'multi-thread') { + if (val) { + this.setAttribute('thread-strategy', val); + } else { + this.removeAttribute('thread-strategy'); + } + } + /** * @private */ @@ -332,7 +349,11 @@ export class LynxView extends HTMLElement { this.attachShadow({ mode: 'open' }); } const lynxGroupId = this.lynxGroupId; + const threadStrategy = (this.threadStrategy ?? 'multi-thread') as + | 'all-on-ui' + | 'multi-thread'; const lynxView = createLynxView({ + threadStrategy, tagMap, shadowRoot: this.shadowRoot!, templateUrl: this.#url, diff --git a/packages/web-platform/web-core/src/apis/createLynxView.ts b/packages/web-platform/web-core/src/apis/createLynxView.ts index ef0d4cb35a..468aa2f862 100644 --- a/packages/web-platform/web-core/src/apis/createLynxView.ts +++ b/packages/web-platform/web-core/src/apis/createLynxView.ts @@ -9,7 +9,10 @@ import type { sendGlobalEventEndpoint, UpdateDataType, } from '@lynx-js/web-constants'; -import { startUIThread } from '../uiThread/startUIThread.js'; +import { + startUIThread, + type StartUIThreadCallbacks, +} from '../uiThread/startUIThread.js'; import type { RpcCallType } from '@lynx-js/web-worker-rpc'; export interface LynxViewConfigs { @@ -17,11 +20,12 @@ export interface LynxViewConfigs { initData: Cloneable; globalProps: Cloneable; shadowRoot: ShadowRoot; - callbacks: Parameters[4]; + callbacks: StartUIThreadCallbacks; nativeModulesMap: NativeModulesMap; napiModulesMap: NapiModulesMap; tagMap: Record; lynxGroupId: number | undefined; + threadStrategy: 'all-on-ui' | 'multi-thread'; } export interface LynxView { @@ -45,6 +49,7 @@ export function createLynxView(configs: LynxViewConfigs): LynxView { napiModulesMap, tagMap, lynxGroupId, + threadStrategy = 'multi-thread', } = configs; return startUIThread( templateUrl, @@ -60,6 +65,7 @@ export function createLynxView(configs: LynxViewConfigs): LynxView { }, shadowRoot, lynxGroupId, + threadStrategy, callbacks, ); } diff --git a/packages/web-platform/web-core/src/uiThread/bootWorkers.ts b/packages/web-platform/web-core/src/uiThread/bootWorkers.ts index 2abf7d049f..e3662fcda4 100644 --- a/packages/web-platform/web-core/src/uiThread/bootWorkers.ts +++ b/packages/web-platform/web-core/src/uiThread/bootWorkers.ts @@ -15,12 +15,21 @@ const backgroundWorkerContextCount: number[] = []; const contextIdToBackgroundWorker: (Worker | undefined)[] = []; let preHeatedMainWorker = createMainWorker(); - export function bootWorkers( lynxGroupId: number | undefined, + allOnUI?: boolean, ): LynxViewRpc { - const curMainWorker = preHeatedMainWorker; - preHeatedMainWorker = createMainWorker(); + let curMainWorker: { + mainThreadRpc: Rpc; + mainThreadWorker?: Worker; + channelMainThreadWithBackground: MessageChannel; + }; + if (allOnUI) { + curMainWorker = createUIChannel(); + } else { + curMainWorker = preHeatedMainWorker; + preHeatedMainWorker = createMainWorker(); + } const curBackgroundWorker = createBackgroundWorker( lynxGroupId, curMainWorker.channelMainThreadWithBackground, @@ -32,12 +41,11 @@ export function bootWorkers( backgroundWorkerContextCount[lynxGroupId] = 1; } } - return { mainThreadRpc: curMainWorker.mainThreadRpc, backgroundRpc: curBackgroundWorker.backgroundRpc, terminateWorkers: () => { - curMainWorker.mainThreadWorker.terminate(); + curMainWorker.mainThreadWorker?.terminate(); if (lynxGroupId === undefined) { curBackgroundWorker.backgroundThreadWorker.terminate(); } else if (backgroundWorkerContextCount[lynxGroupId] === 1) { @@ -49,6 +57,18 @@ export function bootWorkers( }; } +function createUIChannel() { + const channelMainThreadWithBackground = new MessageChannel(); + const mainThreadRpc = new Rpc( + channelMainThreadWithBackground.port1, + 'main-to-bg', + ); + return { + mainThreadRpc, + channelMainThreadWithBackground, + }; +} + function createMainWorker() { const channelToMainThread = new MessageChannel(); const channelMainThreadWithBackground = new MessageChannel(); diff --git a/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts new file mode 100644 index 0000000000..cf5ce26955 --- /dev/null +++ b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts @@ -0,0 +1,54 @@ +import type { + MainThreadStartConfigs, + RpcCallType, + updateDataEndpoint, +} from '@lynx-js/web-constants'; +import type { MainThreadRuntime } from '@lynx-js/web-mainthread-apis'; +import { Rpc } from '@lynx-js/web-worker-rpc'; + +const { + loadMainThread, +} = await import('@lynx-js/web-mainthread-apis'); + +export function createRenderAllOnUI( + mainToBackgroundRpc: Rpc, + shadowRoot: ShadowRoot, + markTimingInternal: ( + timingKey: string, + pipelineId?: string, + timeStamp?: number, + ) => void, + callbacks: { + onError?: () => void; + }, +) { + if (!globalThis.module) { + Object.assign(globalThis, { module: {} }); + } + const docu = Object.assign(shadowRoot, { + createElement: document.createElement.bind(document), + }); + const { startMainThread } = loadMainThread( + mainToBackgroundRpc, + docu, + () => {}, + markTimingInternal, + () => { + callbacks.onError?.(); + }, + ); + let runtime!: MainThreadRuntime; + const start = async (configs: MainThreadStartConfigs) => { + const mainThreadRuntime = startMainThread(configs); + runtime = await mainThreadRuntime; + }; + const updateDataMainThread: RpcCallType = async ( + ...args + ) => { + runtime.updatePage?.(...args); + }; + return { + start, + updateDataMainThread, + }; +} diff --git a/packages/web-platform/web-core/src/uiThread/createRenderMultiThread.ts b/packages/web-platform/web-core/src/uiThread/createRenderMultiThread.ts new file mode 100644 index 0000000000..88df0697c0 --- /dev/null +++ b/packages/web-platform/web-core/src/uiThread/createRenderMultiThread.ts @@ -0,0 +1,32 @@ +import { + mainThreadStartEndpoint, + updateDataEndpoint, +} from '@lynx-js/web-constants'; +import type { Rpc } from '@lynx-js/web-worker-rpc'; +import { registerReportErrorHandler } from './crossThreadHandlers/registerReportErrorHandler.js'; +import { registerFlushElementTreeHandler } from './crossThreadHandlers/registerFlushElementTreeHandler.js'; + +export function createRenderMultiThread( + mainThreadRpc: Rpc, + shadowRoot: ShadowRoot, + callbacks: { + onError?: () => void; + }, +) { + registerReportErrorHandler( + mainThreadRpc, + callbacks.onError, + ); + registerFlushElementTreeHandler( + mainThreadRpc, + { + shadowRoot, + }, + ); + const start = mainThreadRpc.createCall(mainThreadStartEndpoint); + const updateDataMainThread = mainThreadRpc.createCall(updateDataEndpoint); + return { + start, + updateDataMainThread, + }; +} diff --git a/packages/web-platform/web-core/src/uiThread/crossThreadHandlers/createUpdateData.ts b/packages/web-platform/web-core/src/uiThread/crossThreadHandlers/createUpdateData.ts index 403d87a673..10e2206210 100644 --- a/packages/web-platform/web-core/src/uiThread/crossThreadHandlers/createUpdateData.ts +++ b/packages/web-platform/web-core/src/uiThread/crossThreadHandlers/createUpdateData.ts @@ -2,7 +2,7 @@ // 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 { Rpc } from '@lynx-js/web-worker-rpc'; +import type { RpcCallType } from '@lynx-js/web-worker-rpc'; import type { LynxView } from '../../apis/createLynxView.js'; import { updateDataEndpoint, @@ -11,8 +11,8 @@ import { } from '@lynx-js/web-constants'; export function createUpdateData( - mainThreadRpc: Rpc, - backgroundRpc: Rpc, + updateDataMainThread: RpcCallType, + updateDataBackground: RpcCallType, ): LynxView['updateData'] { return ( data: Cloneable, @@ -21,8 +21,8 @@ export function createUpdateData( ) => { Promise.all([ // There is no need to process options for now, as they have default values. - mainThreadRpc.invoke(updateDataEndpoint, [data, {}]), - backgroundRpc.invoke(updateDataEndpoint, [data, {}]), + updateDataMainThread(data, {}), + updateDataBackground(data, {}), ]).then(() => callback?.()); }; } diff --git a/packages/web-platform/web-core/src/uiThread/startBackground.ts b/packages/web-platform/web-core/src/uiThread/startBackground.ts new file mode 100644 index 0000000000..9d05459cf7 --- /dev/null +++ b/packages/web-platform/web-core/src/uiThread/startBackground.ts @@ -0,0 +1,59 @@ +import { + markTimingEndpoint, + sendGlobalEventEndpoint, + updateDataEndpoint, + type NapiModulesCall, + type NativeModulesCall, +} from '@lynx-js/web-constants'; +import type { Rpc } from '@lynx-js/web-worker-rpc'; +import { registerInvokeUIMethodHandler } from './crossThreadHandlers/registerInvokeUIMethodHandler.js'; +import { registerNativePropsHandler } from './crossThreadHandlers/registerSetNativePropsHandler.js'; +import { registerNativeModulesCallHandler } from './crossThreadHandlers/registerNativeModulesCallHandler.js'; +import { registerTriggerComponentEventHandler } from './crossThreadHandlers/registerTriggerComponentEventHandler.js'; +import { registerSelectComponentHandler } from './crossThreadHandlers/registerSelectComponentHandler.js'; +import { registerNapiModulesCallHandler } from './crossThreadHandlers/registerNapiModulesCallHandler.js'; +import { registerDispatchLynxViewEventHandler } from './crossThreadHandlers/registerDispatchLynxViewEventHandler.js'; + +export function startBackground( + backgroundRpc: Rpc, + shadowRoot: ShadowRoot, + callbacks: { + nativeModulesCall: NativeModulesCall; + napiModulesCall: NapiModulesCall; + }, +) { + registerInvokeUIMethodHandler( + backgroundRpc, + shadowRoot, + ); + registerNativePropsHandler( + backgroundRpc, + shadowRoot, + ); + registerTriggerComponentEventHandler( + backgroundRpc, + shadowRoot, + ); + registerSelectComponentHandler( + backgroundRpc, + shadowRoot, + ); + registerNativeModulesCallHandler( + backgroundRpc, + callbacks.nativeModulesCall, + ); + registerNapiModulesCallHandler( + backgroundRpc, + callbacks.napiModulesCall, + ); + registerDispatchLynxViewEventHandler(backgroundRpc, shadowRoot); + + const sendGlobalEvent = backgroundRpc.createCall(sendGlobalEventEndpoint); + const markTiming = backgroundRpc.createCall(markTimingEndpoint); + const updateDataBackground = backgroundRpc.createCall(updateDataEndpoint); + return { + sendGlobalEvent, + markTiming, + updateDataBackground, + }; +} diff --git a/packages/web-platform/web-core/src/uiThread/startUIThread.ts b/packages/web-platform/web-core/src/uiThread/startUIThread.ts index af8e9b49aa..48fffecded 100644 --- a/packages/web-platform/web-core/src/uiThread/startUIThread.ts +++ b/packages/web-platform/web-core/src/uiThread/startUIThread.ts @@ -3,19 +3,9 @@ // LICENSE file in the root directory of this source tree. import type { LynxView } from '../apis/createLynxView.js'; -import { registerInvokeUIMethodHandler } from './crossThreadHandlers/registerInvokeUIMethodHandler.js'; -import { registerNativePropsHandler } from './crossThreadHandlers/registerSetNativePropsHandler.js'; -import { registerNativeModulesCallHandler } from './crossThreadHandlers/registerNativeModulesCallHandler.js'; import { bootWorkers } from './bootWorkers.js'; -import { registerReportErrorHandler } from './crossThreadHandlers/registerReportErrorHandler.js'; -import { registerFlushElementTreeHandler } from './crossThreadHandlers/registerFlushElementTreeHandler.js'; import { createDispose } from './crossThreadHandlers/createDispose.js'; -import { registerTriggerComponentEventHandler } from './crossThreadHandlers/registerTriggerComponentEventHandler.js'; -import { registerSelectComponentHandler } from './crossThreadHandlers/registerSelectComponentHandler.js'; import { - mainThreadStartEndpoint, - markTimingEndpoint, - sendGlobalEventEndpoint, type LynxTemplate, type MainThreadStartConfigs, type NapiModulesCall, @@ -23,30 +13,37 @@ import { } from '@lynx-js/web-constants'; import { loadTemplate } from '../utils/loadTemplate.js'; import { createUpdateData } from './crossThreadHandlers/createUpdateData.js'; -import { registerNapiModulesCallHandler } from './crossThreadHandlers/registerNapiModulesCallHandler.js'; -import { registerDispatchLynxViewEventHandler } from './crossThreadHandlers/registerDispatchLynxViewEventHandler.js'; +import { startBackground } from './startBackground.js'; +import { createRenderMultiThread } from './createRenderMultiThread.js'; +import { createRenderAllOnUI } from './createRenderAllOnUI.js'; + +export type StartUIThreadCallbacks = { + nativeModulesCall: NativeModulesCall; + napiModulesCall: NapiModulesCall; + onError?: () => void; + customTemplateLoader?: (url: string) => Promise; +}; export function startUIThread( templateUrl: string, configs: Omit, shadowRoot: ShadowRoot, lynxGroupId: number | undefined, - callbacks: { - nativeModulesCall: NativeModulesCall; - napiModulesCall: NapiModulesCall; - onError?: () => void; - customTemplateLoader?: (url: string) => Promise; - }, + threadStrategy: 'all-on-ui' | 'multi-thread', + callbacks: StartUIThreadCallbacks, ): LynxView { const createLynxStartTiming = performance.now() + performance.timeOrigin; + const allOnUI = threadStrategy === 'all-on-ui'; const { mainThreadRpc, backgroundRpc, terminateWorkers, - } = bootWorkers(lynxGroupId); - const sendGlobalEvent = backgroundRpc.createCall(sendGlobalEventEndpoint); - const mainThreadStart = mainThreadRpc.createCall(mainThreadStartEndpoint); - const markTiming = backgroundRpc.createCall(markTimingEndpoint); + } = bootWorkers(lynxGroupId, allOnUI); + const { markTiming, sendGlobalEvent, updateDataBackground } = startBackground( + backgroundRpc, + shadowRoot, + callbacks, + ); const markTimingInternal = ( timingKey: string, pipelineId?: string, @@ -55,52 +52,29 @@ export function startUIThread( if (!timeStamp) timeStamp = performance.now() + performance.timeOrigin; markTiming(timingKey, pipelineId, timeStamp); }; + const { start, updateDataMainThread } = allOnUI + ? createRenderAllOnUI( + /* main-to-bg rpc*/ mainThreadRpc, + shadowRoot, + markTimingInternal, + callbacks, + ) + : createRenderMultiThread( + /* main-to-ui rpc*/ mainThreadRpc, + shadowRoot, + callbacks, + ); markTimingInternal('create_lynx_start', undefined, createLynxStartTiming); markTimingInternal('load_template_start'); loadTemplate(templateUrl, callbacks.customTemplateLoader).then((template) => { markTimingInternal('load_template_end'); - mainThreadStart({ + start({ ...configs, template, }); }); - registerReportErrorHandler( - mainThreadRpc, - callbacks.onError, - ); - registerDispatchLynxViewEventHandler(backgroundRpc, shadowRoot); - registerFlushElementTreeHandler( - mainThreadRpc, - { - shadowRoot, - }, - ); - registerInvokeUIMethodHandler( - backgroundRpc, - shadowRoot, - ); - registerNativePropsHandler( - backgroundRpc, - shadowRoot, - ); - registerTriggerComponentEventHandler( - backgroundRpc, - shadowRoot, - ); - registerSelectComponentHandler( - backgroundRpc, - shadowRoot, - ); - registerNativeModulesCallHandler( - backgroundRpc, - callbacks.nativeModulesCall, - ); - registerNapiModulesCallHandler( - backgroundRpc, - callbacks.napiModulesCall, - ); return { - updateData: createUpdateData(mainThreadRpc, backgroundRpc), + updateData: createUpdateData(updateDataMainThread, updateDataBackground), dispose: createDispose( backgroundRpc, terminateWorkers, diff --git a/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts b/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts index a459d733e5..efd9e0ef6e 100644 --- a/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts +++ b/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts @@ -57,7 +57,7 @@ export interface MainThreadConfig { callbacks: MainThreadRuntimeCallbacks; styleInfo: StyleInfo; customSections: LynxTemplate['customSections']; - lepusCode: LynxTemplate['lepusCode']; + lepusCode: Record; browserConfig: BrowserConfig; tagMap: Record; docu: Pick; @@ -226,24 +226,14 @@ export class MainThreadRuntime { __OnLifecycleEvent: (lifeCycleEvent: Cloneable) => void; __LoadLepusChunk: (path: string) => boolean = (path) => { - try { - // @ts-expect-error - if (self.WorkerGlobalScope) { - const lepusChunkUrl = this.config.lepusCode[`${path}`]; - if (lepusChunkUrl) path = lepusChunkUrl; - // @ts-expect-error - importScripts(path); - const entry = (globalThis.module as LynxJSModule).exports; - entry?.(this); - } else { - throw new Error( - 'importing scripts synchronously is only available for the multi-thread running mode', - ); - } + const lepusModule = this.config.lepusCode[`${path}`]; + if (lepusModule) { + const entry = lepusModule.exports; + entry?.(this); return true; - } catch { + } else { + return false; } - return false; }; __FlushElementTree = ( @@ -252,7 +242,7 @@ export class MainThreadRuntime { ) => { const timingFlags = this._timingFlags; this._timingFlags = []; - if (this._page && !this._page.parentElement) { + if (this._page && !this._page.parentNode) { this._rootDom.append(this._page); } this.config.callbacks.flushElementTree(options, timingFlags); diff --git a/packages/web-platform/web-mainthread-apis/src/loadMainThread.ts b/packages/web-platform/web-mainthread-apis/src/loadMainThread.ts index 2f3fbc9446..a1062e2ce6 100644 --- a/packages/web-platform/web-mainthread-apis/src/loadMainThread.ts +++ b/packages/web-platform/web-mainthread-apis/src/loadMainThread.ts @@ -25,6 +25,8 @@ import { switchExposureService, } from './MainThreadRuntime.js'; +const moduleCache: Record = {}; + export function loadMainThread( backgroundThreadRpc: Rpc, docu: Pick, @@ -61,10 +63,23 @@ export function loadMainThread( const { styleInfo, pageConfig, customSections, cardType, lepusCode } = template; markTimingInternal('decode_start'); - await import( - /* webpackIgnore: true */ template.lepusCode.root + const lepusCodeEntries = await Promise.all( + Object.entries(lepusCode).map(async ([name, url]) => { + const cachedModule = moduleCache[name]; + if (cachedModule) { + return [name, cachedModule] as [string, LynxJSModule]; + } else { + Object.assign(globalThis, { module: {} }); + await import(/* webpackIgnore: true */ url); + const module = globalThis.module as LynxJSModule; + Object.assign(globalThis, { module: {} }); + moduleCache[name] = module; + return [name, module] as [string, LynxJSModule]; + } + }), ); - const entry = (globalThis.module as LynxJSModule).exports!; + const lepusCodeLoaded = Object.fromEntries(lepusCodeEntries); + const entry = lepusCodeLoaded['root']!.exports; const jsContext = new LynxCrossThreadContext({ rpc: backgroundThreadRpc, receiveEventEndpoint: dispatchJSContextOnMainThreadEndpoint, @@ -78,7 +93,7 @@ export function loadMainThread( globalProps, pageConfig, styleInfo, - lepusCode, + lepusCode: lepusCodeLoaded, docu, callbacks: { mainChunkReady: () => { diff --git a/packages/web-platform/web-tests/playwright.config.ts b/packages/web-platform/web-tests/playwright.config.ts index 456b5c8187..34c80a9b38 100644 --- a/packages/web-platform/web-tests/playwright.config.ts +++ b/packages/web-platform/web-tests/playwright.config.ts @@ -7,6 +7,7 @@ import { defineConfig, devices } from '@playwright/test'; process.env['LIBGL_ALWAYS_SOFTWARE'] = 'true'; // https://github.com/microsoft/playwright/issues/32151 process.env['GALLIUM_HUD_SCALE'] = '1'; const isCI = !!process.env.CI; +const ALL_ON_UI = !!process.env.ALL_ON_UI; const port = process.env.PORT ?? 3080; const workerLimit = process.env['cpu_limit'] ? Math.floor(parseFloat(process.env['cpu_limit']) / 2) @@ -25,6 +26,7 @@ export default defineConfig({ /** global timeout https://playwright.dev/docs/test-timeouts#global-timeout */ globalTimeout: 20 * 60 * 1000, testDir: './tests', + testMatch: ALL_ON_UI ? '**/{react,web-core}.{test,spec}.ts' : undefined, /* Run tests in files in parallel */ fullyParallel: true, workers: isCI ? workerLimit : undefined, diff --git a/packages/web-platform/web-tests/rspack.config.js b/packages/web-platform/web-tests/rspack.config.js index baa7c793f6..5630b319f5 100644 --- a/packages/web-platform/web-tests/rspack.config.js +++ b/packages/web-platform/web-tests/rspack.config.js @@ -26,6 +26,9 @@ const config = { }, }, plugins: [ + new rspack.DefinePlugin({ + 'process.env.ALL_ON_UI': JSON.stringify(process.env.ALL_ON_UI), + }), new rspack.HtmlRspackPlugin({ title: 'lynx-for-web-test', meta: { diff --git a/packages/web-platform/web-tests/shell-project/index.ts b/packages/web-platform/web-tests/shell-project/index.ts index 35afdcdce5..50bcec71f1 100644 --- a/packages/web-platform/web-tests/shell-project/index.ts +++ b/packages/web-platform/web-tests/shell-project/index.ts @@ -5,6 +5,7 @@ import type { LynxTemplate } from '@lynx-js/web-core'; import { lynxViewTests } from './lynx-view.ts'; +const ALL_ON_UI = !!process.env.ALL_ON_UI; const nativeModulesMap = { CustomModule: URL.createObjectURL( new Blob( @@ -33,6 +34,7 @@ if (casename) { const dir2 = `/dist/${casename2}${hasdir ? `/${casename2}` : ''}`; lynxViewTests(lynxView => { lynxView.setAttribute('url', `${dir}/index.web.json`); + if (ALL_ON_UI) lynxView.setAttribute('thread-strategy', `all-on-ui`); lynxView.nativeModulesMap = nativeModulesMap; lynxView.id = 'lynxview1'; if (casename2) { diff --git a/packages/web-platform/web-tests/shell-project/web-core.ts b/packages/web-platform/web-tests/shell-project/web-core.ts index 2158e01305..6af86c16c3 100644 --- a/packages/web-platform/web-tests/shell-project/web-core.ts +++ b/packages/web-platform/web-tests/shell-project/web-core.ts @@ -8,6 +8,7 @@ import '@lynx-js/web-elements-compat/LinearContainer'; import '@lynx-js/web-core/index.css'; import './index.css'; +const ALL_ON_UI = !!process.env.ALL_ON_UI; const color_environment = URL.createObjectURL( new Blob( [`export default function(NapiModules, NapiModulesCall) { @@ -62,6 +63,7 @@ async function run() { const lepusjs = '/resources/web-core.main-thread.json'; const lynxView = document.createElement('lynx-view') as LynxView; lynxView.setAttribute('url', lepusjs); + if (ALL_ON_UI) lynxView.setAttribute('thread-strategy', `all-on-ui`); lynxView.initData = { mockData: 'mockData' }; lynxView.globalProps = { pink: 'pink' }; lynxView.height = 'auto'; diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index 7d800bb66b..0a3bb4ce3c 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -4,6 +4,7 @@ import { swipe, dragAndHold } from './utils.js'; import { test, expect } from './coverage-fixture.js'; import type { Page } from '@playwright/test'; +const ALL_ON_UI = !!process.env['ALL_ON_UI']; const wait = async (ms: number) => { await new Promise((resolve) => { @@ -577,14 +578,14 @@ test.describe('reactlynx3 tests', () => { await goto(page, title); const target = page.locator('#target'); await expect(target).toHaveCSS('background-color', 'rgb(255, 192, 203)'); // pink - expect(page.workers().length).toStrictEqual(3); + expect(page.workers().length).toStrictEqual(ALL_ON_UI ? 2 : 3); }); test('api-preheat-at-least-one', async ({ page }, { title }) => { await goto(page, title); const target = page.locator('#target'); await expect(target).toHaveCSS('background-color', 'rgb(255, 192, 203)'); // pink - expect(page.workers().length).toBe(3); + expect(page.workers().length).toBe(ALL_ON_UI ? 2 : 3); await page.evaluate(() => { document.body.querySelector('lynx-view')?.remove(); }); @@ -1064,6 +1065,7 @@ test.describe('reactlynx3 tests', () => { test( 'config-splitchunk-single-vendor', async ({ page }, { title }) => { + test.skip(ALL_ON_UI, 'main thread do not support importScript'); await goto(page, title, undefined, true); await wait(1500); const target = page.locator('#target'); @@ -1073,6 +1075,7 @@ test.describe('reactlynx3 tests', () => { test( 'config-splitchunk-split-by-experience', async ({ page }, { title }) => { + test.skip(ALL_ON_UI, 'main thread do not support importScript'); await goto(page, title, undefined, true); await wait(1500); const target = page.locator('#target'); @@ -1082,6 +1085,7 @@ test.describe('reactlynx3 tests', () => { test( 'config-splitchunk-split-by-module', async ({ page }, { title }) => { + test.skip(ALL_ON_UI, 'main thread do not support importScript'); await goto(page, title, undefined, true); await wait(1500); const target = page.locator('#target'); diff --git a/packages/web-platform/web-tests/tests/web-core.test.ts b/packages/web-platform/web-tests/tests/web-core.test.ts index b329abdf7c..72bc6f0f51 100644 --- a/packages/web-platform/web-tests/tests/web-core.test.ts +++ b/packages/web-platform/web-tests/tests/web-core.test.ts @@ -5,6 +5,7 @@ import { test, expect } from './coverage-fixture.js'; import type { Page, Worker } from '@playwright/test'; +const ALL_ON_UI = !!process.env.ALL_ON_UI; const wait = async (ms: number) => { await new Promise((resolve) => { setTimeout(resolve, ms); @@ -18,16 +19,22 @@ const goto = async (page: Page) => { await wait(500); }; -async function getMainThreadWorker(page: Page): Promise { +async function getMainThreadWorker( + page: Page, +): Promise { await wait(100); - for (const i of page.workers()) { - const isActive = await i.evaluate(() => { - return globalThis.runtime !== undefined - && globalThis.__lynx_worker_type === 'main'; - }); + if (ALL_ON_UI) { + return page; + } else { + for (const i of page.workers()) { + const isActive = await i.evaluate(() => { + return globalThis.runtime !== undefined + && globalThis.__lynx_worker_type === 'main'; + }); - if (isActive) { - return i; + if (isActive) { + return i; + } } } } diff --git a/packages/web-platform/web-worker-rpc/src/Rpc.ts b/packages/web-platform/web-worker-rpc/src/Rpc.ts index bd4e2856ae..18cbf08ddc 100644 --- a/packages/web-platform/web-worker-rpc/src/Rpc.ts +++ b/packages/web-platform/web-worker-rpc/src/Rpc.ts @@ -93,7 +93,7 @@ export class Rpc { ) => void = async ( message, ) => { - console.warn(`[rpc] on ${this.name} received ${message.name}`, message); + // console.warn(`[rpc] on ${this.name} received ${message.name}`, message); const handler = this.#handlerMap.get(message.name); if (handler) { const lockViewer = message.sync diff --git a/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts b/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts index 41e03ba381..4918e458d3 100644 --- a/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts +++ b/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts @@ -9,11 +9,11 @@ import { reportErrorEndpoint, } from '@lynx-js/web-constants'; import { Rpc } from '@lynx-js/web-worker-rpc'; -import { loadMainThread } from '@lynx-js/web-mainthread-apis'; import { createMarkTimingInternal } from './crossThreadHandlers/createMainthreadMarkTimingInternal.js'; import { OffscreenDocument } from '@lynx-js/offscreen-document/webworker'; import { _onEvent } from '@lynx-js/offscreen-document/webworker'; import { registerUpdateDataHandler } from './crossThreadHandlers/registerUpdateDataHandler.js'; +const { loadMainThread } = await import('@lynx-js/web-mainthread-apis'); export function startMainThread( uiThreadPort: MessagePort, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b267ce0b2c..8175e09109 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -646,6 +646,9 @@ importers: '@lynx-js/web-constants': specifier: workspace:* version: link:../web-constants + '@lynx-js/web-mainthread-apis': + specifier: workspace:* + version: link:../web-mainthread-apis '@lynx-js/web-worker-rpc': specifier: workspace:* version: link:../web-worker-rpc