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)]
+ }
}