From 665f2ae121ec31d65cf22bd577f12fb1d9ffa4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20L=C3=BCnborg?= Date: Fri, 11 Nov 2022 05:33:17 +0100 Subject: [PATCH] fix(custom-elements): fix event emitting for async custom elements (#5601) fix #5599 --- .../runtime-core/src/apiAsyncComponent.ts | 11 +++++---- packages/runtime-core/src/hmr.ts | 8 ------- .../__tests__/customElement.spec.ts | 24 ++++++++++++++++++- packages/runtime-dom/src/apiCustomElement.ts | 9 ++----- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/runtime-core/src/apiAsyncComponent.ts b/packages/runtime-core/src/apiAsyncComponent.ts index 41ccd94a744..09a857bc08b 100644 --- a/packages/runtime-core/src/apiAsyncComponent.ts +++ b/packages/runtime-core/src/apiAsyncComponent.ts @@ -211,13 +211,16 @@ export function defineAsyncComponent< function createInnerComp( comp: ConcreteComponent, - { - vnode: { ref, props, children, shapeFlag }, - parent - }: ComponentInternalInstance + parent: ComponentInternalInstance ) { + const { ref, props, children, ce } = parent.vnode const vnode = createVNode(comp, props, children) // ensure inner component inherits the async wrapper's ref owner vnode.ref = ref + // pass the custom element callback on to the inner comp + // and remove it from the async wrapper + vnode.ce = ce + delete parent.vnode.ce + return vnode } diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 3c3f5208bcc..c5039f62b6f 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -136,14 +136,6 @@ function reload(id: string, newComp: HMRComponent) { // components to be unmounted and re-mounted. Queue the update so that we // don't end up forcing the same parent to re-render multiple times. queueJob(instance.parent.update) - // instance is the inner component of an async custom element - // invoke to reset styles - if ( - (instance.parent.type as ComponentOptions).__asyncLoader && - instance.parent.ceReload - ) { - instance.parent.ceReload((newComp as any).styles) - } } else if (instance.appContext.reload) { // root instance mounted via createApp() has a reload method instance.appContext.reload() diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index eccbe4d425f..68049efe7a3 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1,5 +1,6 @@ import { defineAsyncComponent, + defineComponent, defineCustomElement, h, inject, @@ -227,7 +228,7 @@ describe('defineCustomElement', () => { }) describe('emits', () => { - const E = defineCustomElement({ + const CompDef = defineComponent({ setup(_, { emit }) { emit('created') return () => @@ -241,6 +242,7 @@ describe('defineCustomElement', () => { }) } }) + const E = defineCustomElement(CompDef) customElements.define('my-el-emits', E) test('emit on connect', () => { @@ -277,6 +279,26 @@ describe('defineCustomElement', () => { expect(spy1).toHaveBeenCalledTimes(1) expect(spy2).toHaveBeenCalledTimes(1) }) + + test('emit from within async component wrapper', async () => { + const E = defineCustomElement( + defineAsyncComponent( + () => new Promise(res => res(CompDef as any)) + ) + ) + customElements.define('my-async-el-emits', E) + container.innerHTML = `` + const e = container.childNodes[0] as VueElement + const spy = jest.fn() + e.addEventListener('my-click', spy) + // this feels brittle but seems necessary to reach the node in the DOM. + await customElements.whenDefined('my-async-el-emits') + e.shadowRoot!.childNodes[0].dispatchEvent(new CustomEvent('click')) + expect(spy).toHaveBeenCalled() + expect(spy.mock.calls[0][0]).toMatchObject({ + detail: [1] + }) + }) }) describe('slots', () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 92bd2f3ad3d..fc68bb43f26 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -341,13 +341,8 @@ export class VueElement extends BaseClass { this._styles.length = 0 } this._applyStyles(newStyles) - // if this is an async component, ceReload is called from the inner - // component so no need to reload the async wrapper - if (!(this._def as ComponentOptions).__asyncLoader) { - // reload - this._instance = null - this._update() - } + this._instance = null + this._update() } }