Skip to content

Commit e2bc36f

Browse files
authored
fix(engine-dom): refactor stylesheet API for consistency (#2827)
* fix(engine-dom): refactor stylesheet API for consistency * fix: remove useless code comment * test: remove unnecessary test * test: remove unnecessary test * refactor: slight refactor * fix: add code comments * fix: add code comments * fix: add better comment
1 parent 5af18fd commit e2bc36f

File tree

13 files changed

+221
-150
lines changed

13 files changed

+221
-150
lines changed

packages/@lwc/engine-core/src/framework/hydration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ function hydrateCustomElement(elm: Node, vnode: VCustomElement): Node | null {
204204
mode,
205205
owner,
206206
tagName: sel,
207+
hydrated: true,
207208
});
208209

209210
vnode.elm = elm;

packages/@lwc/engine-core/src/framework/main.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ export {
8080
setGetProperty,
8181
setHTMLElement,
8282
setInsert,
83-
setInsertGlobalStylesheet,
84-
setInsertStylesheet,
8583
setIsConnected,
8684
setIsHydrating,
8785
setIsNativeShadowDefined,
@@ -98,4 +96,5 @@ export {
9896
setSetText,
9997
setSsr,
10098
setAddEventListener,
99+
setInsertStylesheet,
101100
} from '../renderer';

packages/@lwc/engine-core/src/framework/stylesheet.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,7 @@
66
*/
77
import { ArrayJoin, ArrayPush, isArray, isNull, isUndefined, KEY__SCOPED_CSS } from '@lwc/shared';
88

9-
import {
10-
getClassList,
11-
removeAttribute,
12-
setAttribute,
13-
insertGlobalStylesheet,
14-
ssr,
15-
isHydrating,
16-
insertStylesheet,
17-
} from '../renderer';
9+
import { getClassList, removeAttribute, setAttribute, ssr, insertStylesheet } from '../renderer';
1810

1911
import api from './api';
2012
import { RenderMode, ShadowMode, VM } from './vm';
@@ -207,9 +199,9 @@ export function createStylesheet(vm: VM, stylesheets: string[]): VNode | null {
207199
const { renderMode, shadowMode } = vm;
208200
if (renderMode === RenderMode.Shadow && shadowMode === ShadowMode.Synthetic) {
209201
for (let i = 0; i < stylesheets.length; i++) {
210-
insertGlobalStylesheet(stylesheets[i]);
202+
insertStylesheet(stylesheets[i]);
211203
}
212-
} else if (ssr || isHydrating()) {
204+
} else if (ssr || vm.hydrated) {
213205
// Note: We need to ensure that during hydration, the stylesheets method is the same as those in ssr.
214206
// This works in the client, because the stylesheets are created, and cached in the VM
215207
// the first time the VM renders.
@@ -220,14 +212,10 @@ export function createStylesheet(vm: VM, stylesheets: string[]): VNode | null {
220212
} else {
221213
// native shadow or light DOM, DOM renderer
222214
const root = getNearestNativeShadowComponent(vm);
223-
const isGlobal = isNull(root);
215+
// null root means a global style
216+
const target = isNull(root) ? undefined : root.shadowRoot!;
224217
for (let i = 0; i < stylesheets.length; i++) {
225-
if (isGlobal) {
226-
insertGlobalStylesheet(stylesheets[i]);
227-
} else {
228-
// local level
229-
insertStylesheet(stylesheets[i], root!.shadowRoot!);
230-
}
218+
insertStylesheet(stylesheets[i], target);
231219
}
232220
}
233221
return null;

packages/@lwc/engine-core/src/framework/vm.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ export interface VM<N = HostNode, E = HostElement> {
111111
readonly context: Context;
112112
/** The owner VM or null for root elements. */
113113
readonly owner: VM<N, E> | null;
114+
/** Whether or not the VM was hydrated */
115+
readonly hydrated: boolean;
114116
/** Rendering operations associated with the VM */
115117
readonly renderMode: RenderMode;
116118
shadowMode: ShadowMode;
@@ -263,9 +265,10 @@ export function createVM<HostNode, HostElement>(
263265
mode: ShadowRootMode;
264266
owner: VM<HostNode, HostElement> | null;
265267
tagName: string;
268+
hydrated?: boolean;
266269
}
267270
): VM {
268-
const { mode, owner, tagName } = options;
271+
const { mode, owner, tagName, hydrated } = options;
269272
const def = getComponentInternalDef(ctor);
270273

271274
const vm: VM = {
@@ -286,6 +289,7 @@ export function createVM<HostNode, HostElement>(
286289
cmpSlots: create(null),
287290
oar: create(null),
288291
cmpTemplate: null,
292+
hydrated: Boolean(hydrated),
289293

290294
renderMode: def.renderMode,
291295
shadowMode: computeShadowMode(def, owner),

packages/@lwc/engine-core/src/renderer.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,7 @@ export let isConnected: isConnectedFunc;
245245
export function setIsConnected(isConnectedImpl: isConnectedFunc) {
246246
isConnected = isConnectedImpl;
247247
}
248-
249-
type insertGlobalStylesheetFunc = (content: string) => void;
250-
export let insertGlobalStylesheet: insertGlobalStylesheetFunc;
251-
export function setInsertGlobalStylesheet(insertGlobalStylesheetImpl: insertGlobalStylesheetFunc) {
252-
insertGlobalStylesheet = insertGlobalStylesheetImpl;
253-
}
254-
255-
type insertStylesheetFunc = (content: string, target: ShadowRoot) => void;
248+
type insertStylesheetFunc = (content: string, target?: ShadowRoot) => void;
256249
export let insertStylesheet: insertStylesheetFunc;
257250
export function setInsertStylesheet(insertStylesheetImpl: insertStylesheetFunc) {
258251
insertStylesheet = insertStylesheetImpl;

packages/@lwc/engine-dom/src/apis/hydrate-component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function createVMWithProps(element: Element, Ctor: typeof LightningElement, prop
3636
mode: 'open',
3737
owner: null,
3838
tagName: element.tagName.toLowerCase(),
39+
hydrated: true,
3940
});
4041

4142
for (const [key, value] of Object.entries(props)) {

packages/@lwc/engine-dom/src/initializeRenderer.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import {
2828
setGetProperty,
2929
setHTMLElement,
3030
setInsert,
31-
setInsertGlobalStylesheet,
32-
setInsertStylesheet,
3331
setIsConnected,
3432
setIsHydrating,
3533
setIsNativeShadowDefined,
@@ -46,6 +44,7 @@ import {
4644
setSetText,
4745
setSsr,
4846
setAddEventListener,
47+
setInsertStylesheet,
4948
} from '@lwc/engine-core';
5049

5150
import {
@@ -71,8 +70,6 @@ import {
7170
getProperty,
7271
HTMLElement,
7372
insert,
74-
insertGlobalStylesheet,
75-
insertStylesheet,
7673
isConnected,
7774
isHydrating,
7875
isNativeShadowDefined,
@@ -89,6 +86,7 @@ import {
8986
setText,
9087
ssr,
9188
addEventListener,
89+
insertStylesheet,
9290
} from './renderer';
9391

9492
setAssertInstanceOfHTMLElement(assertInstanceOfHTMLElement);
@@ -113,8 +111,6 @@ setGetLastElementChild(getLastElementChild);
113111
setGetProperty(getProperty);
114112
setHTMLElement(HTMLElement);
115113
setInsert(insert);
116-
setInsertGlobalStylesheet(insertGlobalStylesheet);
117-
setInsertStylesheet(insertStylesheet);
118114
setIsConnected(isConnected);
119115
setIsHydrating(isHydrating);
120116
setIsNativeShadowDefined(isNativeShadowDefined);
@@ -131,3 +127,4 @@ setSetProperty(setProperty);
131127
setSetText(setText);
132128
setSsr(ssr);
133129
setAddEventListener(addEventListener);
130+
setInsertStylesheet(insertStylesheet);

packages/@lwc/engine-dom/src/renderer.ts

Lines changed: 1 addition & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,13 @@ import {
1111
hasOwnProperty,
1212
htmlPropertyToAttribute,
1313
globalThis,
14-
isFunction,
1514
isUndefined,
16-
isArray,
1715
KEY__IS_NATIVE_SHADOW_ROOT_DEFINED,
1816
KEY__SHADOW_TOKEN,
1917
setPrototypeOf,
2018
StringToLowerCase,
21-
getOwnPropertyDescriptor,
2219
} from '@lwc/shared';
23-
24-
const globalStylesheets: { [content: string]: true } = create(null);
25-
26-
if (process.env.NODE_ENV === 'development') {
27-
// @ts-ignore
28-
window.__lwcResetGlobalStylesheets = () => {
29-
for (const key of Object.keys(globalStylesheets)) {
30-
delete globalStylesheets[key];
31-
}
32-
};
33-
}
34-
35-
const globalStylesheetsParentElement: Element = document.head || document.body || document;
36-
// This check for constructable stylesheets is similar to Fast's:
37-
// https://github.com/microsoft/fast/blob/d49d1ec/packages/web-components/fast-element/src/dom.ts#L51-L53
38-
// See also: https://github.com/whatwg/webidl/issues/1027#issuecomment-934510070
39-
const supportsConstructableStyleSheets =
40-
isFunction(CSSStyleSheet.prototype.replaceSync) && isArray(document.adoptedStyleSheets);
41-
const supportsMutableAdoptedStyleSheets =
42-
supportsConstructableStyleSheets &&
43-
getOwnPropertyDescriptor(document.adoptedStyleSheets, 'length')!.writable;
44-
const styleElements: { [content: string]: HTMLStyleElement } = create(null);
45-
const styleSheets: { [content: string]: CSSStyleSheet } = create(null);
46-
const shadowRootsToStyleSheets = new WeakMap<ShadowRoot, { [content: string]: true }>();
20+
export { insertStylesheet } from './styles';
4721

4822
export let getCustomElement: any;
4923
export let defineCustomElement: any;
@@ -72,53 +46,6 @@ function isCustomElementRegistryAvailable() {
7246
}
7347
}
7448

75-
function insertConstructableStyleSheet(content: string, target: ShadowRoot) {
76-
// It's important for CSSStyleSheets to be unique based on their content, so that
77-
// `shadowRoot.adoptedStyleSheets.includes(sheet)` works.
78-
let styleSheet = styleSheets[content];
79-
if (isUndefined(styleSheet)) {
80-
styleSheet = new CSSStyleSheet();
81-
styleSheet.replaceSync(content);
82-
styleSheets[content] = styleSheet;
83-
}
84-
const { adoptedStyleSheets } = target;
85-
if (!adoptedStyleSheets.includes(styleSheet)) {
86-
if (supportsMutableAdoptedStyleSheets) {
87-
// This is only supported in later versions of Chromium:
88-
// https://chromestatus.com/feature/5638996492288000
89-
adoptedStyleSheets.push(styleSheet);
90-
} else {
91-
target.adoptedStyleSheets = [...adoptedStyleSheets, styleSheet];
92-
}
93-
}
94-
}
95-
96-
function insertStyleElement(content: string, target: ShadowRoot) {
97-
// Avoid inserting duplicate `<style>`s
98-
let sheets = shadowRootsToStyleSheets.get(target);
99-
if (isUndefined(sheets)) {
100-
sheets = create(null);
101-
shadowRootsToStyleSheets.set(target, sheets!);
102-
}
103-
if (sheets![content]) {
104-
return;
105-
}
106-
sheets![content] = true;
107-
108-
// This `<style>` may be repeated multiple times in the DOM, so cache it. It's a bit
109-
// faster to call `cloneNode()` on an existing node than to recreate it every time.
110-
let elm = styleElements[content];
111-
if (isUndefined(elm)) {
112-
elm = document.createElement('style');
113-
elm.type = 'text/css';
114-
elm.textContent = content;
115-
styleElements[content] = elm;
116-
} else {
117-
elm = elm.cloneNode(true) as HTMLStyleElement;
118-
}
119-
target.appendChild(elm);
120-
}
121-
12249
if (isCustomElementRegistryAvailable()) {
12350
getCustomElement = customElements.get.bind(customElements);
12451
defineCustomElement = customElements.define.bind(customElements);
@@ -349,29 +276,6 @@ export function isConnected(node: Node): boolean {
349276
return node.isConnected;
350277
}
351278

352-
export function insertGlobalStylesheet(content: string): void {
353-
if (!isUndefined(globalStylesheets[content])) {
354-
return;
355-
}
356-
357-
globalStylesheets[content] = true;
358-
359-
const elm = document.createElement('style');
360-
elm.type = 'text/css';
361-
elm.textContent = content;
362-
363-
globalStylesheetsParentElement.appendChild(elm);
364-
}
365-
366-
export function insertStylesheet(content: string, target: ShadowRoot): void {
367-
if (supportsConstructableStyleSheets) {
368-
insertConstructableStyleSheet(content, target);
369-
} else {
370-
// Fall back to <style> element
371-
insertStyleElement(content, target);
372-
}
373-
}
374-
375279
export function assertInstanceOfHTMLElement(elm: any, msg: string) {
376280
assert.invariant(elm instanceof HTMLElement, msg);
377281
}

0 commit comments

Comments
 (0)