diff --git a/packages/@lwc/engine-core/src/framework/renderer.ts b/packages/@lwc/engine-core/src/framework/renderer.ts index 851098b84a..a9ea488791 100644 --- a/packages/@lwc/engine-core/src/framework/renderer.ts +++ b/packages/@lwc/engine-core/src/framework/renderer.ts @@ -46,6 +46,7 @@ export interface Renderer { getElementsByClassName(element: E, names: string): HTMLCollection; isConnected(node: N): boolean; insertGlobalStylesheet(content: string): void; + insertStylesheet(content: string, target: N): void; assertInstanceOfHTMLElement?(elm: any, msg: string): void; defineCustomElement( name: string, diff --git a/packages/@lwc/engine-core/src/framework/stylesheet.ts b/packages/@lwc/engine-core/src/framework/stylesheet.ts index 3e6d7ca68a..1c8af5028f 100644 --- a/packages/@lwc/engine-core/src/framework/stylesheet.ts +++ b/packages/@lwc/engine-core/src/framework/stylesheet.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import { isArray, isUndefined, ArrayJoin, ArrayPush } from '@lwc/shared'; +import { isArray, isUndefined, ArrayJoin, ArrayPush, isNull } from '@lwc/shared'; import * as api from './api'; import { VNode } from '../3rdparty/snabbdom/types'; @@ -138,16 +138,42 @@ export function getStylesheetsContent(vm: VM, template: Template): string[] { return content; } +// It might be worth caching this to avoid doing the lookup repeatedly, but +// perf testing has not shown it to be a huge improvement yet: +// https://github.com/salesforce/lwc/pull/2460#discussion_r691208892 +function getNearestNativeShadowComponent(vm: VM): VM | null { + let owner: VM | null = vm; + while (!isNull(owner)) { + if (owner.renderMode === RenderMode.Shadow && owner.shadowMode === ShadowMode.Native) { + return owner; + } + owner = owner.owner; + } + return owner; +} + export function createStylesheet(vm: VM, stylesheets: string[]): VNode | null { const { renderer, renderMode, shadowMode } = vm; if (renderMode === RenderMode.Shadow && shadowMode === ShadowMode.Synthetic) { for (let i = 0; i < stylesheets.length; i++) { renderer.insertGlobalStylesheet(stylesheets[i]); } - return null; - } else { - // native shadow or light DOM + } else if (renderer.ssr) { + // native shadow or light DOM, SSR const combinedStylesheetContent = ArrayJoin.call(stylesheets, '\n'); return createInlineStyleVNode(combinedStylesheetContent); + } else { + // native shadow or light DOM, DOM renderer + const root = getNearestNativeShadowComponent(vm); + const isGlobal = isNull(root); + for (let i = 0; i < stylesheets.length; i++) { + if (isGlobal) { + renderer.insertGlobalStylesheet(stylesheets[i]); + } else { + // local level + renderer.insertStylesheet(stylesheets[i], root!.cmpRoot); + } + } } + return null; } diff --git a/packages/@lwc/engine-dom/src/renderer.ts b/packages/@lwc/engine-dom/src/renderer.ts index 38ac201530..25757dfe84 100644 --- a/packages/@lwc/engine-dom/src/renderer.ts +++ b/packages/@lwc/engine-dom/src/renderer.ts @@ -10,6 +10,7 @@ import { create, hasOwnProperty, htmlPropertyToAttribute, + isFunction, isUndefined, KEY__SHADOW_TOKEN, setPrototypeOf, @@ -29,6 +30,10 @@ if (process.env.NODE_ENV === 'development') { } const globalStylesheetsParentElement: Element = document.head || document.body || document; +const supportsConstructableStyleSheets = isFunction((CSSStyleSheet.prototype as any).replaceSync); +const styleElements: { [content: string]: HTMLStyleElement } = create(null); +const styleSheets: { [content: string]: CSSStyleSheet } = create(null); +const nodesToStyleSheets = new WeakMap(); let getCustomElement, defineCustomElement, HTMLElementConstructor; @@ -54,6 +59,46 @@ function isCustomElementRegistryAvailable() { } } +function insertConstructableStyleSheet(content: string, target: Node) { + // 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 as any).replaceSync(content); + styleSheets[content] = styleSheet; + } + if (!(target as any).adoptedStyleSheets.includes(styleSheet)) { + (target as any).adoptedStyleSheets = [...(target as any).adoptedStyleSheets, styleSheet]; + } +} + +function insertStyleElement(content: string, target: Node) { + // Avoid inserting duplicate `