From: daiwei Date: Tue, 13 May 2025 03:17:22 +0000 (+0800) Subject: chore: remove the code related to handling full diff X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Fedison%2Ffix%2F13206;p=thirdparty%2Fvuejs%2Fcore.git chore: remove the code related to handling full diff _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. --- diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 1372c126e8..a94ff35681 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -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' - ) -} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index da171b0d05..9910f82102 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -382,7 +382,6 @@ export { normalizeClass, normalizeStyle, } from '@vue/shared' -export { isTemplateNode } from './hydration' // For test-utils export { transformVNodeArgs } from './vnode' diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index d1278bda31..79965b28a9 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -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 diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index e7c5bcd47f..5eb234f66d 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1029,10 +1029,7 @@ describe('defineCustomElement', () => { toggle.value = false await nextTick() expect(e.innerHTML).toBe( - `defaulttext` + - `` + - `` + - `
fallback
`, + `defaulttext` + `` + `
fallback
`, ) }) @@ -1215,7 +1212,7 @@ describe('defineCustomElement', () => { app.mount(container) expect(container.innerHTML).toBe( `` + - `` + + `` + ``, ) @@ -1231,7 +1228,7 @@ describe('defineCustomElement', () => { await nextTick() expect(container.innerHTML).toBe( `` + - `` + + `` + ``, ) @@ -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( - `` + - `` + - ``, - ) - - click() - await nextTick() - expect(container.innerHTML).toBe( - `` + - `
true
` + - `
`, - ) - - click() - await nextTick() - expect(container.innerHTML).toBe( - `` + - `` + - ``, - ) - - click() - await nextTick() - expect(container.innerHTML).toBe( - `` + - `
true
hi
` + - `
`, - ) - }) - // #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( `` + - `fallback` + + `fallback` + ``, ) @@ -1416,87 +1326,10 @@ describe('defineCustomElement', () => { await nextTick() expect(container.innerHTML).toBe( `` + - `fallback` + + `fallback` + ``, ) }) - - 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( - `` + - `fallback` + - ``, - ) - - isShown.value = true - await nextTick() - expect(container.innerHTML).toBe( - `` + - `
foo
` + - `
`, - ) - - // isShown.value = false - // await nextTick() - // expect(container.innerHTML).toBe( - // `` + - // `fallback` + - // ``, - // ) - }) }) describe('helpers', () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index d3233935be..4c5192e9a3 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -30,7 +30,6 @@ import { createVNode, defineComponent, getCurrentInstance, - isTemplateNode, isVNode, nextTick, unref, @@ -248,7 +247,6 @@ export class VueElement private _slots?: Record private _slotFallbacks?: Record private _slotAnchors?: Map - private _slotNames: Set | 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() 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,