]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(custom-element): handle keys set on custom elements (#11655)
authoredison <daiwei521@126.com>
Thu, 29 Aug 2024 07:09:15 +0000 (15:09 +0800)
committerGitHub <noreply@github.com>
Thu, 29 Aug 2024 07:09:15 +0000 (15:09 +0800)
close #11641

packages/runtime-dom/src/apiCustomElement.ts
packages/vue/__tests__/e2e/ssr-custom-element.html [deleted file]
packages/vue/__tests__/e2e/ssr-custom-element.spec.ts

index ccd8989b377b1ef54a8f5adac7773c5f4f602206..97389c8e6ec9d90c97e21dd8fdb77cc93ec50046 100644 (file)
@@ -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()
diff --git a/packages/vue/__tests__/e2e/ssr-custom-element.html b/packages/vue/__tests__/e2e/ssr-custom-element.html
deleted file mode 100644 (file)
index 14139c2..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<script src="../../dist/vue.global.js"></script>
-
-<my-element
-  ><template shadowrootmode="open"><button>1</button></template></my-element
->
-<my-element-async
-  ><template shadowrootmode="open"
-    ><button>1</button></template
-  ></my-element-async
->
-
-<script>
-  const {
-    h,
-    ref,
-    defineSSRCustomElement,
-    defineAsyncComponent,
-    onMounted,
-    useHost,
-  } = 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.resolve = () => r(def)
-          }),
-      ),
-    ),
-  )
-</script>
index 0c8413d1769dbb8a3fe4d3370d1625ee75b4eed0..c39286d3d1274e8efdf749121e907e017a8e368f 100644 (file)
@@ -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 [
@@ -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')
+})