diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 777f9677d04..4fd852308c3 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1,6 +1,7 @@ import { defineAsyncComponent, defineCustomElement, + defineComponent, h, inject, nextTick, @@ -339,6 +340,51 @@ describe('defineCustomElement', () => { const style = el.shadowRoot?.querySelector('style')! expect(style.textContent).toBe(`div { color: red; }`) }) + + test('should attach styles of children components to shadow dom', () => { + const Bar = defineComponent({ + styles: [`.green-color { color: green; }`], + render() { + return h( + 'h1', + { + attrs: { class: 'green-color' } + }, + 'hello' + ) + } + }) + const Foo = defineComponent({ + components: { Bar }, + styles: [`.blue-back { color: blue; }`], + render() { + return h( + 'span', + { + attrs: { class: 'blue-back' } + }, + '' + ) + } + }) + + const FooBar = defineCustomElement({ + components: { Foo }, + styles: [`div { color: red; }`], + render() { + return h('div', '') + } + }) + customElements.define('my-el-with-nested-styles', FooBar) + container.innerHTML = `` + const el = container.childNodes[0] as VueElement + const style = el.shadowRoot?.querySelectorAll('style')! + + expect(style.length).toBe(3) + expect(style[0].textContent).toBe(`.green-color { color: green; }`) + expect(style[1].textContent).toBe(`.blue-back { color: blue; }`) + expect(style[2].textContent).toBe(`div { color: red; }`) + }) }) describe('async', () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 059fcac16f1..89e267fd139 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -19,7 +19,8 @@ import { nextTick, warn, ConcreteComponent, - ComponentOptions + ComponentOptions, + Component } from '@vue/runtime-core' import { camelize, extend, hyphenate, isArray, toNumber } from '@vue/shared' import { hydrate, render } from '.' @@ -215,6 +216,7 @@ export class VueElement extends BaseClass { }).observe(this, { attributes: true }) const resolve = (def: InnerComponentDef) => { + const { props, styles } = def const hasOptions = !isArray(props) const rawKeys = props ? (hasOptions ? Object.keys(props) : props) : [] @@ -250,12 +252,11 @@ export class VueElement extends BaseClass { } }) } - - // apply CSS - this._applyStyles(styles) + this._applyStyles(this._getChildrenComponentsStyles(def)) // initial render this._update() + } const asyncDef = (this._def as ComponentOptions).__asyncLoader @@ -375,4 +376,42 @@ export class VueElement extends BaseClass { }) } } + + private _getChildrenComponentsStyles( + component: Component & { + components?: Record + styles?: string[] + } + ): string[] { + let componentStyles: string[] = [] + + if (component.components) { + componentStyles = Object.values(component.components).reduce( + ( + aggregatedStyles: string[], + nestedComponent: Component & { + components?: Record + styles?: string[] + } + ) => { + if (nestedComponent?.components) { + aggregatedStyles = [ + ...aggregatedStyles, + ...this._getChildrenComponentsStyles(nestedComponent) + ] + } + return nestedComponent.styles + ? [...aggregatedStyles, ...nestedComponent.styles] + : aggregatedStyles + }, + [] as string[] + ) + } + + if (component.styles) { + componentStyles.push(...component.styles) + } + + return [...new Set(componentStyles)] + } }