Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/brown-coats-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@lynx-js/web-mainthread-apis": patch
---

feat(web): use pure DOM API to implement Element PAPIs

1. rewrite all element PAPIs impl. Now we use DOM.
2. use our new package `@lynx-js/offscreen-document` to support the new Element PAPI implementation in a worker
1 change: 1 addition & 0 deletions cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"vitest", // https://github.com/vitest-dev/vitest
"worklet",
"xlarge",
"PAPI", // Lynx's Element PAPI
],
// Glob
"ignorePaths": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import {
OffscreenEvent,
propagationStopped,
} from './OffscreenEvent.js';
import { uniqueId, type OffscreenNode } from './OffscreenNode.js';
import { OffscreenNode, uniqueId } from './OffscreenNode.js';

export const operations = Symbol('operations');
export const enableEvent = Symbol('enableEvent');
export const getElementByUniqueId = Symbol('getElementByUniqueId');
const _onEvent = Symbol('_onEvent');
const _uniqueIdInc = Symbol('uniqueIdInc');
const _uniqueIdToElement = Symbol('_uniqueIdToElement');
export class OffscreenDocument extends EventTarget {
export class OffscreenDocument extends OffscreenNode {
/**
* @private
*/
Expand Down Expand Up @@ -49,7 +49,7 @@ export class OffscreenDocument extends EventTarget {
onCommit: (operations: ElementOperation[]) => void;
},
) {
super();
super(0);
}

commit(): void {
Expand All @@ -58,12 +58,13 @@ export class OffscreenDocument extends EventTarget {
this._callbacks.onCommit(currentOperations);
}

append(element: OffscreenElement) {
override append(element: OffscreenElement) {
this[operations].push({
type: OperationType.Append,
uid: 0,
cid: [element[uniqueId]],
});
super.append(element);
}

createElement(tagName: string): OffscreenElement {
Expand Down
1 change: 1 addition & 0 deletions packages/web-platform/web-constants/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './constants.js';
export * from './eventName.js';
export * from './endpoints.js';
export * from './types/index.js';
export type * from '@lynx-js/web-worker-rpc';
Comment thread
PupilTong marked this conversation as resolved.
2 changes: 1 addition & 1 deletion packages/web-platform/web-mainthread-apis/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@lynx-js/web-mainthread-apis",
"version": "0.9.1",
"private": false,
"private": true,
"description": "",
"keywords": [],
"repository": {
Expand Down
18 changes: 13 additions & 5 deletions packages/web-platform/web-mainthread-apis/src/MainThreadLynx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@ export function createMainThreadLynx(
__globalProps: config.globalProps,

requireModule(path: string) {
const mainfestUrl = config.lepusCode[`/${path}`];
if (mainfestUrl) path = mainfestUrl;
importScripts(path);
const entry = (globalThis.module as LynxJSModule).exports;
return entry?.(lepusRuntime);
// @ts-expect-error
if (self.WorkerGlobalScope) {
const mainfestUrl = config.lepusCode[`/${path}`];
if (mainfestUrl) path = mainfestUrl;
// @ts-expect-error
importScripts(path);
const entry = (globalThis.module as LynxJSModule).exports;
return entry?.(lepusRuntime);
} else {
throw new Error(
'importing scripts synchronously is only available for the multi-thread running mode',
);
}
},
requireModuleAsync(
path: string,
Expand Down
165 changes: 113 additions & 52 deletions packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// LICENSE file in the root directory of this source tree.

import {
type ElementOperation,
type LynxTemplate,
type PageConfig,
type ProcessDataCallback,
Expand All @@ -12,32 +11,42 @@ import {
type Cloneable,
type CssInJsInfo,
type BrowserConfig,
type onLifecycleEventEndpoint,
lynxUniqueIdAttribute,
type publishEventEndpoint,
type publicComponentEventEndpoint,
type reportErrorEndpoint,
type flushElementTreeEndpoint,
type onLifecycleEventEndpoint,
type RpcCallType,
type postExposureEndpoint,
} from '@lynx-js/web-constants';
import { globalMuteableVars } from '@lynx-js/web-constants';
import { createMainThreadLynx, type MainThreadLynx } from './MainThreadLynx.js';
import { initializeElementCreatingFunction } from './elementAPI/elementCreating/elementCreatingFunctions.js';
import * as attributeAndPropertyApis from './elementAPI/attributeAndProperty/attributeAndPropertyFunctions.js';
import { createAttributeAndPropertyFunctions } from './elementAPI/attributeAndProperty/attributeAndPropertyFunctions.js';
import * as domTreeApis from './elementAPI/domTree/domTreeFunctions.js';
import * as eventApis from './elementAPI/event/eventFunctions.js';
import * as styleApis from './elementAPI/style/styleFunctions.js';
import { createEventFunctions } from './elementAPI/event/eventFunctions.js';
import { createStyleFunctions } from './elementAPI/style/styleFunctions.js';
import {
flattenStyleInfo,
genCssContent,
genCssInJsInfo,
transformToWebCss,
} from './utils/processStyleInfo.js';
import { createAttributeAndPropertyFunctionsWithContext } from './elementAPI/attributeAndProperty/createAttributeAndPropertyFunctionsWithContext.js';
import type { RpcCallType } from '../../web-worker-rpc/src/TypeUtils.js';
import type { LynxRuntimeInfo } from './elementAPI/ElementThreadElement.js';
import { createExposureService } from './utils/createExposureService.js';

export interface MainThreadRuntimeCallbacks {
mainChunkReady: () => void;
flushElementTree: RpcCallType<typeof flushElementTreeEndpoint>;
flushElementTree: (
options: FlushElementTreeOptions,
timingFlags: string[],
) => void;
_ReportError: RpcCallType<typeof reportErrorEndpoint>;
__OnLifecycleEvent: RpcCallType<typeof onLifecycleEventEndpoint>;
markTiming: (pipelineId: string, timingKey: string) => void;
publishEvent: RpcCallType<typeof publishEventEndpoint>;
publicComponentEvent: RpcCallType<typeof publicComponentEventEndpoint>;
postExposure: RpcCallType<typeof postExposureEndpoint>;
}

export interface MainThreadConfig {
Expand All @@ -49,55 +58,96 @@ export interface MainThreadConfig {
lepusCode: LynxTemplate['lepusCode'];
browserConfig: BrowserConfig;
tagMap: Record<string, string>;
docu: Pick<Document, 'append' | 'createElement' | 'addEventListener'> & {
commit?: () => void;
};
}

export const elementToRuntimeInfoMap = Symbol('elementToRuntimeInfoMap');
export const getElementByUniqueId = Symbol('getElementByUniqueId');
export const updateCSSInJsStyle = Symbol('updateCSSInJsStyle');
export const lynxUniqueIdToElement = Symbol('lynxUniqueIdToElement');
export const switchExposureService = Symbol('switchExposureService');

export class MainThreadRuntime {
private isFp = true;
/**
* @private
*/
[lynxUniqueIdToElement]: WeakRef<HTMLElement>[] = [];

/**
* @private
*/
[switchExposureService]: (enable: boolean, sendEvent: boolean) => void;
/**
* @private
*/
private _lynxUniqueIdToStyleSheet: WeakRef<HTMLStyleElement>[] = [];
/**
* @private the CreatePage will append it to this
*/
_rootDom: Pick<Element, 'append' | 'addEventListener'>;
/**
* @private
*/
_timingFlags: string[] = [];

public operationsRef: {
operations: ElementOperation[];
} = {
operations: [],
};
/**
* @private
*/
[elementToRuntimeInfoMap]: WeakMap<HTMLElement, LynxRuntimeInfo> =
new WeakMap();

constructor(
private config: MainThreadConfig,
public config: MainThreadConfig,
) {
this.__globalProps = config.globalProps;
this.lynx = createMainThreadLynx(config, this);
/**
* now create the style content
* 1. flatten the styleInfo
* 2. transform the styleInfo to web css
* 3. generate the css in js info
* 4. create the style element
* 5. append the style element to the root dom
*/
flattenStyleInfo(this.config.styleInfo);
transformToWebCss(this.config.styleInfo);
const cssInJs: CssInJsInfo = this.config.pageConfig.enableCSSSelector
const cssInJsInfo: CssInJsInfo = this.config.pageConfig.enableCSSSelector
? {}
: genCssInJsInfo(this.config.styleInfo);
this._rootDom = this.config.docu.createElement('div');
const cardStyleElement = this.config.docu.createElement('style');
cardStyleElement.innerHTML = genCssContent(
this.config.styleInfo,
this.config.pageConfig,
);
this._rootDom = this.config.docu;
this._rootDom.append(cardStyleElement);
/**
* now create Element PAPIs
*/
Object.assign(
this,
createAttributeAndPropertyFunctionsWithContext(this),
attributeAndPropertyApis,
createAttributeAndPropertyFunctions(this),
domTreeApis,
eventApis,
styleApis,
initializeElementCreatingFunction({
operationsRef: this.operationsRef,
pageConfig: config.pageConfig,
styleInfo: cssInJs,
tagMap: config.tagMap,
}),
createEventFunctions(this),
createStyleFunctions(
this,
cssInJsInfo,
),
initializeElementCreatingFunction(this),
);
this.__LoadLepusChunk = (path) => {
try {
this.lynx.requireModule(path);
return true;
} catch {
}
return false;
};
this._ReportError = this.config.callbacks._ReportError;
this.__OnLifecycleEvent = this.config.callbacks.__OnLifecycleEvent;
/**
* Start the exposure service
*/
this[switchExposureService] =
createExposureService(this).switchExposureService;
/**
* to know when the main thread is ready
*/
Object.defineProperty(this, 'renderPage', {
get: () => {
return this.#renderPage;
Expand All @@ -119,6 +169,25 @@ export class MainThreadRuntime {
});
}
}
/**
* @private
*/
[getElementByUniqueId](uniqueId: number): HTMLElement | undefined {
return this[lynxUniqueIdToElement][uniqueId]?.deref();
}

[updateCSSInJsStyle](uniqueId: number, newStyles: string) {
let currentElement = this._lynxUniqueIdToStyleSheet[uniqueId]?.deref();
if (!currentElement) {
currentElement = this.config.docu.createElement(
'style',
) as HTMLStyleElement;
this._lynxUniqueIdToStyleSheet[uniqueId] = new WeakRef(currentElement);
this._rootDom.append(currentElement);
}
currentElement.innerHTML =
`[${lynxUniqueIdAttribute}="${uniqueId}"]{${newStyles}}`;
}

/**
* @private
Expand All @@ -131,8 +200,6 @@ export class MainThreadRuntime {

lynx: MainThreadLynx;

NativeModules = undefined;

__globalProps: unknown;

processData?: ProcessDataCallback;
Expand All @@ -145,28 +212,22 @@ export class MainThreadRuntime {

__OnLifecycleEvent: RpcCallType<typeof onLifecycleEventEndpoint>;

__LoadLepusChunk: (path: string) => boolean;
__LoadLepusChunk: (path: string) => boolean = (path) => {
try {
this.lynx.requireModule(path);
return true;
} catch {
}
return false;
};

__FlushElementTree = (
_subTree: unknown,
options: FlushElementTreeOptions,
) => {
const operations = this.operationsRef.operations;
const timingFlags = this._timingFlags;
this.operationsRef.operations = [];
this._timingFlags = [];
this.config.callbacks.flushElementTree(
operations,
options,
this.isFp
? genCssContent(
this.config.styleInfo,
this.config.pageConfig,
)
: undefined,
timingFlags,
);
this.isFp = false;
this.config.callbacks.flushElementTree(options, timingFlags);
};

updatePage?: (data: Cloneable, options?: Record<string, string>) => void;
Expand Down
Loading
Loading