@lynx-js/web-mainthread-apis-canary 0.16.1 → 0.17.0-canary-20250918-b17b7cb2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @lynx-js/web-mainthread-apis
2
2
 
3
+ ## 0.17.0-canary-20250918080343-b17b7cb2bb92d1539e76276f136806eefa788258
4
+
5
+ ### Patch Changes
6
+
7
+ - fix: \_\_QueryComponentImpl in mts should execute only once for same url ([#1763](https://github.com/lynx-family/lynx-stack/pull/1763))
8
+
9
+ - fix: avoid duplicate style transformation ([#1748](https://github.com/lynx-family/lynx-stack/pull/1748))
10
+
11
+ After this commit, we use DAG methods to handle the styleInfos
12
+
13
+ - Updated dependencies [[`93d707b`](https://github.com/lynx-family/lynx-stack/commit/93d707b82a59f7256952e21da6dcad2999f8233d)]:
14
+ - @lynx-js/web-constants@0.17.0-canary-20250918080343-b17b7cb2bb92d1539e76276f136806eefa788258
15
+ - @lynx-js/web-style-transformer@0.17.0-canary-20250918080343-b17b7cb2bb92d1539e76276f136806eefa788258
16
+
3
17
  ## 0.16.1
4
18
 
5
19
  ### Patch Changes
@@ -212,25 +226,25 @@
212
226
  lynxView.initI18nResources = [
213
227
  {
214
228
  options: {
215
- locale: 'en',
216
- channel: '1',
217
- fallback_url: '',
229
+ locale: "en",
230
+ channel: "1",
231
+ fallback_url: "",
218
232
  },
219
233
  resource: {
220
- hello: 'hello',
221
- lynx: 'lynx web platform1',
234
+ hello: "hello",
235
+ lynx: "lynx web platform1",
222
236
  },
223
237
  },
224
238
  ];
225
- lynxView.addEventListener('i18nResourceMissed', (e) => {
239
+ lynxView.addEventListener("i18nResourceMissed", (e) => {
226
240
  console.log(e);
227
241
  });
228
242
 
229
243
  // mts
230
244
  _I18nResourceTranslation({
231
- locale: 'en',
232
- channel: '1',
233
- fallback_url: '',
245
+ locale: "en",
246
+ channel: "1",
247
+ fallback_url: "",
234
248
  });
235
249
  ```
236
250
 
@@ -1,19 +1,27 @@
1
1
  import { queryComponentEndpoint, updateBTSTemplateCacheEndpoint, } from '@lynx-js/web-constants';
2
2
  export function createQueryComponent(loadTemplate, updateLazyComponentStyle, backgroundThreadRpc, mtsGlobalThisRef, jsContext, mtsRealm) {
3
3
  const updateBTSTemplateCache = backgroundThreadRpc.createCall(updateBTSTemplateCacheEndpoint);
4
+ const lazyCache = new Map();
4
5
  const __QueryComponentImpl = (url, callback) => {
5
- loadTemplate(url).then(async (template) => {
6
- const updateBTSCachePromise = updateBTSTemplateCache(url, template);
7
- let lepusRootChunkExport = await mtsRealm.loadScript(template.lepusCode.root);
8
- if (mtsGlobalThisRef.mtsGlobalThis.processEvalResult) {
9
- lepusRootChunkExport = mtsGlobalThisRef.mtsGlobalThis.processEvalResult(lepusRootChunkExport, url);
10
- }
11
- updateLazyComponentStyle(template.styleInfo, url);
12
- await updateBTSCachePromise;
13
- jsContext.dispatchEvent({
14
- type: '__OnDynamicJSSourcePrepared',
15
- data: url,
6
+ const cacheLazy = lazyCache.get(url);
7
+ const loadPromise = cacheLazy
8
+ ?? loadTemplate(url).then(async (template) => {
9
+ const updateBTSCachePromise = updateBTSTemplateCache(url, template);
10
+ let lepusRootChunkExport = await mtsRealm.loadScript(template.lepusCode.root);
11
+ if (mtsGlobalThisRef.mtsGlobalThis.processEvalResult) {
12
+ lepusRootChunkExport = mtsGlobalThisRef.mtsGlobalThis
13
+ .processEvalResult(lepusRootChunkExport, url);
14
+ }
15
+ updateLazyComponentStyle(template.styleInfo, url);
16
+ await updateBTSCachePromise;
17
+ jsContext.dispatchEvent({
18
+ type: '__OnDynamicJSSourcePrepared',
19
+ data: url,
20
+ });
21
+ return lepusRootChunkExport;
16
22
  });
23
+ !cacheLazy && lazyCache.set(url, loadPromise);
24
+ loadPromise.then(lepusRootChunkExport => {
17
25
  callback?.({
18
26
  code: 0,
19
27
  data: {
@@ -23,6 +31,7 @@ export function createQueryComponent(loadTemplate, updateLazyComponentStyle, bac
23
31
  });
24
32
  }).catch((error) => {
25
33
  console.error(`lynx web: lazy bundle load failed:`, error);
34
+ lazyCache.delete(url);
26
35
  callback?.({
27
36
  code: -1,
28
37
  data: undefined,
@@ -1,17 +1,23 @@
1
- import { type StyleInfo, type CssOGInfo, type PageConfig, type SSRHydrateInfo } from '@lynx-js/web-constants';
2
- export declare function flattenStyleInfo(styleInfo: StyleInfo, enableCSSSelector: boolean): void;
1
+ import { type StyleInfo, type CssOGInfo, type PageConfig, type SSRHydrateInfo, type FlattenedStyleInfo } from '@lynx-js/web-constants';
2
+ /**
3
+ * get Transitive Closure of a Direct Acyclic Graph (DAG)
4
+ * 1. for each css, find all the imported by css files (directly and indirectly)
5
+ * 2. for each css, find all the importing css files (directly and indirectly)
6
+ * 3. return the flattened style info, do not modify the content and rules
7
+ */
8
+ export declare function flattenStyleInfo(styleInfo: StyleInfo): FlattenedStyleInfo;
3
9
  /**
4
10
  * apply the lynx css -> web css transformation
5
11
  */
6
- export declare function transformToWebCss(styleInfo: StyleInfo): void;
12
+ export declare function transformToWebCss(styleInfo: FlattenedStyleInfo): void;
7
13
  /**
8
14
  * generate those styles applied by <style>...</style>
9
15
  */
10
- export declare function genCssContent(styleInfo: StyleInfo, pageConfig: PageConfig, entryName?: string): string;
16
+ export declare function genCssContent(styleInfo: FlattenedStyleInfo, pageConfig: PageConfig, entryName?: string): string;
11
17
  /**
12
18
  * generate the css-in-js data
13
19
  */
14
- export declare function genCssOGInfo(styleInfo: StyleInfo): CssOGInfo;
20
+ export declare function genCssOGInfo(styleInfo: FlattenedStyleInfo): CssOGInfo;
15
21
  export declare function appendStyleElement(styleInfo: StyleInfo, pageConfig: PageConfig, rootDom: Node, document: Document, entryName?: string, ssrHydrateInfo?: SSRHydrateInfo): {
16
22
  updateCssOGStyle: (uniqueId: number, newClassName: string, cssID: string | null) => void;
17
23
  updateLazyComponentStyle: (styleInfo: StyleInfo, entryName: string) => void;
@@ -4,36 +4,94 @@
4
4
  import { cssIdAttribute, lynxTagAttribute, lynxUniqueIdAttribute, lynxEntryNameAttribute, } from '@lynx-js/web-constants';
5
5
  import { transformParsedStyles } from './tokenizer.js';
6
6
  import { decodeCssOG } from './decodeCssOG.js';
7
- export function flattenStyleInfo(styleInfo, enableCSSSelector) {
8
- function flattenOneStyleInfo(cssId) {
9
- const oneInfo = styleInfo[cssId];
10
- const imports = oneInfo?.imports;
11
- if (oneInfo && imports?.length) {
12
- for (const im of imports) {
13
- const flatInfo = flattenOneStyleInfo(im);
14
- if (flatInfo) {
15
- oneInfo.content.push(...flatInfo.content);
16
- // oneInfo.rules.push(...flatInfo.rules);
17
- oneInfo.rules.push(...(enableCSSSelector
18
- ? flatInfo.rules
19
- // when enableCSSSelector is false, need to make a shallow copy of rules.sel
20
- // otherwise updating `oneCssInfo.sel` in `genCssOGInfo()` will affect other imported cssInfo
21
- : flatInfo.rules.map(i => ({ ...i }))));
7
+ function topologicalSort(styleInfo) {
8
+ /**
9
+ * kahn's algorithm
10
+ * 1. The styleInfo is already equivalent to a adjacency list. (cssId, import)
11
+ * 2. The styleInfo is a DAG therefore we don't need to do cyclic detection
12
+ */
13
+ const queue = [];
14
+ const inDegreeMap = new Map();
15
+ for (const [cssId, oneStyleInfo] of Object.entries(styleInfo)) {
16
+ !inDegreeMap.has(cssId) && inDegreeMap.set(cssId, 0); // initialize
17
+ for (const importCssId of oneStyleInfo.imports ?? []) {
18
+ const currentInDegree = inDegreeMap.get(importCssId) ?? 0;
19
+ inDegreeMap.set(importCssId, currentInDegree + 1);
20
+ }
21
+ }
22
+ for (const [cssId, inDegree] of inDegreeMap.entries()) {
23
+ if (inDegree === 0) {
24
+ queue.push(cssId);
25
+ }
26
+ }
27
+ const sortedCssIds = [];
28
+ while (queue.length > 0) {
29
+ const currentCssId = queue.shift();
30
+ sortedCssIds.push(currentCssId);
31
+ const currentAdjunction = styleInfo[currentCssId]?.imports;
32
+ if (currentAdjunction) {
33
+ for (const importCssId of currentAdjunction) {
34
+ const importInDegree = inDegreeMap.get(importCssId) - 1;
35
+ inDegreeMap.set(importCssId, importInDegree);
36
+ if (importInDegree === 0) {
37
+ queue.push(importCssId);
22
38
  }
23
39
  }
24
- oneInfo.imports = undefined;
25
40
  }
26
- return oneInfo;
27
41
  }
28
- Object.keys(styleInfo).map((cssId) => {
29
- flattenOneStyleInfo(cssId);
42
+ return sortedCssIds;
43
+ }
44
+ function generateImportByMap(styleInfo, sortedCssIds) {
45
+ const cssIdToImportBy = new Map();
46
+ for (const cssId of sortedCssIds) {
47
+ const currentAdjunction = styleInfo[cssId]?.imports;
48
+ if (currentAdjunction) {
49
+ const currentImportBy = cssIdToImportBy.get(cssId) ?? new Set([cssId]);
50
+ for (const importCssId of currentAdjunction) {
51
+ const importDeps = cssIdToImportBy.get(importCssId)
52
+ ?? new Set([importCssId]);
53
+ importDeps.add(cssId);
54
+ cssIdToImportBy.set(importCssId, currentImportBy.union(importDeps));
55
+ }
56
+ cssIdToImportBy.set(cssId, currentImportBy);
57
+ }
58
+ }
59
+ return cssIdToImportBy;
60
+ }
61
+ /**
62
+ * get Transitive Closure of a Direct Acyclic Graph (DAG)
63
+ * 1. for each css, find all the imported by css files (directly and indirectly)
64
+ * 2. for each css, find all the importing css files (directly and indirectly)
65
+ * 3. return the flattened style info, do not modify the content and rules
66
+ */
67
+ export function flattenStyleInfo(styleInfo) {
68
+ // Step 1. Topological sorting
69
+ const sortedCssIds = topologicalSort(styleInfo);
70
+ // Step 2. generate deps;
71
+ const cssIdToImportBy = generateImportByMap(styleInfo, sortedCssIds);
72
+ sortedCssIds.reverse();
73
+ // Step 3. generate the flattened style info
74
+ return sortedCssIds.map(cssId => {
75
+ const oneInfo = styleInfo[cssId];
76
+ const flattenedInfo = oneInfo
77
+ ? {
78
+ content: oneInfo.content,
79
+ rules: oneInfo.rules,
80
+ importBy: Array.from(cssIdToImportBy.get(cssId) ?? [cssId]),
81
+ }
82
+ : {
83
+ content: [],
84
+ rules: [],
85
+ importBy: [cssId],
86
+ };
87
+ return flattenedInfo;
30
88
  });
31
89
  }
32
90
  /**
33
91
  * apply the lynx css -> web css transformation
34
92
  */
35
93
  export function transformToWebCss(styleInfo) {
36
- for (const cssInfos of Object.values(styleInfo)) {
94
+ for (const cssInfos of styleInfo) {
37
95
  for (const rule of cssInfos.rules) {
38
96
  const { sel: selectors, decl: declarations } = rule;
39
97
  const { transformedStyle, childStyle } = transformParsedStyles(declarations);
@@ -56,13 +114,7 @@ export function genCssContent(styleInfo, pageConfig, entryName) {
56
114
  function getExtraSelectors(cssId) {
57
115
  let suffix;
58
116
  if (!pageConfig.enableRemoveCSSScope) {
59
- if (cssId !== undefined) {
60
- suffix = `[${cssIdAttribute}="${cssId}"]`;
61
- }
62
- else {
63
- // To make sure the Specificity correct
64
- suffix = `[${lynxTagAttribute}]`;
65
- }
117
+ suffix = `[${cssIdAttribute}="${cssId}"]`;
66
118
  }
67
119
  else {
68
120
  suffix = `[${lynxTagAttribute}]`;
@@ -76,13 +128,15 @@ export function genCssContent(styleInfo, pageConfig, entryName) {
76
128
  return suffix;
77
129
  }
78
130
  const finalCssContent = [];
79
- for (const [cssId, cssInfos] of Object.entries(styleInfo)) {
80
- const suffix = getExtraSelectors(cssId);
131
+ for (const cssInfos of styleInfo) {
81
132
  const declarationContent = cssInfos.rules.map((rule) => {
82
133
  const { sel: selectorList, decl: declarations } = rule;
83
134
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
84
- const selectorString = selectorList.map((selectors) => {
85
- return selectors.toSpliced(-4, 0, [suffix]).flat().join('');
135
+ const selectorString = cssInfos.importBy.map(cssId => {
136
+ const suffix = getExtraSelectors(cssId);
137
+ return selectorList.map((selectors) => {
138
+ return selectors.toSpliced(-4, 0, [suffix]).flat().join('');
139
+ }).join(',');
86
140
  }).join(',');
87
141
  const declarationString = declarations.map(([k, v]) => `${k}:${v};`).join('');
88
142
  return `${selectorString}{${declarationString}}`;
@@ -95,10 +149,10 @@ export function genCssContent(styleInfo, pageConfig, entryName) {
95
149
  * generate the css-in-js data
96
150
  */
97
151
  export function genCssOGInfo(styleInfo) {
98
- return Object.fromEntries(Object.entries(styleInfo).map(([cssId, cssInfos]) => {
99
- const oneCssOGInfo = {};
100
- cssInfos.rules = cssInfos.rules.filter(oneCssInfo => {
101
- oneCssInfo.sel = oneCssInfo.sel.filter(selectorList => {
152
+ const cssOGInfo = {};
153
+ for (const oneInfo of styleInfo) {
154
+ oneInfo.rules = oneInfo.rules.filter(oneRule => {
155
+ oneRule.sel = oneRule.sel.filter(selectorList => {
102
156
  const [classSelectors, pseudoClassSelectors, pseudoElementSelectors, combinator,] = selectorList;
103
157
  if (
104
158
  // only one class selector
@@ -107,21 +161,26 @@ export function genCssOGInfo(styleInfo) {
107
161
  && pseudoElementSelectors.length === 0
108
162
  && combinator.length === 0) {
109
163
  const selectorName = classSelectors[0].substring(1);
110
- const currentDeclarations = oneCssOGInfo[selectorName];
111
- if (currentDeclarations) {
112
- currentDeclarations.push(...oneCssInfo.decl);
113
- }
114
- else {
115
- oneCssOGInfo[selectorName] = oneCssInfo.decl;
164
+ for (const cssId of oneInfo.importBy) {
165
+ if (!cssOGInfo[cssId]) {
166
+ cssOGInfo[cssId] = {};
167
+ }
168
+ const currentDeclarations = cssOGInfo[cssId][selectorName];
169
+ if (currentDeclarations) {
170
+ currentDeclarations.push(...oneRule.decl);
171
+ }
172
+ else {
173
+ cssOGInfo[cssId][selectorName] = oneRule.decl;
174
+ }
116
175
  }
117
176
  return false; // remove this selector from style info
118
177
  }
119
178
  return true;
120
179
  });
121
- return oneCssInfo.sel.length > 0;
180
+ return oneRule.sel.length > 0;
122
181
  });
123
- return [cssId, oneCssOGInfo];
124
- }));
182
+ }
183
+ return cssOGInfo;
125
184
  }
126
185
  export function appendStyleElement(styleInfo, pageConfig, rootDom, document, entryName, ssrHydrateInfo) {
127
186
  const lynxUniqueIdToStyleRulesIndex = ssrHydrateInfo?.lynxUniqueIdToStyleRulesIndex ?? [];
@@ -133,18 +192,18 @@ export function appendStyleElement(styleInfo, pageConfig, rootDom, document, ent
133
192
  * 4. create the style element
134
193
  * 5. append the style element to the root dom
135
194
  */
136
- flattenStyleInfo(styleInfo, pageConfig.enableCSSSelector);
137
- transformToWebCss(styleInfo);
195
+ const flattenedStyleInfo = flattenStyleInfo(styleInfo);
196
+ transformToWebCss(flattenedStyleInfo);
138
197
  const cssOGInfo = pageConfig.enableCSSSelector
139
198
  ? {}
140
- : genCssOGInfo(styleInfo);
199
+ : genCssOGInfo(flattenedStyleInfo);
141
200
  let cardStyleElement;
142
201
  if (ssrHydrateInfo?.cardStyleElement) {
143
202
  cardStyleElement = ssrHydrateInfo.cardStyleElement;
144
203
  }
145
204
  else {
146
205
  cardStyleElement = document.createElement('style');
147
- cardStyleElement.textContent = genCssContent(styleInfo, pageConfig, entryName);
206
+ cardStyleElement.textContent = genCssContent(flattenedStyleInfo, pageConfig, entryName);
148
207
  rootDom.appendChild(cardStyleElement);
149
208
  }
150
209
  const cardStyleElementSheet = cardStyleElement.sheet;
@@ -161,9 +220,9 @@ export function appendStyleElement(styleInfo, pageConfig, rootDom, document, ent
161
220
  }
162
221
  };
163
222
  const updateLazyComponentStyle = (styleInfo, entryName) => {
164
- flattenStyleInfo(styleInfo, pageConfig.enableCSSSelector);
165
- transformToWebCss(styleInfo);
166
- const newStyleSheet = genCssContent(styleInfo, pageConfig, entryName);
223
+ const flattenedStyleInfo = flattenStyleInfo(styleInfo);
224
+ transformToWebCss(flattenedStyleInfo);
225
+ const newStyleSheet = genCssContent(flattenedStyleInfo, pageConfig, entryName);
167
226
  cardStyleElement.textContent += newStyleSheet;
168
227
  };
169
228
  return { updateCssOGStyle, updateLazyComponentStyle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynx-js/web-mainthread-apis-canary",
3
- "version": "0.16.1",
3
+ "version": "0.17.0-canary-20250918-b17b7cb2",
4
4
  "private": false,
5
5
  "description": "",
6
6
  "keywords": [],
@@ -24,8 +24,8 @@
24
24
  "**/*.css"
25
25
  ],
26
26
  "dependencies": {
27
- "@lynx-js/web-constants": "npm:@lynx-js/web-constants-canary@0.16.1",
28
- "@lynx-js/web-style-transformer": "npm:@lynx-js/web-style-transformer-canary@0.16.1",
27
+ "@lynx-js/web-constants": "npm:@lynx-js/web-constants-canary@0.17.0-canary-20250918-b17b7cb2",
28
+ "@lynx-js/web-style-transformer": "npm:@lynx-js/web-style-transformer-canary@0.17.0-canary-20250918-b17b7cb2",
29
29
  "hyphenate-style-name": "^1.1.0"
30
30
  }
31
31
  }