From 07d28f879dc00b30899019916cf26824c26c97e0 Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Thu, 13 Mar 2025 15:04:10 +0800 Subject: [PATCH 1/3] fix(custom-element): ensure correct order of nested component styles --- .../__tests__/customElement.spec.ts | 33 +++++++++++++++++++ packages/runtime-dom/src/apiCustomElement.ts | 17 ++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index df438d47eee..3d9505cb0e7 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -791,6 +791,39 @@ describe('defineCustomElement', () => { assertStyles(el, [`div { color: blue; }`, `div { color: red; }`]) }) + test('child components styles should before parent styles', async () => { + const Baz = () => h(Bar) + const Bar = defineComponent({ + styles: [`div { color: green; }`], + render() { + return 'bar' + }, + }) + const WarpperBar = defineComponent({ + styles: [`div { color: blue; }`], + render() { + return h(Baz) + }, + }) + const WBaz = () => h(WarpperBar) + 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('with nonce', () => { const Foo = defineCustomElement( { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index aeeaeec9b9f..98a076fbe13 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -229,7 +229,7 @@ export class VueElement private _connected = false private _resolved = false private _numberProps: Record | null = null - private _styleChildren = new WeakSet() + private _styleChildren = new WeakMap() private _pendingResolve: Promise | undefined private _parent: VueElement | undefined /** @@ -581,18 +581,29 @@ export class VueElement owner?: ConcreteComponent, ) { if (!styles) return + const styleList: HTMLStyleElement[] = [] if (owner) { - if (owner === this._def || this._styleChildren.has(owner)) { + if (owner === this._def) { return } - this._styleChildren.add(owner) + if (this._styleChildren.has(owner)) { + const styleList = this._styleChildren.get(owner)! + styleList.forEach(s => s.remove()) + this.shadowRoot!.prepend(...styleList) + return + } + this._styleChildren.set(owner, styleList) } + const nonce = this._nonce 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) + if (owner) { + styleList.unshift(s) + } // record for HMR if (__DEV__) { if (owner) { From c95366653ac68d93be9c7b4cdf9355bb758ded14 Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Fri, 14 Mar 2025 10:40:23 +0800 Subject: [PATCH 2/3] chore: update --- packages/runtime-dom/src/apiCustomElement.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 98a076fbe13..a99b41b56cf 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -586,10 +586,9 @@ export class VueElement if (owner === this._def) { return } - if (this._styleChildren.has(owner)) { - const styleList = this._styleChildren.get(owner)! - styleList.forEach(s => s.remove()) - this.shadowRoot!.prepend(...styleList) + const styleChild = this._styleChildren.get(owner) + if (styleChild) { + this.shadowRoot!.prepend(...styleChild) return } this._styleChildren.set(owner, styleList) From a03dcca3002cf0275642987aa3a50f218b45bfde Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 21 May 2025 15:10:23 +0800 Subject: [PATCH 3/3] chore: tweaks --- .../__tests__/customElement.spec.ts | 4 ++-- packages/runtime-dom/src/apiCustomElement.ts | 19 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 3d9505cb0e7..47e3ef9ad38 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -799,13 +799,13 @@ describe('defineCustomElement', () => { return 'bar' }, }) - const WarpperBar = defineComponent({ + const WrapperBar = defineComponent({ styles: [`div { color: blue; }`], render() { return h(Baz) }, }) - const WBaz = () => h(WarpperBar) + const WBaz = () => h(WrapperBar) const Foo = defineCustomElement({ styles: [`div { color: red; }`], render() { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index a99b41b56cf..4631eb13447 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -581,17 +581,16 @@ export class VueElement owner?: ConcreteComponent, ) { if (!styles) return - const styleList: HTMLStyleElement[] = [] + const styleElements: HTMLStyleElement[] = [] if (owner) { - if (owner === this._def) { + if (owner === this._def) return + const existingStyles = this._styleChildren.get(owner) + if (existingStyles) { + // move existing styles to the top + this.shadowRoot!.prepend(...existingStyles) return } - const styleChild = this._styleChildren.get(owner) - if (styleChild) { - this.shadowRoot!.prepend(...styleChild) - return - } - this._styleChildren.set(owner, styleList) + this._styleChildren.set(owner, styleElements) } const nonce = this._nonce @@ -600,9 +599,7 @@ export class VueElement if (nonce) s.setAttribute('nonce', nonce) s.textContent = styles[i] this.shadowRoot!.prepend(s) - if (owner) { - styleList.unshift(s) - } + if (owner) styleElements.unshift(s) // record for HMR if (__DEV__) { if (owner) {