]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: remove the code related to handling full diff edison/fix/13206 13208/head
authordaiwei <daiwei521@126.com>
Tue, 13 May 2025 03:17:22 +0000 (11:17 +0800)
committerdaiwei <daiwei521@126.com>
Tue, 13 May 2025 03:34:05 +0000 (11:34 +0800)
_renderSlots only renders slots that have corresponding outlets, but not all slots in this._slots are rendered to the DOM tree. During a full diff, parent.insertBefore(node,anchor) will throw an error because the anchor is not in the DOM tree.

packages/runtime-core/src/hydration.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/__tests__/customElement.spec.ts
packages/runtime-dom/src/apiCustomElement.ts

index 1372c126e800cc07d7103827b61f24fa4df5f649..a94ff3568107e6c20d383eeb4e5e839012825ee2 100644 (file)
@@ -772,6 +772,13 @@ export function createHydrationFunctions(
     }
   }
 
+  const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
+    return (
+      node.nodeType === DOMNodeTypes.ELEMENT &&
+      (node as Element).tagName === 'TEMPLATE'
+    )
+  }
+
   return [hydrate, hydrateNode]
 }
 
@@ -986,10 +993,3 @@ function isMismatchAllowed(
     return allowedAttr.split(',').includes(MismatchTypeString[allowedType])
   }
 }
-
-export const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
-  return (
-    node.nodeType === DOMNodeTypes.ELEMENT &&
-    (node as Element).tagName === 'TEMPLATE'
-  )
-}
index da171b0d0574ff104d8192046bad54683f3aac4a..9910f82102b06daa9f132f6e15dd86d908dccb66 100644 (file)
@@ -382,7 +382,6 @@ export {
   normalizeClass,
   normalizeStyle,
 } from '@vue/shared'
-export { isTemplateNode } from './hydration'
 
 // For test-utils
 export { transformVNodeArgs } from './vnode'
