From e2bc36ffec1551c60f5b8461017a93acd8a4627b Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Wed, 11 May 2022 10:55:14 -0700 Subject: [PATCH] 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 --- .../engine-core/src/framework/hydration.ts | 1 + .../@lwc/engine-core/src/framework/main.ts | 3 +- .../engine-core/src/framework/stylesheet.ts | 24 +-- packages/@lwc/engine-core/src/framework/vm.ts | 6 +- packages/@lwc/engine-core/src/renderer.ts | 9 +- .../engine-dom/src/apis/hydrate-component.ts | 1 + .../@lwc/engine-dom/src/initializeRenderer.ts | 9 +- packages/@lwc/engine-dom/src/renderer.ts | 98 +---------- packages/@lwc/engine-dom/src/styles.ts | 152 ++++++++++++++++++ .../engine-server/src/initializeRenderer.ts | 9 +- packages/@lwc/engine-server/src/renderer.ts | 4 - .../integration-karma/helpers/test-setup.js | 43 ++++- .../test/misc/clean-dom/index.spec.js | 12 ++ 13 files changed, 221 insertions(+), 150 deletions(-) create mode 100644 packages/@lwc/engine-dom/src/styles.ts diff --git a/packages/@lwc/engine-core/src/framework/hydration.ts b/packages/@lwc/engine-core/src/framework/hydration.ts index 76b6c64b76..d741e21548 100644 --- a/packages/@lwc/engine-core/src/framework/hydration.ts +++ b/packages/@lwc/engine-core/src/framework/hydration.ts @@ -204,6 +204,7 @@ function hydrateCustomElement(elm: Node, vnode: VCustomElement): Node | null { mode, owner, tagName: sel, + hydrated: true, }); vnode.elm = elm; diff --git a/packages/@lwc/engine-core/src/framework/main.ts b/packages/@lwc/engine-core/src/framework/main.ts index 816bbe4d22..a4ff3815f4 100644 --- a/packages/@lwc/engine-core/src/framework/main.ts +++ b/packages/@lwc/engine-core/src/framework/main.ts @@ -80,8 +80,6 @@ export { setGetProperty, setHTMLElement, setInsert, - setInsertGlobalStylesheet, - setInsertStylesheet, setIsConnected, setIsHydrating, setIsNativeShadowDefined, @@ -98,4 +96,5 @@ export { setSetText, setSsr, setAddEventListener, + setInsertStylesheet, } from '../renderer'; diff --git a/packages/@lwc/engine-core/src/framework/stylesheet.ts b/packages/@lwc/engine-core/src/framework/stylesheet.ts index 40cec3f838..270ef25dde 100644 --- a/packages/@lwc/engine-core/src/framework/stylesheet.ts +++ b/packages/@lwc/engine-core/src/framework/stylesheet.ts @@ -6,15 +6,7 @@ */ import { ArrayJoin, ArrayPush, isArray, isNull, isUndefined, KEY__SCOPED_CSS } from '@lwc/shared'; -import { - getClassList, - removeAttribute, - setAttribute, - insertGlobalStylesheet, - ssr, - isHydrating, - insertStylesheet, -} from '../renderer'; +import { getClassList, removeAttribute, setAttribute, ssr, insertStylesheet } from '../renderer'; import api from './api'; import { RenderMode, ShadowMode, VM } from './vm'; @@ -207,9 +199,9 @@ export function createStylesheet(vm: VM, stylesheets: string[]): VNode | null { const { renderMode, shadowMode } = vm; if (renderMode === RenderMode.Shadow && shadowMode === ShadowMode.Synthetic) { for (let i = 0; i < stylesheets.length; i++) { - insertGlobalStylesheet(stylesheets[i]); + insertStylesheet(stylesheets[i]); } - } else if (ssr || isHydrating()) { + } else if (ssr || vm.hydrated) { // Note: We need to ensure that during hydration, the stylesheets method is the same as those in ssr. // This works in the client, because the stylesheets are created, and cached in the VM // the first time the VM renders. @@ -220,14 +212,10 @@ export function createStylesheet(vm: VM, stylesheets: string[]): VNode | null { } else { // native shadow or light DOM, DOM renderer const root = getNearestNativeShadowComponent(vm); - const isGlobal = isNull(root); + // null root means a global style + const target = isNull(root) ? undefined : root.shadowRoot!; for (let i = 0; i < stylesheets.length; i++) { - if (isGlobal) { - insertGlobalStylesheet(stylesheets[i]); - } else { - // local level - insertStylesheet(stylesheets[i], root!.shadowRoot!); - } + insertStylesheet(stylesheets[i], target); } } return null; diff --git a/packages/@lwc/engine-core/src/framework/vm.ts b/packages/@lwc/engine-core/src/framework/vm.ts index fdb1a2bc1a..c8c29af0b3 100644 --- a/packages/@lwc/engine-core/src/framework/vm.ts +++ b/packages/@lwc/engine-core/src/framework/vm.ts @@ -111,6 +111,8 @@ export interface VM { readonly context: Context; /** The owner VM or null for root elements. */ readonly owner: VM | null; + /** Whether or not the VM was hydrated */ + readonly hydrated: boolean; /** Rendering operations associated with the VM */ readonly renderMode: RenderMode; shadowMode: ShadowMode; @@ -263,9 +265,10 @@ export function createVM( mode: ShadowRootMode; owner: VM | null; tagName: string; + hydrated?: boolean; } ): VM { - const { mode, owner, tagName } = options; + const { mode, owner, tagName, hydrated } = options; const def = getComponentInternalDef(ctor); const vm: VM = { @@ -286,6 +289,7 @@ export function createVM( cmpSlots: create(null), oar: create(null), cmpTemplate: null, + hydrated: Boolean(hydrated), renderMode: def.renderMode, shadowMode: computeShadowMode(def, owner), diff --git a/packages/@lwc/engine-core/src/renderer.ts b/packages/@lwc/engine-core/src/renderer.ts index 4d8cf91ca5..002d4df0c3 100644 --- a/packages/@lwc/engine-core/src/renderer.ts +++ b/packages/@lwc/engine-core/src/renderer.ts @@ -245,14 +245,7 @@ export let isConnected: isConnectedFunc; export function setIsConnected(isConnectedImpl: isConnectedFunc) { isConnected = isConnectedImpl; } - -type insertGlobalStylesheetFunc = (content: string) => void; -export let insertGlobalStylesheet: insertGlobalStylesheetFunc; -export function setInsertGlobalStylesheet(insertGlobalStylesheetImpl: insertGlobalStylesheetFunc) { - insertGlobalStylesheet = insertGlobalStylesheetImpl; -} - -type insertStylesheetFunc = (content: string, target: ShadowRoot) => void; +type insertStylesheetFunc = (content: string, target?: ShadowRoot) => void; export let insertStylesheet: insertStylesheetFunc; export function setInsertStylesheet(insertStylesheetImpl: insertStylesheetFunc) { insertStylesheet = insertStylesheetImpl; diff --git a/packages/@lwc/engine-dom/src/apis/hydrate-component.ts b/packages/@lwc/engine-dom/src/apis/hydrate-component.ts index 8aca99f4d0..335ebc0aba 100644 --- a/packages/@lwc/engine-dom/src/apis/hydrate-component.ts +++ b/packages/@lwc/engine-dom/src/apis/hydrate-component.ts @@ -36,6 +36,7 @@ function createVMWithProps(element: Element, Ctor: typeof LightningElement, prop mode: 'open', owner: null, tagName: element.tagName.toLowerCase(), + hydrated: true, }); for (const [key, value] of Object.entries(props)) { diff --git a/packages/@lwc/engine-dom/src/initializeRenderer.ts b/packages/@lwc/engine-dom/src/initializeRenderer.ts index 24b92b3a3e..0629aad941 100644 --- a/packages/@lwc/engine-dom/src/initializeRenderer.ts +++ b/packages/@lwc/engine-dom/src/initializeRenderer.ts @@ -28,8 +28,6 @@ import { setGetProperty, setHTMLElement, setInsert, - setInsertGlobalStylesheet, - setInsertStylesheet, setIsConnected, setIsHydrating, setIsNativeShadowDefined, @@ -46,6 +44,7 @@ import { setSetText, setSsr, setAddEventListener, + setInsertStylesheet, } from '@lwc/engine-core'; import { @@ -71,8 +70,6 @@ import { getProperty, HTMLElement, insert, - insertGlobalStylesheet, - insertStylesheet, isConnected, isHydrating, isNativeShadowDefined, @@ -89,6 +86,7 @@ import { setText, ssr, addEventListener, + insertStylesheet, } from './renderer'; setAssertInstanceOfHTMLElement(assertInstanceOfHTMLElement); @@ -113,8 +111,6 @@ setGetLastElementChild(getLastElementChild); setGetProperty(getProperty); setHTMLElement(HTMLElement); setInsert(insert); -setInsertGlobalStylesheet(insertGlobalStylesheet); -setInsertStylesheet(insertStylesheet); setIsConnected(isConnected); setIsHydrating(isHydrating); setIsNativeShadowDefined(isNativeShadowDefined); @@ -131,3 +127,4 @@ setSetProperty(setProperty); setSetText(setText); setSsr(ssr); setAddEventListener(addEventListener); +setInsertStylesheet(insertStylesheet); diff --git a/packages/@lwc/engine-dom/src/renderer.ts b/packages/@lwc/engine-dom/src/renderer.ts index 7aae935623..d2b947692a 100644 --- a/packages/@lwc/engine-dom/src/renderer.ts +++ b/packages/@lwc/engine-dom/src/renderer.ts @@ -11,39 +11,13 @@ import { hasOwnProperty, htmlPropertyToAttribute, globalThis, - isFunction, isUndefined, - isArray, KEY__IS_NATIVE_SHADOW_ROOT_DEFINED, KEY__SHADOW_TOKEN, setPrototypeOf, StringToLowerCase, - getOwnPropertyDescriptor, } from '@lwc/shared'; - -const globalStylesheets: { [content: string]: true } = create(null); - -if (process.env.NODE_ENV === 'development') { - // @ts-ignore - window.__lwcResetGlobalStylesheets = () => { - for (const key of Object.keys(globalStylesheets)) { - delete globalStylesheets[key]; - } - }; -} - -const globalStylesheetsParentElement: Element = document.head || document.body || document; -// This check for constructable stylesheets is similar to Fast's: -// https://github.com/microsoft/fast/blob/d49d1ec/packages/web-components/fast-element/src/dom.ts#L51-L53 -// See also: https://github.com/whatwg/webidl/issues/1027#issuecomment-934510070 -const supportsConstructableStyleSheets = - isFunction(CSSStyleSheet.prototype.replaceSync) && isArray(document.adoptedStyleSheets); -const supportsMutableAdoptedStyleSheets = - supportsConstructableStyleSheets && - getOwnPropertyDescriptor(document.adoptedStyleSheets, 'length')!.writable; -const styleElements: { [content: string]: HTMLStyleElement } = create(null); -const styleSheets: { [content: string]: CSSStyleSheet } = create(null); -const shadowRootsToStyleSheets = new WeakMap(); +export { insertStylesheet } from './styles'; export let getCustomElement: any; export let defineCustomElement: any; @@ -72,53 +46,6 @@ function isCustomElementRegistryAvailable() { } } -function insertConstructableStyleSheet(content: string, target: ShadowRoot) { - // It's important for CSSStyleSheets to be unique based on their content, so that - // `shadowRoot.adoptedStyleSheets.includes(sheet)` works. - let styleSheet = styleSheets[content]; - if (isUndefined(styleSheet)) { - styleSheet = new CSSStyleSheet(); - styleSheet.replaceSync(content); - styleSheets[content] = styleSheet; - } - const { adoptedStyleSheets } = target; - if (!adoptedStyleSheets.includes(styleSheet)) { - if (supportsMutableAdoptedStyleSheets) { - // This is only supported in later versions of Chromium: - // https://chromestatus.com/feature/5638996492288000 - adoptedStyleSheets.push(styleSheet); - } else { - target.adoptedStyleSheets = [...adoptedStyleSheets, styleSheet]; - } - } -} - -function insertStyleElement(content: string, target: ShadowRoot) { - // Avoid inserting duplicate `