Skip to content

Commit

Permalink
fix(engine-dom): refactor stylesheet API for consistency (#2827)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nolanlawson authored May 11, 2022
1 parent 5af18fd commit e2bc36f
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 150 deletions.
1 change: 1 addition & 0 deletions packages/@lwc/engine-core/src/framework/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ function hydrateCustomElement(elm: Node, vnode: VCustomElement): Node | null {
mode,
owner,
tagName: sel,
hydrated: true,
});

vnode.elm = elm;
Expand Down
3 changes: 1 addition & 2 deletions packages/@lwc/engine-core/src/framework/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ export {
setGetProperty,
setHTMLElement,
setInsert,
setInsertGlobalStylesheet,
setInsertStylesheet,
setIsConnected,
setIsHydrating,
setIsNativeShadowDefined,
Expand All @@ -98,4 +96,5 @@ export {
setSetText,
setSsr,
setAddEventListener,
setInsertStylesheet,
} from '../renderer';
24 changes: 6 additions & 18 deletions packages/@lwc/engine-core/src/framework/stylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion packages/@lwc/engine-core/src/framework/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export interface VM<N = HostNode, E = HostElement> {
readonly context: Context;
/** The owner VM or null for root elements. */
readonly owner: VM<N, E> | null;
/** Whether or not the VM was hydrated */
readonly hydrated: boolean;
/** Rendering operations associated with the VM */
readonly renderMode: RenderMode;
shadowMode: ShadowMode;
Expand Down Expand Up @@ -263,9 +265,10 @@ export function createVM<HostNode, HostElement>(
mode: ShadowRootMode;
owner: VM<HostNode, HostElement> | null;
tagName: string;
hydrated?: boolean;
}
): VM {
const { mode, owner, tagName } = options;
const { mode, owner, tagName, hydrated } = options;
const def = getComponentInternalDef(ctor);

const vm: VM = {
Expand All @@ -286,6 +289,7 @@ export function createVM<HostNode, HostElement>(
cmpSlots: create(null),
oar: create(null),
cmpTemplate: null,
hydrated: Boolean(hydrated),

renderMode: def.renderMode,
shadowMode: computeShadowMode(def, owner),
Expand Down
9 changes: 1 addition & 8 deletions packages/@lwc/engine-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/@lwc/engine-dom/src/apis/hydrate-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
9 changes: 3 additions & 6 deletions packages/@lwc/engine-dom/src/initializeRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import {
setGetProperty,
setHTMLElement,
setInsert,
setInsertGlobalStylesheet,
setInsertStylesheet,
setIsConnected,
setIsHydrating,
setIsNativeShadowDefined,
Expand All @@ -46,6 +44,7 @@ import {
setSetText,
setSsr,
setAddEventListener,
setInsertStylesheet,
} from '@lwc/engine-core';

import {
Expand All @@ -71,8 +70,6 @@ import {
getProperty,
HTMLElement,
insert,
insertGlobalStylesheet,
insertStylesheet,
isConnected,
isHydrating,
isNativeShadowDefined,
Expand All @@ -89,6 +86,7 @@ import {
setText,
ssr,
addEventListener,
insertStylesheet,
} from './renderer';

setAssertInstanceOfHTMLElement(assertInstanceOfHTMLElement);
Expand All @@ -113,8 +111,6 @@ setGetLastElementChild(getLastElementChild);
setGetProperty(getProperty);
setHTMLElement(HTMLElement);
setInsert(insert);
setInsertGlobalStylesheet(insertGlobalStylesheet);
setInsertStylesheet(insertStylesheet);
setIsConnected(isConnected);
setIsHydrating(isHydrating);
setIsNativeShadowDefined(isNativeShadowDefined);
Expand All @@ -131,3 +127,4 @@ setSetProperty(setProperty);
setSetText(setText);
setSsr(ssr);
setAddEventListener(addEventListener);
setInsertStylesheet(insertStylesheet);
98 changes: 1 addition & 97 deletions packages/@lwc/engine-dom/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ShadowRoot, { [content: string]: true }>();
export { insertStylesheet } from './styles';

export let getCustomElement: any;
export let defineCustomElement: any;
Expand Down Expand Up @@ -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 `<style>`s
let sheets = shadowRootsToStyleSheets.get(target);
if (isUndefined(sheets)) {
sheets = create(null);
shadowRootsToStyleSheets.set(target, sheets!);
}
if (sheets![content]) {
return;
}
sheets![content] = true;

// This `<style>` may be repeated multiple times in the DOM, so cache it. It's a bit
// faster to call `cloneNode()` on an existing node than to recreate it every time.
let elm = styleElements[content];
if (isUndefined(elm)) {
elm = document.createElement('style');
elm.type = 'text/css';
elm.textContent = content;
styleElements[content] = elm;
} else {
elm = elm.cloneNode(true) as HTMLStyleElement;
}
target.appendChild(elm);
}

if (isCustomElementRegistryAvailable()) {
getCustomElement = customElements.get.bind(customElements);
defineCustomElement = customElements.define.bind(customElements);
Expand Down Expand Up @@ -349,29 +276,6 @@ export function isConnected(node: Node): boolean {
return node.isConnected;
}

export function insertGlobalStylesheet(content: string): void {
if (!isUndefined(globalStylesheets[content])) {
return;
}

globalStylesheets[content] = true;

const elm = document.createElement('style');
elm.type = 'text/css';
elm.textContent = content;

globalStylesheetsParentElement.appendChild(elm);
}

export function insertStylesheet(content: string, target: ShadowRoot): void {
if (supportsConstructableStyleSheets) {
insertConstructableStyleSheet(content, target);
} else {
// Fall back to <style> element
insertStyleElement(content, target);
}
}

export function assertInstanceOfHTMLElement(elm: any, msg: string) {
assert.invariant(elm instanceof HTMLElement, msg);
}
Expand Down
Loading

0 comments on commit e2bc36f

Please sign in to comment.