diff --git a/.changeset/brown-coats-study.md b/.changeset/brown-coats-study.md new file mode 100644 index 0000000000..99f64bdbe2 --- /dev/null +++ b/.changeset/brown-coats-study.md @@ -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 diff --git a/cspell.jsonc b/cspell.jsonc index 602fc904e5..ddee287011 100644 --- a/cspell.jsonc +++ b/cspell.jsonc @@ -108,6 +108,7 @@ "vitest", // https://github.com/vitest-dev/vitest "worklet", "xlarge", + "PAPI", // Lynx's Element PAPI ], // Glob "ignorePaths": [ diff --git a/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts b/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts index 89241283f2..de4df87814 100644 --- a/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts +++ b/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts @@ -11,7 +11,7 @@ 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'); @@ -19,7 +19,7 @@ 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 */ @@ -49,7 +49,7 @@ export class OffscreenDocument extends EventTarget { onCommit: (operations: ElementOperation[]) => void; }, ) { - super(); + super(0); } commit(): void { @@ -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 { diff --git a/packages/web-platform/web-constants/src/index.ts b/packages/web-platform/web-constants/src/index.ts index e193fef6fc..6bbc43847b 100644 --- a/packages/web-platform/web-constants/src/index.ts +++ b/packages/web-platform/web-constants/src/index.ts @@ -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'; diff --git a/packages/web-platform/web-mainthread-apis/package.json b/packages/web-platform/web-mainthread-apis/package.json index 49586733a3..1fcf1a8c44 100644 --- a/packages/web-platform/web-mainthread-apis/package.json +++ b/packages/web-platform/web-mainthread-apis/package.json @@ -1,7 +1,7 @@ { "name": "@lynx-js/web-mainthread-apis", "version": "0.9.1", - "private": false, + "private": true, "description": "", "keywords": [], "repository": { diff --git a/packages/web-platform/web-mainthread-apis/src/MainThreadLynx.ts b/packages/web-platform/web-mainthread-apis/src/MainThreadLynx.ts index a6b4d7f7fb..301972d977 100644 --- a/packages/web-platform/web-mainthread-apis/src/MainThreadLynx.ts +++ b/packages/web-platform/web-mainthread-apis/src/MainThreadLynx.ts @@ -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, diff --git a/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts b/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts index 697ee54365..286deb966a 100644 --- a/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts +++ b/packages/web-platform/web-mainthread-apis/src/MainThreadRuntime.ts @@ -3,7 +3,6 @@ // LICENSE file in the root directory of this source tree. import { - type ElementOperation, type LynxTemplate, type PageConfig, type ProcessDataCallback, @@ -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; + flushElementTree: ( + options: FlushElementTreeOptions, + timingFlags: string[], + ) => void; _ReportError: RpcCallType; __OnLifecycleEvent: RpcCallType; markTiming: (pipelineId: string, timingKey: string) => void; + publishEvent: RpcCallType; + publicComponentEvent: RpcCallType; + postExposure: RpcCallType; } export interface MainThreadConfig { @@ -49,55 +58,96 @@ export interface MainThreadConfig { lepusCode: LynxTemplate['lepusCode']; browserConfig: BrowserConfig; tagMap: Record; + docu: Pick & { + 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[] = []; + /** + * @private + */ + [switchExposureService]: (enable: boolean, sendEvent: boolean) => void; + /** + * @private + */ + private _lynxUniqueIdToStyleSheet: WeakRef[] = []; + /** + * @private the CreatePage will append it to this + */ + _rootDom: Pick; /** * @private */ _timingFlags: string[] = []; - public operationsRef: { - operations: ElementOperation[]; - } = { - operations: [], - }; + /** + * @private + */ + [elementToRuntimeInfoMap]: WeakMap = + 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; @@ -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 @@ -131,8 +200,6 @@ export class MainThreadRuntime { lynx: MainThreadLynx; - NativeModules = undefined; - __globalProps: unknown; processData?: ProcessDataCallback; @@ -145,28 +212,22 @@ export class MainThreadRuntime { __OnLifecycleEvent: RpcCallType; - __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) => void; diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/ElementThreadElement.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/ElementThreadElement.ts index ef7f877153..106364d949 100644 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/ElementThreadElement.ts +++ b/packages/web-platform/web-mainthread-apis/src/elementAPI/ElementThreadElement.ts @@ -1,309 +1,28 @@ // Copyright 2023 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 { - componentIdAttribute, - cssIdAttribute, - OperationType, -} from '@lynx-js/web-constants'; -import type { - ElementOperation, - LynxCrossThreadEvent, - LynxEventType, - PageConfig, - CssInJsInfo, -} from '@lynx-js/web-constants'; - -function getParentIdx( - element: ElementThreadElement, - parent?: ElementThreadElement, -): number { - parent = parent ?? element.parent; - const idx = parent!.children.findIndex((e) => e === element); - if (idx === -1) { - console.error(`[lynx-web]`, element, ` is not a child of`, parent); - throw new Error(`[lynx-web] ${element} is not a child of ${parent}`); - } - return idx; -} - -export enum RefCountType { - Element, -} - -export class ElementThreadElement { - static uniqueIdToElement: (WeakRef | undefined)[] = []; - static receiveEvent(event: LynxCrossThreadEvent) { - const currentTargetUniqueId = event.currentTarget.uniqueId; - const target = this.uniqueIdToElement[currentTargetUniqueId]?.deref(); - if (target) { - const handler = target.eventHandlerMap[event.type]?.handler; - if (typeof handler === 'function') { - queueMicrotask(() => { - handler(event); - }); - } - } else { - this.uniqueIdToElement[currentTargetUniqueId] = undefined; - } - } - static getElementByUniqueId( - uniqueId: number, - ): ElementThreadElement | undefined { - return ElementThreadElement.uniqueIdToElement[uniqueId]?.deref(); - } - public type: RefCountType = RefCountType.Element; - public eventHandlerMap: Record< - string, - { +import type { LynxEventType, Cloneable } from '@lynx-js/web-constants'; + +export interface LynxRuntimeInfo { + uniqueId: number; + componentConfig: Record; + lynxDataset: Record; + eventHandlerMap: Record void) | string; - } | undefined - > = {}; - public attributes: { - id?: string; - [componentIdAttribute]?: string; - style: string | null; - class: string | null; - [cssIdAttribute]: string | null; - [key: string]: string | undefined | null; - }; - public property: { - parent?: ElementThreadElement; - componentConfig: Record; - dataset: Record; - [key: string]: unknown; - } = { - componentConfig: {}, - dataset: {}, - }; - public children: ElementThreadElement[] = []; - public parent?: ElementThreadElement; - // public parentComponentUniqueId!: number; - constructor( - public tag: string, - public uniqueId: number, - public readonly pageConfig: PageConfig, - private operationsRef: { - operations: ElementOperation[]; - }, - public styleInfo: CssInJsInfo, - ) { - this.attributes = { - style: null, - class: '', - [cssIdAttribute]: null, - }; - ElementThreadElement.uniqueIdToElement[this.uniqueId] = new WeakRef(this); - operationsRef.operations.push({ - type: OperationType.Create, - uid: uniqueId, - tag: tag, - }); - } - setProperty(key: string, value: any) { - this.property[key] = value; - if (key === 'dataset') { - this.operationsRef.operations.push({ - uid: this.uniqueId, - type: OperationType.SetProperty, - key: key, - value: value, - }); - } - } - - setDatasetProperty(key: string, value: any) { - this.property.dataset[key] = value; - this.operationsRef.operations.push({ - uid: this.uniqueId, - type: OperationType.SetDatasetProperty, - key, - value, - }); - } - - setAttribute(key: string, value: string | null) { - this.attributes[key] = value; - this.operationsRef.operations.push({ - uid: this.uniqueId, - type: OperationType.SetAttribute, - key, - value, - }); - } - - getAttribute( - key: T, - ): ElementThreadElement['attributes'][T] { - return this.attributes[key]; - } - - appendChild(children: ElementThreadElement[]) { - this.children.push(...children); - for (const kid of children) { - if (kid.parent) { - // note that Node.appendChild() will do `move node` Implicitly. - const idx = getParentIdx(kid); - kid.parent.children.splice(idx, 1); - } - kid.parent = this; - } - this.operationsRef.operations.push({ - uid: this.uniqueId, - type: OperationType.Append, - cid: children.map(e => e.uniqueId), - }); - } - - removeChild(child: ElementThreadElement) { - const idx = getParentIdx(child, this); - this.children.splice(idx, 1); - child.parent = undefined; - this.operationsRef.operations.push({ - type: OperationType.Remove, - uid: this.uniqueId, - cid: [child.uniqueId], - }); - return child; - } - - replaceWithElements(newElements: ElementThreadElement[]) { - for (const kid of newElements) { - if (this.parent === kid) { - console.error( - `[lynx-web] cannot replace the element`, - this, - `by its parent`, - kid, - ); - throw new Error(`[lynx-web] cannot replace `); - } - } - const parent = this.parent; - if (parent) { - const currentPosition = getParentIdx(this); - parent.children.splice(currentPosition, 1); - this.parent = undefined; - for (const kid of newElements) { - if (kid.parent) { - const idx = getParentIdx(kid); - kid.parent.children.splice(idx, 1); - } - kid.parent = parent; - } - parent.children.splice(currentPosition, 0, ...newElements); - this.operationsRef.operations.push({ - type: OperationType.Replace, - uid: this.uniqueId, - nid: newElements.map(e => e.uniqueId), - }); - } - } - - swapWith(elementB: ElementThreadElement) { - const parentA = this.parent!; - const parentB = elementB.parent!; - const idxA = getParentIdx(this); - const idxB = getParentIdx(elementB); - parentA.children[idxA] = elementB; - elementB.parent = parentA; - parentB.children[idxB] = this; - this.parent = parentB; - this.operationsRef.operations.push({ - type: OperationType.SwapElement, - uid: this.uniqueId, - tid: elementB.uniqueId, - }); - } - - insertBefore(child: ElementThreadElement, ref?: ElementThreadElement | null) { - if (ref) { - const idx = getParentIdx(ref, this); - this.children.splice(idx, 0, child); - child.parent = this; - this.operationsRef.operations.push({ - type: OperationType.InsertBefore, - uid: this.uniqueId, - cid: child.uniqueId, - ref: ref.uniqueId, - }); - } else { - this.children.push(child); - child.parent = this; - this.operationsRef.operations.push({ - type: OperationType.Append, - uid: this.uniqueId, - cid: [child.uniqueId], - }); - } - return child; - } - - updateCssInJsGeneratedStyle(classStyleStr: string) { - this.operationsRef.operations.push({ - type: OperationType.UpdateCssInJs, - uid: this.uniqueId, - classStyleStr, - }); - } - - setStyleProperty(key: string, value: string | null, important?: boolean) { - this.attributes.style = (this.attributes.style ?? '') - + `${key}:${value ?? ''}${important ? '!important' : ''};`; - this.operationsRef.operations.push({ - type: OperationType.SetStyleProperty, - uid: this.uniqueId, - key, - value, - im: important, - }); - } - - setEventHandler( - ename: string, - handler: ((ev: LynxCrossThreadEvent) => void) | string | undefined, - eventType: LynxEventType, - ) { - let hname: string | undefined | null; - if (handler) { - this.eventHandlerMap[ename] = { type: eventType, handler }; - if (typeof handler === 'function') { - hname = null; - } else { - hname = handler; - } - } else { - this.eventHandlerMap[ename] = undefined; - } - - this.operationsRef.operations.push({ - type: OperationType.RegisterEventHandler, - uid: this.uniqueId, - eventType, - hname, - ename, - }); - } - - get firstElementChild() { - return this.children[0]; - } - get lastElementChild(): ElementThreadElement | undefined { - const childLength = this.children.length; - return childLength > 0 ? this.children[childLength - 1] : undefined; - } - get nextElementSibling(): ElementThreadElement | undefined { - if (this.parent) { - const idx = getParentIdx(this); - return this.parent.children[idx + 1]; - } - return; - } + handler: string; + } | undefined; + bind: { + type: LynxEventType; + handler: string; + } | undefined; + }>; + componentAtIndex?: ComponentAtIndexCallback; + enqueueComponent?: EnqueueComponentCallback; } export type ComponentAtIndexCallback = ( - list: ListElement, + list: HTMLElement, listID: number, cellIndex: number, operationID: number, @@ -311,49 +30,7 @@ export type ComponentAtIndexCallback = ( ) => void; export type EnqueueComponentCallback = ( - list: ListElement, + list: HTMLElement, listID: number, sign: number, ) => void; - -type UpdateListInfoAttributeValue = { - insertAction: { - position: number; - }[]; - removeAction: { - position: number; - }[]; -}; - -export class ListElement extends ElementThreadElement { - componentAtIndex!: ComponentAtIndexCallback; - enqueueComponent!: EnqueueComponentCallback; - // _positionFiredComponentAtIndex = new Set(); - override setAttribute( - key: 'update-list-info', - value: UpdateListInfoAttributeValue, - ): void; - override setAttribute( - key: Exclude, - value: string | null, - ): void; - override setAttribute( - key: string, - value: string | null | UpdateListInfoAttributeValue, - ): void { - if (key === 'update-list-info' && value) { - const listInfo = value as UpdateListInfoAttributeValue; - const { insertAction, removeAction } = listInfo; - queueMicrotask(() => { - for (const action of insertAction) { - this.componentAtIndex(this, this.uniqueId, action.position, 0, false); - } - for (const action of removeAction) { - this.enqueueComponent(this, this.uniqueId, action.position); - } - }); - value = value.toString(); - } - super.setAttribute(key, value as string | null); - } -} diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/attributeAndProperty/attributeAndPropertyFunctions.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/attributeAndProperty/attributeAndPropertyFunctions.ts index 5087cce92c..3b488dcaa2 100644 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/attributeAndProperty/attributeAndPropertyFunctions.ts +++ b/packages/web-platform/web-mainthread-apis/src/elementAPI/attributeAndProperty/attributeAndPropertyFunctions.ts @@ -2,114 +2,158 @@ // 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 { componentIdAttribute, lynxTagAttribute } from '@lynx-js/web-constants'; import { - type ElementThreadElement, + __lynx_timing_flag, + componentIdAttribute, + lynxTagAttribute, +} from '@lynx-js/web-constants'; +import { type ComponentAtIndexCallback, type EnqueueComponentCallback, - type ListElement, - RefCountType, } from '../ElementThreadElement.js'; +import { + elementToRuntimeInfoMap, + type MainThreadRuntime, +} from '../../MainThreadRuntime.js'; -export function __AddConfig( - element: ElementThreadElement, - type: string, - value: any, +export function createAttributeAndPropertyFunctions( + runtime: MainThreadRuntime, ) { - element.property.componentConfig[type] = value; -} - -export function __AddDataset( - element: ElementThreadElement, - key: string, - value: string | number | Record, -): void { - element.setDatasetProperty(key, value); -} + function __AddConfig( + element: HTMLElement, + type: string, + value: any, + ) { + runtime[elementToRuntimeInfoMap].get(element)!.componentConfig[type] = + value; + } -export function __GetAttributes(element: ElementThreadElement) { - return element.attributes; -} + function __AddDataset( + element: HTMLElement, + key: string, + value: string | number | Record, + ): void { + runtime[elementToRuntimeInfoMap].get(element)!.lynxDataset[key] = value; + } -export function __GetComponentID(element: ElementThreadElement) { - return element.attributes[componentIdAttribute]; -} + function __GetAttributes( + element: HTMLElement, + ): Record { + return Object.fromEntries( + element.getAttributeNames().map(( + attributeName, + ) => [attributeName, element.getAttribute(attributeName)]), + ); + } -export function __GetDataByKey( - element: ElementThreadElement, - key: string, -) { - return element.property.dataset[key]; -} + function __GetComponentID(element: HTMLElement): string | null { + return element.getAttribute(componentIdAttribute); + } -export function __GetDataset( - element: ElementThreadElement, -): Record { - return element.property.dataset; -} + function __GetDataByKey( + element: HTMLElement, + key: string, + ) { + return runtime[elementToRuntimeInfoMap].get(element)!.lynxDataset[key]; + } -export function __GetElementConfig( - element: ElementThreadElement, -) { - return element.property.componentConfig; -} + function __GetDataset( + element: HTMLElement, + ): Record { + return runtime[elementToRuntimeInfoMap].get(element)!.lynxDataset; + } -export function __GetElementUniqueID( - element: ElementThreadElement | unknown, -): number { - if ( - element && typeof element === 'object' - && (element as ElementThreadElement).type === RefCountType.Element + function __GetElementConfig( + element: HTMLElement, ) { - return (element as ElementThreadElement).uniqueId; + return runtime[elementToRuntimeInfoMap].get(element)!.componentConfig; } - return -1; -} -export function __GetID(element: ElementThreadElement): string { - return element.attributes.id ?? ''; -} + function __GetElementUniqueID( + element: HTMLElement, + ): number { + return runtime[elementToRuntimeInfoMap].get(element)?.uniqueId ?? -1; + } -export function __GetTag(element: ElementThreadElement): string { - return element.getAttribute(lynxTagAttribute)!; -} + function __GetID(element: HTMLElement): string { + return element.id; + } -export function __SetConfig( - element: ElementThreadElement, - config: Record, -): void { - element.property.componentConfig = config; -} + function __GetTag(element: HTMLElement): string { + return element.getAttribute(lynxTagAttribute)!; + } -export function __SetDataset( - element: ElementThreadElement, - dataset: Record, -): void { - element.setProperty('dataset', dataset); -} + function __SetConfig( + element: HTMLElement, + config: Record, + ): void { + runtime[elementToRuntimeInfoMap].get(element)!.componentConfig = config; + } -export function __SetID(element: ElementThreadElement, id: string) { - element.setAttribute('id', id); -} + function __SetDataset( + element: HTMLElement, + dataset: Record, + ): void { + runtime[elementToRuntimeInfoMap].get(element)!.lynxDataset = dataset; + } -export function __UpdateComponentID( - element: ElementThreadElement, - componentID: string, -) { - element.setAttribute(componentIdAttribute, componentID); -} + function __SetID(element: HTMLElement, id: string) { + element.id = id; + } -export function __GetConfig( - element: ElementThreadElement, -) { - return element.property.componentConfig; -} + function __UpdateComponentID( + element: HTMLElement, + componentID: string, + ) { + element.setAttribute(componentIdAttribute, componentID); + } -export function __UpdateListCallbacks( - list: ListElement, - componentAtIndex: ComponentAtIndexCallback, - enqueueComponent: EnqueueComponentCallback, -) { - list.componentAtIndex = componentAtIndex; - list.enqueueComponent = enqueueComponent; + function __GetConfig( + element: HTMLElement, + ) { + return runtime[elementToRuntimeInfoMap].get(element)!.componentConfig; + } + + function __UpdateListCallbacks( + element: HTMLElement, + componentAtIndex: ComponentAtIndexCallback, + enqueueComponent: EnqueueComponentCallback, + ) { + runtime[elementToRuntimeInfoMap].get(element)!.componentAtIndex = + componentAtIndex; + runtime[elementToRuntimeInfoMap].get(element)!.enqueueComponent = + enqueueComponent; + } + + function __SetAttribute( + element: HTMLElement, + key: string, + value: string | null | undefined, + ): void { + if (value) element.setAttribute(key, value); + else element.removeAttribute(key); + if (key === __lynx_timing_flag && value) { + runtime._timingFlags.push(value); + } + } + + return { + __AddConfig, + __AddDataset, + __GetAttributes, + __GetComponentID, + __GetDataByKey, + __GetDataset, + __GetElementConfig, + __GetElementUniqueID, + __GetID, + __GetTag, + __SetConfig, + __SetDataset, + __SetID, + __UpdateComponentID, + __UpdateListCallbacks, + __GetConfig, + __SetAttribute, + }; } diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/attributeAndProperty/createAttributeAndPropertyFunctionsWithContext.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/attributeAndProperty/createAttributeAndPropertyFunctionsWithContext.ts deleted file mode 100644 index 67434ad734..0000000000 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/attributeAndProperty/createAttributeAndPropertyFunctionsWithContext.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { __lynx_timing_flag } from '@lynx-js/web-constants'; -import type { MainThreadRuntime } from '../../MainThreadRuntime.js'; -import type { ElementThreadElement } from '../ElementThreadElement.js'; - -export function createAttributeAndPropertyFunctionsWithContext( - runtime: MainThreadRuntime, -) { - function __SetAttribute( - element: ElementThreadElement, - key: string, - value: string | null | undefined, - ): void { - element.setAttribute(key, value ?? null); - if (key === __lynx_timing_flag && value) { - runtime._timingFlags.push(value); - } - } - - return { - __SetAttribute, - }; -} diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/createOffscreenDocument.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/createOffscreenDocument.ts deleted file mode 100644 index ace4e94d0e..0000000000 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/createOffscreenDocument.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { - CssInJsInfo, - ElementOperation, - PageConfig, -} from '@lynx-js/web-constants'; -import { ListElement, ElementThreadElement } from './ElementThreadElement.js'; - -interface OffscreenDocument { - createElement(tagName: string): ElementThreadElement; -} - -export function createOffscreenDocument(options: { - pageConfig: PageConfig; - styleInfo: CssInJsInfo; - operationsRef: { - operations: ElementOperation[]; - }; -}): OffscreenDocument { - const { pageConfig, styleInfo, operationsRef } = options; - let incrementalUniqueId = 0; - function createElement(tagName: string): ElementThreadElement { - const uniqueId = incrementalUniqueId++; - if (tagName === 'x-list') { - return new ListElement( - tagName, - uniqueId, - pageConfig, - operationsRef, - styleInfo, - ); - } else { - return new ElementThreadElement( - tagName, - uniqueId, - pageConfig, - operationsRef, - styleInfo, - ); - } - } - return { - createElement, - }; -} diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/domTree/domTreeFunctions.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/domTree/domTreeFunctions.ts index 4a23fcddaf..c2379a4f68 100644 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/domTree/domTreeFunctions.ts +++ b/packages/web-platform/web-mainthread-apis/src/elementAPI/domTree/domTreeFunctions.ts @@ -2,98 +2,89 @@ // 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 { ElementThreadElement } from '../ElementThreadElement.js'; - export function __AppendElement( - parent: ElementThreadElement, - child: ElementThreadElement, -) { - parent.appendChild([child]); + parent: HTMLElement, + child: HTMLElement, +): void { + parent.append(child); } export function __ElementIsEqual( - left: ElementThreadElement, - right: ElementThreadElement, + left: HTMLElement, + right: HTMLElement, ): boolean { return left === right; } export function __FirstElement( - element: ElementThreadElement, -): ElementThreadElement | undefined { - return element.firstElementChild; + element: HTMLElement, +): HTMLElement | undefined { + return element.firstElementChild as HTMLElement || undefined; } export function __GetChildren( - element: ElementThreadElement, -): ElementThreadElement[] { - return element.children; + element: HTMLElement, +): HTMLElement[] { + return element.children as unknown as HTMLElement[]; } export function __GetParent( - element: ElementThreadElement, -): ElementThreadElement | undefined { - return element.parent; + element: HTMLElement, +): HTMLElement | undefined { + return element.parentElement as HTMLElement || undefined; } export function __InsertElementBefore( - parent: ElementThreadElement, - child: ElementThreadElement, - ref: ElementThreadElement | null, -): ElementThreadElement { - return parent.insertBefore(child, ref); + parent: HTMLElement, + child: HTMLElement, + ref: HTMLElement | null, +): HTMLElement { + return parent.insertBefore(child, ref) as HTMLElement; } export function __LastElement( - element: ElementThreadElement, -): ElementThreadElement | undefined { - return element.lastElementChild; + element: HTMLElement, +): HTMLElement | undefined { + return element.lastElementChild as HTMLElement || undefined; } export function __NextElement( - element: ElementThreadElement, -): ElementThreadElement | undefined { - return element.nextElementSibling; + element: HTMLElement, +): HTMLElement | undefined { + return element.nextElementSibling as HTMLElement || undefined; } export function __RemoveElement( - parent: ElementThreadElement, - child: ElementThreadElement, -): ElementThreadElement { + parent: HTMLElement, + child: HTMLElement, +): HTMLElement { parent.removeChild(child); return child; } export function __ReplaceElement( - newElement: ElementThreadElement, - oldElement: ElementThreadElement, + newElement: HTMLElement, + oldElement: HTMLElement, ) { - oldElement.replaceWithElements([newElement]); + oldElement.replaceWith(newElement); } export function __ReplaceElements( - parent: ElementThreadElement, - newChildren: ElementThreadElement[] | ElementThreadElement, - oldChildren: ElementThreadElement[] | ElementThreadElement | null | undefined, + parent: HTMLElement, + newChildren: HTMLElement[] | HTMLElement, + oldChildren: HTMLElement[] | HTMLElement | null | undefined, ) { newChildren = Array.isArray(newChildren) ? newChildren : [newChildren]; if ( !oldChildren || (Array.isArray(oldChildren) && oldChildren?.length === 0) ) { - parent.appendChild(newChildren); + parent.append(...newChildren); } else { oldChildren = Array.isArray(oldChildren) ? oldChildren : [oldChildren]; for (let ii = 1; ii < oldChildren.length; ii++) { __RemoveElement(parent, oldChildren[ii]!); } const firstOldChildren = oldChildren[0]!; - firstOldChildren.replaceWithElements(newChildren); + firstOldChildren.replaceWith(...newChildren); } } - -export function __SwapElement( - childA: ElementThreadElement, - childB: ElementThreadElement, -): void { - childA.swapWith(childB); -} diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/elementCreating/elementCreatingFunctions.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/elementCreating/elementCreatingFunctions.ts index 932c732bbc..49b6820321 100644 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/elementCreating/elementCreatingFunctions.ts +++ b/packages/web-platform/web-mainthread-apis/src/elementAPI/elementCreating/elementCreatingFunctions.ts @@ -2,55 +2,29 @@ // 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 PageConfig, - type ElementOperation, cssIdAttribute, - type CssInJsInfo, + lynxUniqueIdAttribute, parentComponentUniqueIdAttribute, lynxTagAttribute, + lynxDefaultDisplayLinearAttribute, } from '@lynx-js/web-constants'; -import { __UpdateComponentID } from '../attributeAndProperty/attributeAndPropertyFunctions.js'; import { type ComponentAtIndexCallback, type EnqueueComponentCallback, - ListElement, - ElementThreadElement, + type LynxRuntimeInfo, } from '../ElementThreadElement.js'; -import { __SetCSSId } from '../style/styleFunctions.js'; -import { createOffscreenDocument } from '../createOffscreenDocument.js'; - -export interface initializeElementCreatingFunctionConfig { - operationsRef: { - operations: ElementOperation[]; - }; - pageConfig: PageConfig; - styleInfo: CssInJsInfo; - tagMap: Record; -} +import { + elementToRuntimeInfoMap, + getElementByUniqueId, + lynxUniqueIdToElement, + type MainThreadRuntime, +} from '../../MainThreadRuntime.js'; +import type { createStyleFunctions } from '../style/styleFunctions.js'; export function initializeElementCreatingFunction( - config: initializeElementCreatingFunctionConfig, + runtime: MainThreadRuntime, ) { - const { operationsRef, pageConfig, styleInfo, tagMap } = config; - const document = createOffscreenDocument({ - pageConfig, - operationsRef, - styleInfo, - }); - function createLynxElement( - tag: Exclude, - parentComponentUniqueId: number, - cssId?: number, - componentId?: string, - info?: Record | null | undefined, - ): ElementThreadElement; - function createLynxElement( - tag: 'list', - parentComponentUniqueId: number, - cssId?: number, - componentId?: string, - info?: Record | null | undefined, - ): ListElement; + let uniqueIdInc = 1; function createLynxElement( tag: string, parentComponentUniqueId: number, @@ -59,17 +33,32 @@ export function initializeElementCreatingFunction( // @ts-expect-error info?: Record | null | undefined, ) { - const htmlTag = tagMap[tag] ?? tag; - const element = document.createElement(htmlTag); + // @ts-expect-error + const __SetCSSId = runtime.__SetCSSId as ReturnType< + typeof createStyleFunctions + >['__SetCSSId']; + const htmlTag = runtime.config.tagMap[tag] ?? tag; + const element = runtime.config.docu.createElement( + htmlTag, + ) as HTMLElement; element.setAttribute(lynxTagAttribute, tag); - // element.parentComponentUniqueId = parentComponentUniqueId; + const uniqueId = uniqueIdInc++; + const runtimeInfo: LynxRuntimeInfo = { + uniqueId, + componentConfig: {}, + lynxDataset: {}, + eventHandlerMap: {}, + }; + runtime[elementToRuntimeInfoMap].set(element, runtimeInfo); + runtime[lynxUniqueIdToElement][uniqueId] = new WeakRef(element); + element.setAttribute(lynxUniqueIdAttribute, uniqueId.toString()); element.setAttribute( parentComponentUniqueIdAttribute, parentComponentUniqueId.toString(), ); if (cssId !== undefined) __SetCSSId([element], cssId); else if (parentComponentUniqueId >= 0) { // don't infer for uniqueid === -1 - const parentComponent = ElementThreadElement.getElementByUniqueId( + const parentComponent = runtime[getElementByUniqueId]( parentComponentUniqueId, ); const parentCssId = parentComponent?.getAttribute(cssIdAttribute); @@ -78,7 +67,8 @@ export function initializeElementCreatingFunction( } } if (componentId !== undefined) { - __UpdateComponentID(element, componentId); + // @ts-expect-error + runtime.__UpdateComponentID(element, componentId); } return element; } @@ -110,7 +100,7 @@ export function initializeElementCreatingFunction( tagName: string, parentComponentUniqueId: number, info?: object, - ): ElementThreadElement { + ): HTMLElement { return createLynxElement( tagName, parentComponentUniqueId, @@ -129,8 +119,12 @@ export function initializeElementCreatingFunction( page.setAttribute('part', 'page'); page.setAttribute( parentComponentUniqueIdAttribute, - page.uniqueId.toString(), + runtime[elementToRuntimeInfoMap].get(page)!.uniqueId.toString(), ); + if (runtime.config.pageConfig.defaultDisplayLinear === false) { + page.setAttribute(lynxDefaultDisplayLinearAttribute, 'false'); + } + runtime._rootDom.append(page); return page; } @@ -169,18 +163,28 @@ export function initializeElementCreatingFunction( componentAtIndex: ComponentAtIndexCallback, enqueueComponent: EnqueueComponentCallback, info?: any, - ): ListElement { + ): HTMLElement { const element = createLynxElement( 'list', parentComponentUniqueId, undefined, undefined, info, - ) as ListElement; - element.componentAtIndex = componentAtIndex; - element.enqueueComponent = enqueueComponent; + ) as HTMLElement; + const runtimeInfo = runtime[elementToRuntimeInfoMap].get(element)!; + runtimeInfo.componentAtIndex = componentAtIndex; + runtimeInfo.enqueueComponent = enqueueComponent; return element; } + function __SwapElement( + childA: HTMLElement, + childB: HTMLElement, + ): void { + const temp = runtime.config.docu.createElement('div'); + childA.replaceWith(temp); + childB.replaceWith(childA); + temp.replaceWith(childB); + } return { __CreateView, @@ -193,5 +197,6 @@ export function initializeElementCreatingFunction( __CreateElement, __CreateWrapperElement, __CreateList, + __SwapElement, }; } diff --git a/packages/web-platform/web-mainthread-apis/src/elementAPI/event/eventFunctions.ts b/packages/web-platform/web-mainthread-apis/src/elementAPI/event/eventFunctions.ts index 229959d08c..f4e88e20f3 100644 --- a/packages/web-platform/web-mainthread-apis/src/elementAPI/event/eventFunctions.ts +++ b/packages/web-platform/web-mainthread-apis/src/elementAPI/event/eventFunctions.ts @@ -1,69 +1,186 @@ // Copyright 2023 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 { - LynxCrossThreadEvent, - LynxEventType, +import { + componentIdAttribute, + LynxEventNameToW3cByTagName, + LynxEventNameToW3cCommon, + lynxTagAttribute, + parentComponentUniqueIdAttribute, + W3cEventNameToLynx, + type LynxCrossThreadEvent, + type LynxEventType, } from '@lynx-js/web-constants'; -import type { ElementThreadElement } from '../ElementThreadElement.js'; +import { + elementToRuntimeInfoMap, + getElementByUniqueId, + type MainThreadRuntime, +} from '../../MainThreadRuntime.js'; +import { createCrossThreadEvent } from '../../utils/createCrossThreadEvent.js'; -export function __AddEvent( - element: ElementThreadElement, - eventType: LynxEventType, - eventName: string, - eventHandler: string | ((ev: LynxCrossThreadEvent) => void) | undefined, -) { - element.setEventHandler(eventName, eventHandler, eventType); -} +export function createEventFunctions(runtime: MainThreadRuntime) { + const btsHandler = (event: Event) => { + if (!event.currentTarget) { + return; + } + const currentTarget = event.currentTarget as HTMLElement; + const isCapture = event.eventPhase === Event.CAPTURING_PHASE; + const lynxEventName = W3cEventNameToLynx[event.type] ?? event.type; + const runtimeInfo = runtime[elementToRuntimeInfoMap].get(currentTarget)!; + const hname = isCapture + ? runtimeInfo.eventHandlerMap[lynxEventName]?.capture + ?.handler + : runtimeInfo.eventHandlerMap[lynxEventName]?.bind + ?.handler; + if (hname) { + const crossThreadEvent = createCrossThreadEvent(runtime, event); + const parentComponentUniqueId = currentTarget.getAttribute( + parentComponentUniqueIdAttribute, + )!; + const parentComponent = runtime[getElementByUniqueId]( + Number(parentComponentUniqueId), + )!; + const componentId = + parentComponent?.getAttribute(lynxTagAttribute) !== 'page' + ? parentComponent?.getAttribute(componentIdAttribute) ?? undefined + : undefined; + if (componentId) { + runtime.config.callbacks.publicComponentEvent( + hname, + componentId, + crossThreadEvent, + ); + } else { + runtime.config.callbacks.publishEvent( + hname, + crossThreadEvent, + ); + } + } + }; + const btsCatchHandler = (event: Event) => { + btsHandler(event); + event.stopPropagation(); + }; + function __AddEvent( + element: HTMLElement, + eventType: LynxEventType, + eventName: string, + newEventHandler: string | undefined // | ((ev: LynxCrossThreadEvent) => void) | undefined, + , + ) { + eventName = eventName.toLowerCase(); + const isCatch = eventType === 'catchEvent' || eventType === 'capture-catch'; + const isCapture = eventType.startsWith('capture'); + const runtimeInfo = runtime[elementToRuntimeInfoMap].get(element)!; + const currentHandler = isCapture + ? runtimeInfo.eventHandlerMap[eventName]?.capture + : runtimeInfo.eventHandlerMap[eventName]?.bind; + const currentRegisteredHandler = isCatch ? btsCatchHandler : btsHandler; + if (currentHandler) { + if (!newEventHandler) { + /** + * remove handler + */ + element.removeEventListener(eventName, currentRegisteredHandler, { + capture: isCapture, + }); + } + } else { + /** + * append new handler + */ + if (newEventHandler) { + const htmlEventName = + LynxEventNameToW3cByTagName[element.tagName]?.[eventName] + ?? LynxEventNameToW3cCommon[eventName] ?? eventName; + element.addEventListener(htmlEventName, currentRegisteredHandler, { + capture: isCapture, + }); + } + } + if (newEventHandler) { + const info = { + type: eventType, + handler: newEventHandler, + }; + if (!runtimeInfo.eventHandlerMap[eventName]) { + runtimeInfo.eventHandlerMap[eventName] = { + capture: undefined, + bind: undefined, + }; + } + if (isCapture) { + runtimeInfo.eventHandlerMap[eventName]!.capture = info; + } else { + runtimeInfo.eventHandlerMap[eventName]!.bind = info; + } + } + } -export function __GetEvent( - element: ElementThreadElement, - eventName: string, - eventType: string, -): string | ((ev: LynxCrossThreadEvent) => void) | undefined { - const lynxEventName = eventName.toLowerCase(); - const eventHandlerMap = element.eventHandlerMap; - const currentHandlerInfo = eventHandlerMap[lynxEventName]; - if (currentHandlerInfo?.type === eventType) { - return currentHandlerInfo.handler; + function __GetEvent( + element: HTMLElement, + eventName: string, + eventType: LynxEventType, + ): string | ((ev: LynxCrossThreadEvent) => void) | undefined { + const runtimeInfo = runtime[elementToRuntimeInfoMap].get(element)!; + eventName = eventName.toLowerCase(); + const isCapture = eventType.startsWith('capture'); + const handler = isCapture + ? runtimeInfo.eventHandlerMap[eventName]?.capture + : runtimeInfo.eventHandlerMap[eventName]?.bind; + return handler?.handler; } - return; -} -export function __GetEvents(element: ElementThreadElement): { - type: LynxEventType; - name: string; - function: string | ((ev: Event) => void) | undefined; -}[] { - const eventHandlerMap = element.eventHandlerMap; - return Object.entries(eventHandlerMap).map(([lynxEventName, info]) => { - if (info) { - return { - type: info.type, - function: info.handler, - name: lynxEventName, - }; - } - return; - }).filter(e => e!) as { + function __GetEvents(element: HTMLElement): { type: LynxEventType; name: string; function: string | ((ev: Event) => void) | undefined; - }[]; -} + }[] { + const eventHandlerMap = + runtime[elementToRuntimeInfoMap].get(element)!.eventHandlerMap; + const eventInfos: { + type: LynxEventType; + name: string; + function: string | ((ev: Event) => void) | undefined; + }[] = []; + for (const [lynxEventName, info] of Object.entries(eventHandlerMap)) { + for (const atomInfo of [info.bind, info.capture]) { + if (atomInfo) { + for (const [type, handler] of Object.values(atomInfo)) { + if (handler) { + eventInfos.push({ + type: type as LynxEventType, + name: lynxEventName, + function: handler, + }); + } + } + } + } + } + return eventInfos; + } -export function __SetEvents( - element: ElementThreadElement, - listeners: { - type: LynxEventType; - name: string; - function: string | ((ev: LynxCrossThreadEvent) => void) | undefined; - }[], -) { - for ( - const { type: eventType, name: lynxEventName, function: eventHandler } - of listeners + function __SetEvents( + element: HTMLElement, + listeners: { + type: LynxEventType; + name: string; + function: string | undefined; // ((ev: LynxCrossThreadEvent) => void) | undefined; + }[], ) { - __AddEvent(element, eventType, lynxEventName, eventHandler); + for ( + const { type: eventType, name: lynxEventName, function: eventHandler } + of listeners + ) { + __AddEvent(element, eventType, lynxEventName, eventHandler); + } } + return { + __AddEvent, + __GetEvent, + __GetEvents, + __SetEvents, + }; } 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 2359bfb90e..0fcefe891b 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 @@ -1,8 +1,7 @@ // Copyright 2023 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 { ElementThreadElement } from '../ElementThreadElement.js'; -import { cssIdAttribute } from '@lynx-js/web-constants'; +import { cssIdAttribute, type CssInJsInfo } from '@lynx-js/web-constants'; import hyphenateStyleName from 'hyphenate-style-name'; import { queryCSSProperty } from './cssPropertyMap.js'; import { decodeCssInJs } from '../../utils/decodeCssInJs.js'; @@ -10,100 +9,116 @@ import { transformInlineStyleString, transfromParsedStyles, } from './transformInlineStyle.js'; +import { + elementToRuntimeInfoMap, + updateCSSInJsStyle, + type MainThreadRuntime, +} from '../../MainThreadRuntime.js'; -function updateInlineStyleForCssInJs( - element: ElementThreadElement, - newClassNames: string, -) { - const classStyleStr = decodeCssInJs( - newClassNames, - element.styleInfo!, - element.attributes[cssIdAttribute], - ); - element.updateCssInJsGeneratedStyle(classStyleStr); -} - -export function __AddClass( - element: ElementThreadElement, - className: string, +export function createStyleFunctions( + runtime: MainThreadRuntime, + cssInJsInfo: CssInJsInfo, ) { - const newClassName = ((element.attributes.class ?? '') + ' ' + className) - .trim(); - element.setAttribute('class', newClassName); - if (!element.pageConfig.enableCSSSelector) { - updateInlineStyleForCssInJs( - element, - newClassName, - ); + function __AddClass( + element: HTMLElement, + className: string, + ) { + const newClassName = ((element.className ?? '') + ' ' + className) + .trim(); + element.setAttribute('class', newClassName); + if (!runtime.config.pageConfig.enableCSSSelector) { + const newStyleStr = decodeCssInJs( + newClassName, + cssInJsInfo, + element.getAttribute(cssIdAttribute), + ); + runtime[updateCSSInJsStyle]( + runtime[elementToRuntimeInfoMap].get(element)!.uniqueId, + newStyleStr, + ); + } } -} - -export function __SetClasses( - element: ElementThreadElement, - classNames: string | null, -): void { - element.setAttribute('class', classNames); - if (!element.pageConfig.enableCSSSelector) { - updateInlineStyleForCssInJs( - element, - classNames ?? '', - ); + function __SetClasses( + element: HTMLElement, + classNames: string | null, + ): void { + classNames + ? element.setAttribute('class', classNames) + : element.removeAttribute('class'); + if (!runtime.config.pageConfig.enableCSSSelector) { + const newStyleStr = decodeCssInJs( + classNames ?? '', + cssInJsInfo, + element.getAttribute(cssIdAttribute), + ); + runtime[updateCSSInJsStyle]( + runtime[elementToRuntimeInfoMap].get(element)!.uniqueId, + newStyleStr ?? '', + ); + } } -} -export function __GetClasses(element: ElementThreadElement) { - return (element.attributes.class ?? '').split(' ').filter(e => e); -} - -export function __AddInlineStyle( - element: ElementThreadElement, - key: number | string, - value: string | undefined, -): void { - const lynxStyleInfo = queryCSSProperty(Number(key)); - if (!value) { - element.setStyleProperty(lynxStyleInfo.dashName, null); - return; + function __GetClasses(element: HTMLElement) { + return (element.className ?? '').split(' ').filter(e => e); } - const { transformedStyle } = transfromParsedStyles([[ - lynxStyleInfo.dashName, - value, - ]]); - for (const [property, value] of transformedStyle) { - element.setStyleProperty(property, value); + function __AddInlineStyle( + element: HTMLElement, + key: number | string, + value: string | undefined, + ): void { + const lynxStyleInfo = queryCSSProperty(Number(key)); + if (!value) { + element.style.setProperty(lynxStyleInfo.dashName, null); + return; + } + const { transformedStyle } = transfromParsedStyles([[ + lynxStyleInfo.dashName, + value, + ]]); + for (const [property, value] of transformedStyle) { + element.style.setProperty(property, value); + } } -} -export function __SetInlineStyles( - element: ElementThreadElement, - value: string | Record | undefined, -) { - if (!value) return; - const { transformedStyle } = typeof value === 'string' - ? transformInlineStyleString(value) - : transfromParsedStyles( - Object.entries(value).map(([k, value]) => [ - hyphenateStyleName(k), - value, - ]), - ); - const transformedStyleStr = transformedStyle.map(( - [property, value], - ) => `${property}:${value};`).join(''); - element.setAttribute('style', transformedStyleStr); -} + function __SetInlineStyles( + element: HTMLElement, + value: string | Record | undefined, + ) { + if (!value) return; + const { transformedStyle } = typeof value === 'string' + ? transformInlineStyleString(value) + : transfromParsedStyles( + Object.entries(value).map(([k, value]) => [ + hyphenateStyleName(k), + value, + ]), + ); + const transformedStyleStr = transformedStyle.map(( + [property, value], + ) => `${property}:${value};`).join(''); + element.setAttribute('style', transformedStyleStr); + } -export function __SetCSSId( - elements: (ElementThreadElement)[], - cssId: string | number, -) { - cssId = cssId.toString(); - for (const element of elements) { - if (element.getAttribute(cssIdAttribute) === cssId) continue; // skip operation - element.setAttribute(cssIdAttribute, cssId); - if (!element.pageConfig.enableCSSSelector) { - const cls = element.getAttribute('class'); - cls && __SetClasses(element, cls); + function __SetCSSId( + elements: (HTMLElement)[], + cssId: string | number, + ) { + cssId = cssId.toString(); + for (const element of elements) { + element.setAttribute(cssIdAttribute, cssId); + if (!runtime.config.pageConfig.enableCSSSelector) { + const cls = element.getAttribute('class'); + cls && __SetClasses(element, cls); + } } } + + return { + __AddClass, + __SetClasses, + __GetClasses, + __AddInlineStyle, + __SetInlineStyles, + __SetCSSId, + }; } diff --git a/packages/web-platform/web-mainthread-apis/src/index.ts b/packages/web-platform/web-mainthread-apis/src/index.ts index d2847e35e3..3733bcf3b1 100644 --- a/packages/web-platform/web-mainthread-apis/src/index.ts +++ b/packages/web-platform/web-mainthread-apis/src/index.ts @@ -3,4 +3,3 @@ // LICENSE file in the root directory of this source tree. export * from './MainThreadRuntime.js'; -export { ElementThreadElement } from './elementAPI/ElementThreadElement.js'; diff --git a/packages/web-platform/web-mainthread-apis/src/utils/createCrossThreadEvent.ts b/packages/web-platform/web-mainthread-apis/src/utils/createCrossThreadEvent.ts new file mode 100644 index 0000000000..87f1cb8437 --- /dev/null +++ b/packages/web-platform/web-mainthread-apis/src/utils/createCrossThreadEvent.ts @@ -0,0 +1,55 @@ +// Copyright 2023 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 { Cloneable, LynxCrossThreadEvent } from '@lynx-js/web-constants'; +import { + elementToRuntimeInfoMap, + type MainThreadRuntime, +} from '../MainThreadRuntime.js'; + +export function createCrossThreadEvent( + runtime: MainThreadRuntime, + domEvent: Event, +): LynxCrossThreadEvent { + const targetElement = domEvent.target as HTMLElement; + const currentTargetElement = domEvent + .currentTarget! as HTMLElement; + const type = domEvent.type; + const params: Cloneable = {}; + if (type.match(/^transition/)) { + Object.assign(params, { + 'animation_type': 'keyframe-animation', + 'animation_name': (domEvent as TransitionEvent).propertyName, + new_animator: true, // we support the new_animator only + }); + } else if (type.match(/animation/)) { + Object.assign(params, { + 'animation_type': 'keyframe-animation', + 'animation_name': (domEvent as AnimationEvent).animationName, + new_animator: true, // we support the new_animator only + }); + } + const targetElementRuntimeInfo = runtime[elementToRuntimeInfoMap].get( + targetElement, + )!; + const currentTargetElementRuntimeInfo = runtime[elementToRuntimeInfoMap].get( + targetElement, + )!; + return { + type, + timestamp: domEvent.timeStamp, + target: { + id: targetElement.id, + dataset: targetElementRuntimeInfo.lynxDataset, + uniqueId: targetElementRuntimeInfo.uniqueId, + }, + currentTarget: { + id: currentTargetElement.id, + dataset: currentTargetElementRuntimeInfo.lynxDataset, + uniqueId: currentTargetElementRuntimeInfo.uniqueId, + }, + // @ts-expect-error + detail: domEvent.detail ?? {}, + params, + }; +} diff --git a/packages/web-platform/web-mainthread-apis/src/utils/createExposureService.ts b/packages/web-platform/web-mainthread-apis/src/utils/createExposureService.ts new file mode 100644 index 0000000000..d0ae8f94d4 --- /dev/null +++ b/packages/web-platform/web-mainthread-apis/src/utils/createExposureService.ts @@ -0,0 +1,71 @@ +// Copyright 2023 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 { + lynxUniqueIdAttribute, + type ExposureWorkerEvent, +} from '@lynx-js/web-constants'; +import { createCrossThreadEvent } from './createCrossThreadEvent.js'; +import type { MainThreadRuntime } from '../MainThreadRuntime.js'; + +export function createExposureService(runtime: MainThreadRuntime) { + const postExposure = runtime.config.callbacks.postExposure; + let working = true; + let exposureCache: ExposureWorkerEvent[] = []; + let disexposureCache: ExposureWorkerEvent[] = []; + const onScreen = new Map(); + function exposureEventHandler(ev: Event) { + const exposureEvent = createCrossThreadEvent( + runtime, + ev, + ) as ExposureWorkerEvent; + exposureEvent.detail['unique-id'] = parseFloat( + (ev.target as Element).getAttribute(lynxUniqueIdAttribute)!, + ); + const exposureID = exposureEvent.exposureID; + if (ev.type === 'exposure') { + exposureCache.push(exposureEvent); + onScreen.set(exposureID, exposureEvent); + } else { + disexposureCache.push(exposureEvent); + onScreen.delete(exposureID); + } + } + setInterval(() => { + if (exposureCache.length > 0 || disexposureCache.length > 0) { + const currentExposure = exposureCache; + const currentDisexposure = disexposureCache; + exposureCache = []; + disexposureCache = []; + postExposure({ + exposures: currentExposure, + disExposures: currentDisexposure, + }); + } + }, 1000 / 20); + runtime._rootDom.addEventListener('exposure', exposureEventHandler, { + passive: true, + }); + runtime._rootDom.addEventListener('disexposure', exposureEventHandler, { + passive: true, + }); + + function switchExposureService(enable: boolean, sendEvent: boolean) { + if (enable && !working) { + // send all onScreen info + postExposure({ + exposures: [...onScreen.values()], + disExposures: [], + }); + } else if (!enable && working) { + if (sendEvent) { + postExposure({ + exposures: [], + disExposures: [...onScreen.values()], + }); + } + } + working = enable; + } + return { switchExposureService }; +} diff --git a/packages/web-platform/web-mainthread-apis/tsconfig.json b/packages/web-platform/web-mainthread-apis/tsconfig.json index e1f71504cf..b296eda85a 100644 --- a/packages/web-platform/web-mainthread-apis/tsconfig.json +++ b/packages/web-platform/web-mainthread-apis/tsconfig.json @@ -4,7 +4,7 @@ "composite": true, "rootDir": "./src", "outDir": "./dist", - "lib": ["ESNext", "WebWorker"], + "lib": ["ESNext", "DOM"], }, "include": ["src"], "references": [ diff --git a/packages/web-platform/web-tests/package.json b/packages/web-platform/web-tests/package.json index 3aa9dfa098..9c008ad35b 100644 --- a/packages/web-platform/web-tests/package.json +++ b/packages/web-platform/web-tests/package.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@lynx-js/lynx-core": "0.1.0", + "@lynx-js/offscreen-document": "workspace:*", "@lynx-js/react": "workspace:*", "@lynx-js/react-rsbuild-plugin": "workspace:*", "@lynx-js/rspeedy": "workspace:*", diff --git a/packages/web-platform/web-tests/shell-project/mainthread-test.ts b/packages/web-platform/web-tests/shell-project/mainthread-test.ts index 36a7042986..b753020327 100644 --- a/packages/web-platform/web-tests/shell-project/mainthread-test.ts +++ b/packages/web-platform/web-tests/shell-project/mainthread-test.ts @@ -2,29 +2,51 @@ // 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 { MainThreadRuntime } from '@lynx-js/web-mainthread-apis'; -import { ElementThreadElement } from '@lynx-js/web-mainthread-apis/dist/elementAPI/ElementThreadElement.js'; -import { decodeElementOperation } from '@lynx-js/web-core/dist/uiThread/decodeElementOperation'; -import type { RuntimePropertyOnElement } from '@lynx-js/web-core/dist/types/RuntimePropertyOnElement'; -import { lynxRuntimeValue } from '@lynx-js/web-core'; +import { + getElementByUniqueId, + MainThreadRuntime, +} from '@lynx-js/web-mainthread-apis'; +import { initOffscreenDocument } from '@lynx-js/offscreen-document/main'; +import { OffscreenDocument } from '@lynx-js/offscreen-document/webworker'; +import type { + ElementOperation, + OffscreenElement, +} from '@lynx-js/offscreen-document'; type CompareableElementJson = { tag: string; children: CompareableElementJson[]; parentUid?: number; }; +let runtime: any; +let elementOperations: ElementOperation[] = []; -const uniqueIdToElement: WeakRef[] = []; - -let rootDom: HTMLElement; +const div: HTMLElement = document.createElement('div'); +div.id = 'root'; +const shadowRoot = div.attachShadow({ mode: 'open' }); +document.body.appendChild(div); +const { decodeOperation } = initOffscreenDocument({ + shadowRoot, + onEvent(eventType, targetUniqueId, bubbles) { + // nyi + }, +}); function serializeElementThreadElement( - element: ElementThreadElement, + element: OffscreenElement, ): CompareableElementJson { + const parent = runtime.__GetParent(element); + const tag = runtime.__GetTag(element); + const parentUid = parent && runtime.__GetTag(element) !== 'page' + ? runtime.__GetElementUniqueID(parent) + : undefined; + const children = runtime.__GetChildren(element).map(e => + serializeElementThreadElement(e) + ); return { - tag: element.tag, - children: element.children.map(e => serializeElementThreadElement(e)), - parentUid: element.parent?.uniqueId, + tag, + children, + parentUid, }; } @@ -37,15 +59,15 @@ function serializeDomElement(element: Element): CompareableElementJson { } const parentUid = element?.parentElement?.getAttribute('lynx-unique-id'); return { - tag: element.tagName.toLowerCase(), + tag: element.getAttribute('lynx-tag')!, children: [...element.children].map(e => serializeDomElement(e)), parentUid: parentUid ? parseFloat(parentUid) : undefined, }; } function genFiberElementTree() { - const page = ElementThreadElement.uniqueIdToElement[0]?.deref(); - if (page?.getAttribute('lynx-tag') === 'page') { + const page = runtime[getElementByUniqueId](1) as unknown as OffscreenElement; + if (runtime.__GetTag(page) === 'page') { return serializeElementThreadElement(page); } else { return {}; @@ -53,6 +75,7 @@ function genFiberElementTree() { } function genDomElementTree() { + const rootDom = shadowRoot.querySelector('[lynx-tag=\'page\']'); if (rootDom) { return serializeDomElement(rootDom); } else { @@ -60,12 +83,13 @@ function genDomElementTree() { } } -function getElementThreadElements() { - return ElementThreadElement.uniqueIdToElement; -} - function initializeMainThreadTest() { - const runtime = new MainThreadRuntime({ + const docu = new OffscreenDocument({ + onCommit(operations) { + elementOperations = operations; + }, + }); + runtime = new MainThreadRuntime({ tagMap: { 'page': 'div', 'view': 'x-view', @@ -82,45 +106,15 @@ function initializeMainThreadTest() { enableRemoveCSSScope: true, defaultDisplayLinear: true, }, + docu, styleInfo: {}, globalProps: {}, callbacks: { mainChunkReady: function(): void { }, - flushElementTree: (operations) => { - console.log(operations); - const page = decodeElementOperation( - operations, - { - uniqueIdToElement, - uniqueIdToCssInJsRule: [], - createElementImpl: (tag: string) => { - const element = document.createElement(tag) as - & HTMLDivElement - & RuntimePropertyOnElement; - element[lynxRuntimeValue] = { - dataset: {}, - eventHandler: {}, - }; - return element; - }, - createStyleRuleImpl: function( - uniqueId: number, - initialStyle: string, - ): CSSStyleRule { - throw new Error('Function not implemented.'); - }, - eventHandler: { - mtsHandler: () => {}, - btsHandler: () => {}, - }, - timingFlags: [], - }, - ); - if (page) { - document.body.append(page as HTMLElement); - rootDom = page; - } + flushElementTree: () => { + docu.commit(); + decodeOperation(elementOperations); }, _ReportError: function(error: string, info?: unknown): void { document.body.innerHTML = ''; @@ -128,15 +122,16 @@ function initializeMainThreadTest() { __OnLifecycleEvent() { }, markTiming: function(pipelineId: string, timingKey: string): void { - throw new Error('Function not implemented.'); }, + publishEvent: () => {}, + publicComponentEvent: () => {}, + postExposure: () => {}, }, }); Object.assign(globalThis, runtime); Object.assign(globalThis, { genFiberElementTree, genDomElementTree, - getElementThreadElements, }); } diff --git a/packages/web-platform/web-tests/tests/main-thread-apis.test.ts b/packages/web-platform/web-tests/tests/main-thread-apis.test.ts index c15c83d57a..f5d69ae01e 100644 --- a/packages/web-platform/web-tests/tests/main-thread-apis.test.ts +++ b/packages/web-platform/web-tests/tests/main-thread-apis.test.ts @@ -91,7 +91,6 @@ test.describe('main thread api tests', () => { test( 'create-scroll-view-with-set-attribute', async ({ page, browserName }, { title }) => { - test.skip(browserName !== 'chromium', 'flaky'); const ret = await page.evaluate(() => { let root = globalThis.__CreatePage('page', 0); let ret = globalThis.__CreateScrollView(0); @@ -207,7 +206,7 @@ test.describe('main thread api tests', () => { ret1: globalThis.__GetTag(ret1), }; }); - expect(ret.ret0).toBe(undefined); + expect(ret.ret0).toBeFalsy(); expect(ret.ret_u).toBe(undefined); expect(ret.ret1).toBe('text'); }); @@ -530,12 +529,8 @@ test.describe('main thread api tests', () => { globalThis.__AddInlineStyle(root, 26, '80px'); globalThis.__FlushElementTree(); }); - const style = await page.evaluate(() => { - const elements = globalThis.getElementThreadElements(); - return elements[0].deref().attributes.style; - }); - await expect(style).toBe('height:80px;'); - await expect(page.locator(`[lynx-tag='page']`)).toHaveCSS('height', '80px'); + const pageElement = page.locator(`[lynx-tag='page']`); + await expect(pageElement).toHaveCSS('height', '80px'); }); test('__AddInlineStyle_raw_string', async ({ page }, { title }) => { @@ -958,12 +953,18 @@ test.describe('main thread api tests', () => { globalThis.__SetID(child_2, 'node_id_2'); globalThis.__FlushElementTree(); - let ret_node = document.querySelector('#node_id'); + let ret_node = document.getElementById('root').shadowRoot.querySelector( + '#node_id', + ); let ret_id = ret_node?.getAttribute('id'); - let ret_u = document.querySelector('#node_id_u'); + let ret_u = document.getElementById('root').shadowRoot.querySelector( + '#node_id_u', + ); - let ret_child = document.querySelector('#node_id_2'); + let ret_child = document.getElementById('root').shadowRoot.querySelector( + '#node_id_2', + ); let ret_child_id = ret_child?.getAttribute('id'); // let ret_child_u = parent.querySelector('#node_id_2'); @@ -982,7 +983,7 @@ test.describe('main thread api tests', () => { test('__setAttribute_null_value', async ({ page }, { title }) => { await page.evaluate(() => { - const ret = globalThis.__CreateElement('page', 0) as HTMLElement; + const ret = globalThis.__CreatePage('page', 0); globalThis.__SetAttribute(ret, 'test-attr', 'val'); globalThis.__SetAttribute(ret, 'test-attr', null); globalThis.__FlushElementTree(); diff --git a/packages/web-platform/web-worker-runtime/package.json b/packages/web-platform/web-worker-runtime/package.json index 88b2c11fd1..0d0ee9b6a7 100644 --- a/packages/web-platform/web-worker-runtime/package.json +++ b/packages/web-platform/web-worker-runtime/package.json @@ -23,7 +23,7 @@ ], "dependencies": { "@lynx-js/web-constants": "workspace:*", - "@lynx-js/web-mainthread-apis": "workspace:*", + "@lynx-js/web-mainthread-apis": "0.9.1", "@lynx-js/web-worker-rpc": "workspace:*" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40a57bf0be..83b51b2b96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -661,6 +661,9 @@ importers: '@lynx-js/lynx-core': specifier: 0.1.0 version: 0.1.0 + '@lynx-js/offscreen-document': + specifier: workspace:* + version: link:../offscreen-document '@lynx-js/react': specifier: workspace:* version: link:../../react @@ -724,8 +727,8 @@ importers: specifier: workspace:* version: link:../web-constants '@lynx-js/web-mainthread-apis': - specifier: workspace:* - version: link:../web-mainthread-apis + specifier: 0.9.1 + version: 0.9.1 '@lynx-js/web-worker-rpc': specifier: workspace:* version: link:../web-worker-rpc @@ -2202,6 +2205,18 @@ packages: '@lynx-js/types@3.2.0': resolution: {integrity: sha512-BGN+GQjHgrjIViZAaop+WgA1nVnXZdX9TnOMwBwpgJkEJU02rdwK7n7g919/qNaK7cSw/5mEPcv7YEpF2ToNaQ==} + '@lynx-js/web-constants@0.9.1': + resolution: {integrity: sha512-uUGfpV5EpPMrgjqPmqe/9PLAgEUPrkdGhxs6bb1Jd7v/Sw6hUBO7O8gY6+mQcKK4pwfh3NvpnBf5knvt4kWZTg==} + + '@lynx-js/web-mainthread-apis@0.9.1': + resolution: {integrity: sha512-m2HpJTVjDrncBzRb+9/a9SI7JukuDhW25o+8Ii1iAJicL43BjpGm9ONLzsdacy7gro48G4LOFQLOTHEwn5Z79w==} + + '@lynx-js/web-style-transformer@0.2.3': + resolution: {integrity: sha512-kj+NfRZ0Trc1BaWfkaDgTq3aW6nVKMkGhGAqr6u6QJlfSsAGZReD6t9Cu28vMEskEadYOehFM7GD2yPDGHQWiA==} + + '@lynx-js/web-worker-rpc@0.9.1': + resolution: {integrity: sha512-NvNbbJtrGtDFAeAveWwRrDJZWvlSXWtOskHGQhbEetqFscRgJtdiExg0PFghWOU4TdrEKc4/0QGhtU2IAG6dPQ==} + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -9479,6 +9494,21 @@ snapshots: dependencies: csstype: 3.1.3 + '@lynx-js/web-constants@0.9.1': + dependencies: + '@lynx-js/web-worker-rpc': 0.9.1 + + '@lynx-js/web-mainthread-apis@0.9.1': + dependencies: + '@lynx-js/web-constants': 0.9.1 + '@lynx-js/web-style-transformer': 0.2.3 + css-tree: 3.1.0 + hyphenate-style-name: 1.1.0 + + '@lynx-js/web-style-transformer@0.2.3': {} + + '@lynx-js/web-worker-rpc@0.9.1': {} + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.25.4