From: daiwei Date: Fri, 25 Apr 2025 03:33:35 +0000 (+0800) Subject: wip: hydation for dynamic component X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=34b9a4b5cd8d5ee7da885a793916102e59a7c029;p=thirdparty%2Fvuejs%2Fcore.git wip: hydation for dynamic component --- diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index d33d4c24d5..5889830517 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -1159,6 +1159,154 @@ describe('Vapor Mode hydration', () => { ) }) }) + + // problem is there is a continuous `` + test.todo('on dynamic component with anchor insertion', async () => { + runWithEnv(isProd, async () => { + const dynamicComponentAnchorLabel = isProd ? '$' : 'dynamic-component' + const data = ref(true) + const { container } = await testHydration( + ``, + { Child: `` }, + data, + ) + expect(container.innerHTML).toBe( + `
` + + `` + + `foo` + + `` + + `
`, + ) + + data.value = false + await nextTick() + expect(container.innerHTML).toBe( + `
` + + `` + + `` + + `` + + `
`, + ) + }) + }) + } + }) + + describe('dynamic component', () => { + describe('DEV mode', () => { + runTests() + }) + describe('PROD mode', () => { + runTests(true) + }) + + function runTests(isProd: boolean = false) { + const anchorLabel = isProd ? '$' : 'dynamic-component' + + test('basic dynamic component', async () => { + runWithEnv(isProd, async () => { + const { container, data } = await testHydration( + ``, + { + foo: ``, + bar: ``, + }, + ref('foo'), + ) + expect(container.innerHTML).toBe( + `
foo
`, + ) + + data.value = 'bar' + await nextTick() + expect(container.innerHTML).toBe( + `
bar
`, + ) + }) + }) + + test('dynamic component with anchor insertion', async () => { + runWithEnv(isProd, async () => { + const { container, data } = await testHydration( + ``, + { + foo: ``, + bar: ``, + }, + ref('foo'), + ) + expect(container.innerHTML).toBe( + `
` + + `` + + `
foo
` + + `` + + `
`, + ) + + data.value = 'bar' + await nextTick() + expect(container.innerHTML).toBe( + `
` + + `` + + `
bar
` + + `` + + `
`, + ) + }) + }) + + test('consecutive dynamic components with anchor insertion', async () => { + runWithEnv(isProd, async () => { + const { container, data } = await testHydration( + ``, + { + foo: ``, + bar: ``, + }, + ref('foo'), + ) + expect(container.innerHTML).toBe( + `
` + + `` + + `
foo
` + + `
foo
` + + `` + + `
`, + ) + + data.value = 'bar' + await nextTick() + expect(container.innerHTML).toBe( + `
` + + `` + + `
bar
` + + `
bar
` + + `` + + `
`, + ) + }) + }) } }) diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 2126611d71..db69018429 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -1,9 +1,15 @@ import { resolveDynamicComponent } from '@vue/runtime-dom' -import { DynamicFragment, type VaporFragment } from './block' +import { DynamicFragment, type VaporFragment, insert } from './block' import { createComponentWithFallback } from './component' import { renderEffect } from './renderEffect' import type { RawProps } from './componentProps' import type { RawSlots } from './componentSlots' +import { + insertionAnchor, + insertionParent, + resetInsertionState, +} from './insertionState' +import { isHydrating, locateHydrationNode } from './dom/hydration' export function createDynamicComponent( getter: () => any, @@ -11,6 +17,14 @@ export function createDynamicComponent( rawSlots?: RawSlots | null, isSingleRoot?: boolean, ): VaporFragment { + const _insertionParent = insertionParent + const _insertionAnchor = insertionAnchor + if (isHydrating) { + locateHydrationNode(true) + } else { + resetInsertionState() + } + const frag = __DEV__ ? new DynamicFragment('dynamic-component') : new DynamicFragment() @@ -27,5 +41,9 @@ export function createDynamicComponent( value, ) }) + + if (!isHydrating && _insertionParent) { + insert(frag, _insertionParent, _insertionAnchor) + } return frag } diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 15128d89df..254779c645 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -97,7 +97,7 @@ export class DynamicFragment extends VaporFragment { warn(`DynamicFragment anchor not found...`) } } - if (__DEV__ && label) (this.anchor as Comment).data = label + if (__DEV__ && label && this.anchor) (this.anchor as Comment).data = label } } diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 221d3895e2..f4775bb9d5 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -264,6 +264,7 @@ export function renderVNode( renderElementVNode(push, vnode, parentComponent, slotScopeId) } else if (shapeFlag & ShapeFlags.COMPONENT) { push(renderComponentVNode(vnode, parentComponent, slotScopeId)) + push(``) // anchor for vapor hydration } else if (shapeFlag & ShapeFlags.TELEPORT) { renderTeleportVNode(push, vnode, parentComponent, slotScopeId) } else if (shapeFlag & ShapeFlags.SUSPENSE) {