Skip to content

Commit

Permalink
fix(custom-element): handle keys set on custom elements (vuejs#11655)
Browse files Browse the repository at this point in the history
  • Loading branch information
edison1105 authored Aug 29, 2024
1 parent 1d988b5 commit f1d1831
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 46 deletions.
4 changes: 4 additions & 0 deletions packages/runtime-dom/src/apiCustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 487,10 @@ export class VueElement
delete this._props[key]
} else {
this._props[key] = val
// support set key on ceVNode
if (key === 'key' && this._app) {
this._app._ceVNode!.key = val
}
}
if (shouldUpdate && this._instance) {
this._update()
Expand Down
44 changes: 0 additions & 44 deletions packages/vue/__tests__/e2e/ssr-custom-element.html

This file was deleted.

100 changes: 98 additions & 2 deletions packages/vue/__tests__/e2e/ssr-custom-element.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 3,57 @@ import { setupPuppeteer } from './e2eUtils'

const { page, click, text } = setupPuppeteer()

beforeEach(async () => {
await page().addScriptTag({
path: path.resolve(__dirname, '../../dist/vue.global.js'),
})
})

async function setContent(html: string) {
await page().setContent(`<div id="app">${html}</div>`)
}

// this must be tested in actual Chrome because jsdom does not support
// declarative shadow DOM
test('ssr custom element hydration', async () => {
await page().goto(
`file://${path.resolve(__dirname, './ssr-custom-element.html')}`,
await setContent(
`<my-element><template shadowrootmode="open"><button>1</button></template></my-element><my-element-async><template shadowrootmode="open"><button>1</button></template></my-element-async>`,
)

await page().evaluate(() => {
const {
h,
ref,
defineSSRCustomElement,
defineAsyncComponent,
onMounted,
useHost,
} = (window as any).Vue

const def = {
setup() {
const count = ref(1)
const el = useHost()
onMounted(() => (el.style.border = '1px solid red'))

return () => h('button', { onClick: () => count.value }, count.value)
},
}

customElements.define('my-element', defineSSRCustomElement(def))
customElements.define(
'my-element-async',
defineSSRCustomElement(
defineAsyncComponent(
() =>
new Promise(r => {
;(window as any).resolve = () => r(def)
}),
),
),
)
})

function getColor() {
return page().evaluate(() => {
return [
Expand All @@ -33,3 77,55 @@ test('ssr custom element hydration', async () => {
await assertInteraction('my-element')
await assertInteraction('my-element-async')
})

// #11641
test('pass key to custom element', async () => {
const messages: string[] = []
page().on('console', e => messages.push(e.text()))

await setContent(
`<!--[--><my-element str="1"><template shadowrootmode="open"><div>1</div></template></my-element><!--]-->`,
)
await page().evaluate(() => {
const {
h,
ref,
defineSSRCustomElement,
onBeforeUnmount,
onMounted,
createSSRApp,
renderList,
} = (window as any).Vue

const MyElement = defineSSRCustomElement({
props: {
str: String,
},
setup(props: any) {
onMounted(() => {
console.log('child mounted')
})
onBeforeUnmount(() => {
console.log('child unmount')
})
return () => h('div', props.str)
},
})
customElements.define('my-element', MyElement)

createSSRApp({
setup() {
const arr = ref(['1'])
// pass key to custom element
return () =>
renderList(arr.value, (i: string) =>
h('my-element', { key: i, str: i }, null),
)
},
}).mount('#app')
})

expect(messages.includes('child mounted')).toBe(true)
expect(messages.includes('child unmount')).toBe(false)
expect(await text('my-element >>> div')).toBe('1')
})

0 comments on commit f1d1831

Please sign in to comment.