index d1278bda31f895140e8e229a3e8a9e026d42a72c..79965b28a9cdcc9815d70128812252c17fdfccba 100644 (file)
@@ -711,18 +711,6 @@ function baseCreateRenderer(
     if (needCallTransitionHooks) {
       transition!.beforeEnter(el)
     }
-
-    // For custom element with shadowRoot: false, the anchor node may be moved
-    // to the slot container. In this case, it need to use the anchor's parent
-    // node as the actual container.
-    if (
-      container._isVueCE &&
-      container._def.shadowRoot === false &&
-      anchor &&
-      anchor.$parentNode
-    ) {
-      container = anchor.$parentNode
-    }
     hostInsert(el, container, anchor)
     if (
       (vnodeHook = props && props.onVnodeMounted) ||
@@ -978,7 +966,7 @@ function baseCreateRenderer(
           !isSameVNodeType(oldVNode, newVNode) ||
           // - In the case of a component, it could contain anything.
           oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT))
-          ? hostParentNode(oldVNode.el)!
+          ? hostParentNode(oldVNode.el) || oldVNode.el.$parentNode
           : // In other cases, the parent container is not actually used so we
             // just pass the block element here to avoid a DOM parentNode call.
             fallbackContainer
index e7c5bcd47f84e215e608d7e1ce3f9f9ae8a753fa..5eb234f66d85ee9de19c85493e0e7e02c216c62d 100644 (file)
@@ -1029,10 +1029,7 @@ describe('defineCustomElement', () => {
       toggle.value = false
       await nextTick()
       expect(e.innerHTML).toBe(
-        `<span>default</span>text` +
-          `<template name="named"></template>` +
-          `<!---->` +
-          `<div>fallback</div>`,
+        `<span>default</span>text` + `<!---->` + `<div>fallback</div>`,
       )
     })
 
@@ -1215,7 +1212,7 @@ describe('defineCustomElement', () => {
       app.mount(container)
       expect(container.innerHTML).toBe(
         `<ce-shadow-root-false-optimized data-v-app="">` +
-          `<!--v-if--><template name="default"></template>` +
+          `<!--v-if-->` +
           `</ce-shadow-root-false-optimized>`,
       )
 
@@ -1231,7 +1228,7 @@ describe('defineCustomElement', () => {
       await nextTick()
       expect(container.innerHTML).toBe(
         `<ce-shadow-root-false-optimized data-v-app="">` +
-          `<!--v-if--><template name="default"></template>` +
+          `<!--v-if-->` +
           `</ce-shadow-root-false-optimized>`,
       )
 
@@ -1244,93 +1241,6 @@ describe('defineCustomElement', () => {
       )
     })
 
-    test('update slotted v-if nodes w/ shadowRoot false', async () => {
-      const E = defineCustomElement(
-        defineComponent({
-          props: {
-            isShown: { type: Boolean, required: true },
-          },
-          render() {
-            return this.isShown
-              ? h('div', { key: 0 }, [renderSlot(this.$slots, 'default')])
-              : createCommentVNode('v-if')
-          },
-        }),
-        { shadowRoot: false },
-      )
-      customElements.define('ce-shadow-root-false', E)
-
-      const Comp = defineComponent({
-        props: {
-          isShown: { type: Boolean, required: true },
-        },
-        render() {
-          return h('ce-shadow-root-false', { 'is-shown': this.isShown }, [
-            renderSlot(this.$slots, 'default'),
-          ])
-        },
-      })
-
-      const isShown = ref(false)
-      const count = ref(0)
-
-      function click() {
-        isShown.value = !isShown.value
-        count.value++
-      }
-
-      const App = {
-        render() {
-          return h(
-            Comp,
-            { isShown: isShown.value },
-            {
-              default: () => [
-                h('div', null, String(isShown.value)),
-                count.value > 1
-                  ? h('div', { key: 0 }, 'hi')
-                  : createCommentVNode('v-if', true),
-              ],
-            },
-          )
-        },
-      }
-      const container = document.createElement('div')
-      document.body.appendChild(container)
-
-      const app = createApp(App)
-      app.mount(container)
-      expect(container.innerHTML).toBe(
-        `<ce-shadow-root-false data-v-app="">` +
-          `<!--v-if--><template name="default"></template>` +
-          `</ce-shadow-root-false>`,
-      )
-
-      click()
-      await nextTick()
-      expect(container.innerHTML).toBe(
-        `<ce-shadow-root-false data-v-app="" is-shown="">` +
-          `<div><div>true</div><!--v-if--></div>` +
-          `</ce-shadow-root-false>`,
-      )
-
-      click()
-      await nextTick()
-      expect(container.innerHTML).toBe(
-        `<ce-shadow-root-false data-v-app="">` +
-          `<!--v-if--><template name="default"></template>` +
-          `</ce-shadow-root-false>`,
-      )
-
-      click()
-      await nextTick()
-      expect(container.innerHTML).toBe(
-        `<ce-shadow-root-false data-v-app="" is-shown="">` +
-          `<div><div>true</div><div>hi</div></div>` +
-          `</ce-shadow-root-false>`,
-      )
-    })
-
     // #13234
     test('switch between slotted and fallback nodes w/ shadowRoot false (optimized mode)', async () => {
       const E = defineCustomElement(
@@ -1400,7 +1310,7 @@ describe('defineCustomElement', () => {
       app.mount(container)
       expect(container.innerHTML).toBe(
         `<ce-with-fallback-shadow-root-false-optimized data-v-app="">` +
-          `fallback<template name="default"></template>` +
+          `fallback` +
           `</ce-with-fallback-shadow-root-false-optimized>`,
       )
 
@@ -1416,87 +1326,10 @@ describe('defineCustomElement', () => {
       await nextTick()
       expect(container.innerHTML).toBe(
         `<ce-with-fallback-shadow-root-false-optimized data-v-app="">` +
-          `<!--v-if-->fallback` +
+          `fallback<!--v-if-->` +
           `</ce-with-fallback-shadow-root-false-optimized>`,
       )
     })
-
-    test('switch between slotted and fallback nodes w/ shadowRoot false', async () => {
-      const E = defineCustomElement(
-        defineComponent({
-          render() {
-            return renderSlot(this.$slots, 'foo', {}, () => [
-              createTextVNode('fallback'),
-            ])
-          },
-        }),
-        { shadowRoot: false },
-      )
-      customElements.define('ce-with-fallback-shadow-root-false', E)
-
-      const Comp = defineComponent({
-        render() {
-          return h('ce-with-fallback-shadow-root-false', null, [
-            this.$slots.foo
-              ? h('div', { key: 0, slot: 'foo' }, [
-                  renderSlot(this.$slots, 'foo'),
-                ])
-              : createCommentVNode('v-if', true),
-            renderSlot(this.$slots, 'default'),
-          ])
-        },
-      })
-
-      const isShown = ref(false)
-      const App = defineComponent({
-        components: { Comp },
-        render() {
-          return h(
-            Comp,
-            null,
-            createSlots(
-              { _: 2 /* DYNAMIC */ } as any,
-              [
-                isShown.value
-                  ? {
-                      name: 'foo',
-                      fn: withCtx(() => [createTextVNode('foo')]),
-                      key: '0',
-                    }
-                  : undefined,
-              ] as any,
-            ),
-          )
-        },
-      })
-
-      const container = document.createElement('div')
-      document.body.appendChild(container)
-
-      const app = createApp(App)
-      app.mount(container)
-      expect(container.innerHTML).toBe(
-        `<ce-with-fallback-shadow-root-false data-v-app="">` +
-          `fallback<template name="default"></template>` +
-          `</ce-with-fallback-shadow-root-false>`,
-      )
-
-      isShown.value = true
-      await nextTick()
-      expect(container.innerHTML).toBe(
-        `<ce-with-fallback-shadow-root-false data-v-app="">` +
-          `<div slot="foo">foo</div>` +
-          `</ce-with-fallback-shadow-root-false>`,
-      )
-
-      // isShown.value = false
-      // await nextTick()
-      // expect(container.innerHTML).toBe(
-      //   `<ce-with-fallback-shadow-root-false data-v-app="">` +
-      //     `<!--v-if-->fallback` +
-      //     `</ce-with-fallback-shadow-root-false>`,
-      // )
-    })
   })
 
   describe('helpers', () => {
index d3233935be95a1985e19ecec48ea2d37bd057c0d..4c5192e9a395f91aa788c3253be7c5afc9f17164 100644 (file)
@@ -30,7 +30,6 @@ import {
   createVNode,
   defineComponent,
   getCurrentInstance,
-  isTemplateNode,
   isVNode,
   nextTick,
   unref,
@@ -248,7 +247,6 @@ export class VueElement
   private _slots?: Record<string, (Node & { $parentNode?: Node })[]>
   private _slotFallbacks?: Record<string, Node[]>
   private _slotAnchors?: Map<string, Node>
-  private _slotNames: Set<string> | undefined
 
   constructor(
     /**
@@ -537,7 +535,7 @@ export class VueElement
     const baseProps: VNodeProps = {}
     if (!this.shadowRoot) {
       baseProps.onVnodeMounted = () => {
-        this._captureSlotFallbacks()
+        this._parseSlotFallbacks()
         this._renderSlots()
       }
       baseProps.onVnodeUpdated = this._renderSlots.bind(this)
@@ -628,56 +626,31 @@ export class VueElement
    * Only called when shadowRoot is false
    */
   private _parseSlots(remove: boolean = true) {
-    if (!this._slotNames) this._slotNames = new Set()
-    else this._slotNames.clear()
-    this._slots = {}
-
+    const slots: VueElement['_slots'] = (this._slots = {})
     let n = this.firstChild
     while (n) {
-      const next = n.nextSibling
-      if (isTemplateNode(n)) {
-        this.processTemplateChildren(n, remove)
-        this.removeChild(n)
-      } else {
-        const slotName =
-          (n.nodeType === 1 && (n as Element).getAttribute('slot')) || 'default'
-        this.addToSlot(slotName, n, remove)
-      }
-
-      n = next
-    }
-  }
-
-  private processTemplateChildren(template: Node, remove: boolean) {
-    let n = template.firstChild
-    while (n) {
-      const next = n.nextSibling
       const slotName =
         (n.nodeType === 1 && (n as Element).getAttribute('slot')) || 'default'
-      this.addToSlot(slotName, n, remove)
-      if (remove) template.removeChild(n)
+      ;(slots[slotName] || (slots[slotName] = [])).push(n)
+      const next = n.nextSibling
+      // store the parentNode reference since node will be removed
+      // but it is needed during patching
+      ;(n as any).$parentNode = n.parentNode
+      if (remove) this.removeChild(n)
       n = next
     }
   }
 
-  private addToSlot(slotName: string, node: Node, remove: boolean) {
-    ;(this._slots![slotName] || (this._slots![slotName] = [])).push(node)
-    this._slotNames!.add(slotName)
-    if (remove) this.removeChild(node)
-  }
-
   /**
    * Only called when shadowRoot is false
    */
   private _renderSlots() {
     const outlets = (this._teleportTarget || this).querySelectorAll('slot')
     const scopeId = this._instance!.type.__scopeId
-    const processedSlots = new Set<string>()
 
     for (let i = 0; i < outlets.length; i++) {
       const o = outlets[i] as HTMLSlotElement
       const slotName = o.getAttribute('name') || 'default'
-      processedSlots.add(slotName)
       const content = this._slots![slotName]
       const parent = o.parentNode!
 
@@ -690,11 +663,19 @@ export class VueElement
       parent.insertBefore(anchor, o)
 
       if (content) {
-        const parentNode = content[0].parentNode
-        insertSlottedContent(content, scopeId, parent, anchor)
-        // remove empty template container
-        if (parentNode && isTemplateNode(parentNode)) {
-          this.removeChild(parentNode)
+        for (const n of content) {
+          // for :slotted css
+          if (scopeId && n.nodeType === 1) {
+            const id = scopeId + '-s'
+            const walker = document.createTreeWalker(n, 1)
+            ;(n as Element).setAttribute(id, '')
+            let child
+            while ((child = walker.nextNode())) {
+              ;(child as Element).setAttribute(id, '')
+            }
+          }
+          n.$parentNode = parent
+          parent.insertBefore(n, anchor)
         }
       } else if (this._slotFallbacks) {
         const nodes = this._slotFallbacks[slotName]
@@ -706,35 +687,6 @@ export class VueElement
       }
       parent.removeChild(o)
     }
-
-    // create template for unprocessed slots and insert their content
-    // this prevents errors during full diff when anchors are not in the DOM tree
-    for (const slotName of this._slotNames!) {
-      if (processedSlots.has(slotName)) continue
-
-      const content = this._slots![slotName]
-      if (content && !content[0].isConnected) {
-        let anchor
-        if (this._slotAnchors) {
-          const slotNames = Array.from(this._slotNames!)
-          const slotIndex = slotNames.indexOf(slotName)
-          if (slotIndex > 0) {
-            const prevSlotAnchor = this._slotAnchors.get(
-              slotNames[slotIndex - 1],
-            )
-            if (prevSlotAnchor) anchor = prevSlotAnchor.nextSibling
-          }
-        }
-
-        const container = document.createElement('template')
-        container.setAttribute('name', slotName)
-        for (const n of content) {
-          n.$parentNode = container
-          container.insertBefore(n, null)
-        }
-        this.insertBefore(container, anchor || null)
-      }
-    }
   }
 
   /**
@@ -747,17 +699,11 @@ export class VueElement
     for (let i = 0; i < prevNodes.length; i++) {
       const prevNode = prevNodes[i]
       const newNode = newNodes[i]
-      if (
-        prevNode !== newNode &&
-        (isComment(prevNode, 'v-if') || isComment(newNode, 'v-if'))
-      ) {
+      if (isComment(prevNode, 'v-if') || isComment(newNode, 'v-if')) {
         Object.entries(this._slots!).forEach(([_, nodes]) => {
           const nodeIndex = nodes.indexOf(prevNode)
           if (nodeIndex > -1) {
-            const oldNode = nodes[nodeIndex]
-            const parentNode = (newNode.$parentNode = oldNode.$parentNode)!
             nodes[nodeIndex] = newNode
-            if (oldNode.isConnected) parentNode.replaceChild(newNode, oldNode)
           }
         })
       }
@@ -765,37 +711,27 @@ export class VueElement
 
     // switch between fallback and provided content
     if (this._slotFallbacks) {
-      const oldSlotNames = Array.from(this._slotNames!)
+      const oldSlotNames = Object.keys(this._slots!)
       // re-parse slots
       this._parseSlots(false)
-      const newSlotNames = Array.from(this._slotNames!)
+      const newSlotNames = Object.keys(this._slots!)
       const allSlotNames = new Set([...oldSlotNames, ...newSlotNames])
       allSlotNames.forEach(name => {
         const fallbackNodes = this._slotFallbacks![name]
         if (fallbackNodes) {
           // render fallback nodes for removed slots
-          if (!newSlotNames.includes(name) && this._slotAnchors) {
-            const anchor = this._slotAnchors.get(name)!
+          if (!newSlotNames.includes(name)) {
+            const anchor = this._slotAnchors!.get(name)!
             fallbackNodes.forEach(fallbackNode =>
               this.insertBefore(fallbackNode, anchor),
             )
           }
 
-          // remove fallback nodes and render provided nodes for added slots
+          // remove fallback nodes for added slots
           if (!oldSlotNames.includes(name)) {
             fallbackNodes.forEach(fallbackNode =>
               this.removeChild(fallbackNode),
             )
-
-            const content = this._slots![name]
-            if (content) {
-              insertSlottedContent(
-                content,
-                this._instance!.type.__scopeId,
-                this._root,
-                (this._slotAnchors && this._slotAnchors!.get(name)) || null,
-              )
-            }
           }
         }
       })
@@ -805,7 +741,7 @@ export class VueElement
   /**
    * Only called when shadowRoot is false
    */
-  private _captureSlotFallbacks() {
+  private _parseSlotFallbacks() {
     const outlets = (this._teleportTarget || this).querySelectorAll('slot')
     for (let i = 0; i < outlets.length; i++) {
       const slotElement = outlets[i] as HTMLSlotElement
@@ -875,28 +811,6 @@ export function useShadowRoot(): ShadowRoot | null {
   return el && el.shadowRoot
 }
 
-function insertSlottedContent(
-  content: (Node & { $parentNode?: Node })[],
-  scopeId: string | undefined,
-  parent: ParentNode,
-  anchor: Node | null,
-) {
-  for (const n of content) {
-    // for :slotted css
-    if (scopeId && n.nodeType === 1) {
-      const id = scopeId + '-s'
-      const walker = document.createTreeWalker(n, 1)
-      ;(n as Element).setAttribute(id, '')
-      let child
-      while ((child = walker.nextNode())) {
-        ;(child as Element).setAttribute(id, '')
-      }
-    }
-    n.$parentNode = parent
-    parent.insertBefore(n, anchor)
-  }
-}
-
 function collectFragmentNodes(child: VNode): Node[] {
   return [
     child.el as Node,