diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts
index f191c36df12..2c73e0fa308 100644
--- a/packages/runtime-core/src/component.ts
+++ b/packages/runtime-core/src/component.ts
@@ -1256,7 +1256,7 @@ export interface ComponentCustomElementInterface {
/**
* @internal
*/
- _injectChildStyle(type: ConcreteComponent): void
+ _injectChildStyle(type: ConcreteComponent, parent?: ConcreteComponent): void
/**
* @internal
*/
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 7b39aa917a2..6e31cb01013 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -1349,7 +1349,8 @@ function baseCreateRenderer(
} else {
// custom element style injection
if (root.ce) {
- root.ce._injectChildStyle(type)
+ const parent = instance.parent ? instance.parent.type : undefined
+ root.ce._injectChildStyle(type, parent)
}
if (__DEV__) {
diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts
index eee2151716e..23d5ffed0fa 100644
--- a/packages/runtime-dom/__tests__/customElement.spec.ts
+++ b/packages/runtime-dom/__tests__/customElement.spec.ts
@@ -916,6 +916,110 @@ describe('defineCustomElement', () => {
assertStyles(el, [`div { color: blue; }`, `div { color: red; }`])
})
+ test('inject child component styles before parent styles', async () => {
+ const Baz = () => h(Bar)
+ const Bar = defineComponent({
+ styles: [`div { color: green; }`],
+ render() {
+ return 'bar'
+ },
+ })
+ const WrapperBar = defineComponent({
+ styles: [`div { color: blue; }`],
+ render() {
+ return h(Baz)
+ },
+ })
+ const WBaz = () => h(WrapperBar)
+ const Foo = defineCustomElement({
+ styles: [`div { color: red; }`],
+ render() {
+ return [h(Baz), h(WBaz)]
+ },
+ })
+ customElements.define('my-el-with-wrapper-child-styles', Foo)
+ container.innerHTML = ``
+ const el = container.childNodes[0] as VueElement
+
+ // inject order should be child -> parent
+ assertStyles(el, [
+ `div { color: green; }`,
+ `div { color: blue; }`,
+ `div { color: red; }`,
+ ])
+ })
+
+ test('inject child component styles when parent has no styles', async () => {
+ const Baz = () => h(Bar)
+ const Bar = defineComponent({
+ styles: [`div { color: green; }`],
+ render() {
+ return 'bar'
+ },
+ })
+ const WrapperBar = defineComponent({
+ styles: [`div { color: blue; }`],
+ render() {
+ return h(Baz)
+ },
+ })
+ const WBaz = () => h(WrapperBar)
+ // without styles
+ const Foo = defineCustomElement({
+ render() {
+ return [h(Baz), h(WBaz)]
+ },
+ })
+ customElements.define('my-el-with-inject-child-styles', Foo)
+ container.innerHTML = ``
+ const el = container.childNodes[0] as VueElement
+
+ assertStyles(el, [`div { color: green; }`, `div { color: blue; }`])
+ })
+
+ test('inject nested child component styles', async () => {
+ const Baz = defineComponent({
+ styles: [`div { color: yellow; }`],
+ render() {
+ return h(Bar)
+ },
+ })
+ const Bar = defineComponent({
+ styles: [`div { color: green; }`],
+ render() {
+ return 'bar'
+ },
+ })
+ const WrapperBar = defineComponent({
+ styles: [`div { color: blue; }`],
+ render() {
+ return h(Baz)
+ },
+ })
+ const WBaz = defineComponent({
+ styles: [`div { color: black; }`],
+ render() {
+ return h(WrapperBar)
+ },
+ })
+ const Foo = defineCustomElement({
+ styles: [`div { color: red; }`],
+ render() {
+ return [h(Baz), h(WBaz)]
+ },
+ })
+ customElements.define('my-el-with-inject-nested-child-styles', Foo)
+ container.innerHTML = ``
+ const el = container.childNodes[0] as VueElement
+ assertStyles(el, [
+ `div { color: green; }`,
+ `div { color: yellow; }`,
+ `div { color: blue; }`,
+ `div { color: black; }`,
+ `div { color: red; }`,
+ ])
+ })
+
test('with nonce', () => {
const Foo = defineCustomElement(
{
diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts
index 56b86a5fd9e..5de32735fd5 100644
--- a/packages/runtime-dom/src/apiCustomElement.ts
+++ b/packages/runtime-dom/src/apiCustomElement.ts
@@ -232,6 +232,8 @@ export class VueElement
private _styleChildren = new WeakSet()
private _pendingResolve: Promise | undefined
private _parent: VueElement | undefined
+ private _styleAnchors: WeakMap =
+ new WeakMap()
/**
* dev only
*/
@@ -584,6 +586,7 @@ export class VueElement
private _applyStyles(
styles: string[] | undefined,
owner?: ConcreteComponent,
+ parentComp?: ConcreteComponent & CustomElementOptions,
) {
if (!styles) return
if (owner) {
@@ -592,12 +595,43 @@ export class VueElement
}
this._styleChildren.add(owner)
}
+
+ // if parent has no styles but child does, create an anchor
+ // to inject child styles before it.
+ if (parentComp && !parentComp.styles) {
+ const anchor = document.createTextNode('')
+ const styleAnchor = this._styleAnchors.get(this._def)
+ if (styleAnchor) {
+ this.shadowRoot!.insertBefore(anchor, styleAnchor)
+ } else {
+ this.shadowRoot!.prepend(anchor)
+ }
+ this._styleAnchors.set(this._def, anchor)
+ }
+
const nonce = this._nonce
+ let last = undefined
for (let i = styles.length - 1; i >= 0; i--) {
const s = document.createElement('style')
if (nonce) s.setAttribute('nonce', nonce)
s.textContent = styles[i]
- this.shadowRoot!.prepend(s)
+
+ // inject styles before parent styles
+ if (parentComp) {
+ this.shadowRoot!.insertBefore(
+ s,
+ last ||
+ this._styleAnchors.get(parentComp) ||
+ this._styleAnchors.get(this._def) ||
+ null,
+ )
+ } else {
+ this.shadowRoot!.prepend(s)
+ this._styleAnchors.set(this._def, s)
+ }
+ last = s
+ if (owner && i === 0) this._styleAnchors.set(owner, s)
+
// record for HMR
if (__DEV__) {
if (owner) {
@@ -665,8 +699,11 @@ export class VueElement
/**
* @internal
*/
- _injectChildStyle(comp: ConcreteComponent & CustomElementOptions): void {
- this._applyStyles(comp.styles, comp)
+ _injectChildStyle(
+ comp: ConcreteComponent & CustomElementOptions,
+ parentComp?: ConcreteComponent,
+ ): void {
+ this._applyStyles(comp.styles, comp, parentComp)
}
/**