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