diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 07ea091486e..79ad4d36f54 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -852,12 +852,13 @@ describe('defineCustomElement', () => { return h('div', 'hello') }, }) - const Foo = defineCustomElement(def) + const Foo = defineCustomElement(def, { + extraStyles: [`div { color: green; }`], + }) customElements.define('my-el-with-styles', Foo) container.innerHTML = `` const el = container.childNodes[0] as VueElement - const style = el.shadowRoot?.querySelector('style')! - expect(style.textContent).toBe(`div { color: red; }`) + assertStyles(el, [`div { color: red; }`, `div { color: green; }`]) // hmr __VUE_HMR_RUNTIME__.reload('foo', { @@ -866,7 +867,52 @@ describe('defineCustomElement', () => { } as any) await nextTick() - assertStyles(el, [`div { color: blue; }`, `div { color: yellow; }`]) + assertStyles(el, [ + `div { color: blue; }`, + `div { color: yellow; }`, + `div { color: green; }`, + ]) + }) + + test('extraStyles apply once and are not duplicated by child injections (and persist across child HMR)', async () => { + const Child = defineComponent({ + __hmrId: 'child-extra', + styles: [`div { color: purple; }`], + render() { + return h('div', 'child') + }, + }) + const Parent = defineCustomElement( + { + __hmrId: 'parent-extra', + styles: [`div { color: red; }`], + render() { + return h(Child) + }, + }, + { extraStyles: [`div { color: green; }`] }, + ) + customElements.define('my-el-extra-child', Parent) + container.innerHTML = `` + const el = container.childNodes[0] as VueElement + // Order: child (prepended after mount) -> parent -> extra + assertStyles(el, [ + `div { color: purple; }`, + `div { color: red; }`, + `div { color: green; }`, + ]) + + // HMR child: extraStyles should remain and not duplicate or be removed + __VUE_HMR_RUNTIME__.reload('child-extra', { + ...Child, + styles: [`div { color: orange; }`], + } as any) + await nextTick() + assertStyles(el, [ + `div { color: orange; }`, + `div { color: red; }`, + `div { color: green; }`, + ]) }) test("child components should inject styles to root element's shadow root", async () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index edf7c431353..6223b36bb2b 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -52,6 +52,7 @@ export type VueElementConstructor

= { export interface CustomElementOptions { styles?: string[] + extraStyles?: string[] shadowRoot?: boolean nonce?: string configureApp?: (app: App) => void @@ -220,6 +221,10 @@ export class VueElement * @internal */ _nonce: string | undefined = this._def.nonce + /** + * @internal + */ + _extraStyles?: string[] = this._def.extraStyles /** * @internal @@ -391,7 +396,7 @@ export class VueElement // apply CSS if (this.shadowRoot) { this._applyStyles(styles) - } else if (__DEV__ && styles) { + } else if (__DEV__ && (styles || this._extraStyles)) { warn( 'Custom element style injection is not supported when using ' + 'shadowRoot: false', @@ -586,7 +591,17 @@ export class VueElement styles: string[] | undefined, owner?: ConcreteComponent, ) { - if (!styles) return + const fullStyles: string[] = [] + if (styles && styles.length) { + fullStyles.push(...styles) + } + // Only apply extraStyles for the root element injection. + if (!owner) { + const { _extraStyles } = this + _extraStyles && fullStyles.push(..._extraStyles) + } + if (!fullStyles.length) return + if (owner) { if (owner === this._def || this._styleChildren.has(owner)) { return @@ -594,10 +609,10 @@ export class VueElement this._styleChildren.add(owner) } const nonce = this._nonce - for (let i = styles.length - 1; i >= 0; i--) { + for (let i = fullStyles.length - 1; i >= 0; i--) { const s = document.createElement('style') if (nonce) s.setAttribute('nonce', nonce) - s.textContent = styles[i] + s.textContent = fullStyles[i] this.shadowRoot!.prepend(s) // record for HMR if (__DEV__) {