Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(custom-element): handle nested customElement mount w/ shadowRoot false #11861

Merged
merged 18 commits into from
Sep 13, 2024
Merged
5 changes: 5 additions & 0 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import type { BaseTransitionProps } from './components/BaseTransition'
import type { DefineComponent } from './apiDefineComponent'
import { markAsyncBoundary } from './helpers/useId'
import { isAsyncWrapper } from './apiAsyncComponent'
import type { RendererElement } from './renderer'

export type Data = Record<string, unknown>

Expand Down Expand Up @@ -1263,4 +1264,8 @@ export interface ComponentCustomElementInterface {
shouldReflect?: boolean,
shouldUpdate?: boolean,
): void
/**
* @internal attached by the nested Teleport when shadowRoot is false.
*/
_teleportTarget?: RendererElement
}
3 changes: 3 additions & 0 deletions packages/runtime-core/src/components/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ export const TeleportImpl = {
// Teleport *always* has Array children. This is enforced in both the
// compiler and vnode children normalization.
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (parentComponent && parentComponent.isCE) {
edison1105 marked this conversation as resolved.
Show resolved Hide resolved
parentComponent.ce!._teleportTarget = container
}
mountChildren(
children as VNodeArrayChildren,
container,
Expand Down
88 changes: 88 additions & 0 deletions packages/runtime-dom/__tests__/customElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MockedFunction } from 'vitest'
import {
type HMRRuntime,
type Ref,
Teleport,
type VueElement,
createApp,
defineAsyncComponent,
Expand Down Expand Up @@ -975,6 +976,93 @@ describe('defineCustomElement', () => {
`<span>default</span>text` + `<!---->` + `<div>fallback</div>`,
)
})

test('render nested customElement w/ shadowRoot false', async () => {
const Child = defineCustomElement(
{
render() {
return renderSlot(this.$slots, 'default')
},
},
{ shadowRoot: false },
)
customElements.define('my-child', Child)

const Parent = defineCustomElement(
{
render() {
return renderSlot(this.$slots, 'default')
},
},
{ shadowRoot: false },
)
customElements.define('my-parent', Parent)

const App = {
render() {
return h('my-parent', null, {
default: () => [
h('my-child', null, {
default: () => [h('span', null, 'default')],
}),
],
})
},
}
const app = createApp(App)
app.mount(container)
await nextTick()
const e = container.childNodes[0] as VueElement
expect(e.innerHTML).toBe(
`<my-child data-v-app=""><span>default</span></my-child>`,
)
app.unmount()
})

test('render nested Teleport w/ shadowRoot false', async () => {
const target = document.createElement('div')
const Child = defineCustomElement(
{
render() {
return h(
Teleport,
{ to: target },
{
default: () => [renderSlot(this.$slots, 'default')],
},
)
},
},
{ shadowRoot: false },
)
customElements.define('my-el-teleport-child', Child)
const Parent = defineCustomElement(
{
render() {
return renderSlot(this.$slots, 'default')
},
},
{ shadowRoot: false },
)
customElements.define('my-el-teleport-parent', Parent)

const App = {
render() {
return h('my-el-teleport-parent', null, {
default: () => [
h('my-el-teleport-child', null, {
default: () => [h('span', null, 'default')],
}),
],
})
},
}
const app = createApp(App)
app.mount(container)
await nextTick()
expect(target.innerHTML).toBe(`<span>default</span>`)
app.unmount()
})
})

describe('helpers', () => {
Expand Down
9 changes: 7 additions & 2 deletions packages/runtime-dom/src/apiCustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ export class VueElement
*/
_nonce: string | undefined = this._def.nonce

/**
* @internal
*/
_teleportTarget?: HTMLElement

private _connected = false
private _resolved = false
private _numberProps: Record<string, true> | null = null
Expand Down Expand Up @@ -272,7 +277,7 @@ export class VueElement
}

connectedCallback(): void {
if (!this.shadowRoot) {
if (!this.shadowRoot && !this._slots) {
this._parseSlots()
}
this._connected = true
Expand Down Expand Up @@ -618,7 +623,7 @@ export class VueElement
* Only called when shaddowRoot is false
*/
private _renderSlots() {
const outlets = this.querySelectorAll('slot')
const outlets = (this._teleportTarget || this).querySelectorAll('slot')
const scopeId = this._instance!.type.__scopeId
for (let i = 0; i < outlets.length; i++) {
const o = outlets[i] as HTMLSlotElement
Expand Down
43 changes: 43 additions & 0 deletions packages/vue/__tests__/e2e/ssr-custom-element.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,49 @@ test('ssr custom element hydration', async () => {
await assertInteraction('my-element-async')
})

test('work with Teleport (shadowRoot: false)', async () => {
await setContent(
`<div id='test'></div><my-p><my-y><span>default</span></my-y></my-p>`,
)

await page().evaluate(() => {
const { h, defineSSRCustomElement, Teleport, renderSlot } = (window as any)
.Vue
const Y = defineSSRCustomElement(
{
render() {
return h(
Teleport,
{ to: '#test' },
{
default: () => [renderSlot(this.$slots, 'default')],
},
)
},
},
{ shadowRoot: false },
)
customElements.define('my-y', Y)
const P = defineSSRCustomElement(
{
render() {
return renderSlot(this.$slots, 'default')
},
},
{ shadowRoot: false },
)
customElements.define('my-p', P)
})

function getInnerHTML() {
return page().evaluate(() => {
return (document.querySelector('#test') as any).innerHTML
})
}

expect(await getInnerHTML()).toBe('<span>default</span>')
})

// #11641
test('pass key to custom element', async () => {
const messages: string[] = []
Expand Down