From: daiwei Date: Sat, 26 Apr 2025 02:34:46 +0000 (+0800) Subject: wip: refactor X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7a842ab6cb31cbffcea9ad608686d4ada558779e;p=thirdparty%2Fvuejs%2Fcore.git wip: refactor --- diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts index e232400404..56f38b9e58 100644 --- a/packages/compiler-ssr/src/ssrCodegenTransform.ts +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -302,8 +302,8 @@ function processChildrenDynamicInfo( const child = filteredChildren[i] if ( isStaticChildNode(child) || - // v-if has an anchor, which can be used to distinguish the boundary - child.type === NodeTypes.IF + // fragment has it's own anchor, which can be used to distinguish the boundary + isFragmentChild(child) ) { continue } @@ -359,10 +359,10 @@ function processChildrenDynamicInfo( } /** - * Check if a node should be processed as dynamic. + * Check if a node should be processed as dynamic child. * This is primarily used in Vapor mode hydration to wrap dynamic parts * with markers (`` and ``). - * The purpose is to distinguish the boundaries of nodes during hydration + * The purpose is to distinguish the boundaries of nodes during vapor hydration * * 1. two consecutive dynamic nodes should only wrap the second one * @@ -416,3 +416,8 @@ function shouldProcessChildAsDynamic( return false } + +function isFragmentChild(child: TemplateChildNode): boolean { + const { type } = child + return type === NodeTypes.IF || type === NodeTypes.FOR +} diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 7c7fdf3860..4c8be2281d 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -26,13 +26,13 @@ import { includeBooleanAttr, isBooleanAttr, isDynamicAnchor, - isDynamicFragmentEndAnchor, isKnownHtmlAttr, isKnownSvgAttr, isOn, isRenderableAttrValue, isReservedProp, isString, + isVaporFragmentEndAnchor, normalizeClass, normalizeStyle, stringifyStyle, @@ -126,7 +126,7 @@ export function createHydrationFunctions( // skip if: // - dynamic anchors (``, ``) // - dynamic fragment end anchors (e.g. ``, ``) - if (n && (isDynamicAnchor(n) || isDynamicFragmentEndAnchor(n))) { + if (n && (isDynamicAnchor(n) || isVaporFragmentEndAnchor(n))) { n = next(n) } return n @@ -158,7 +158,7 @@ export function createHydrationFunctions( slotScopeIds: string[] | null, optimized = false, ): Node | null => { - if (isDynamicAnchor(node) || isDynamicFragmentEndAnchor(node)) { + if (isDynamicAnchor(node) || isVaporFragmentEndAnchor(node)) { node = nextSibling(node)! } optimized = optimized || !!vnode.dynamicChildren diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index c15bd68542..d8c40bdc6b 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -79,15 +79,6 @@ const triggerEvent = (type: string, el: Element) => { el.dispatchEvent(event) } -async function runWithEnv(isProd: boolean, fn: () => Promise) { - if (isProd) __DEV__ = false - try { - await fn() - } finally { - if (isProd) __DEV__ = true - } -} - describe('Vapor Mode hydration', () => { delegateEvents('click') @@ -95,7 +86,7 @@ describe('Vapor Mode hydration', () => { document.body.innerHTML = '' }) - describe('element', () => { + describe('text', () => { test('root text', async () => { const { data, container } = await testHydration(` @@ -107,6 +98,10 @@ describe('Vapor Mode hydration', () => { expect(container.innerHTML).toMatchInlineSnapshot(`"bar"`) }) + test.todo('consecutive text nodes', () => {}) + }) + + describe('element', () => { test('root comment', async () => { const { container } = await testHydration(` @@ -635,13 +630,13 @@ describe('Vapor Mode hydration', () => { test('consecutive fragment components with anchor insertion', async () => { const { container, data } = await testHydration( ` +
+ + + + +
+ `, { Child: ``, @@ -864,518 +859,532 @@ describe('Vapor Mode hydration', () => { }) }) - describe('if', () => { - describe('DEV mode', () => { - runTests() + describe('dynamic component', () => { + const anchorLabel = DYNAMIC_COMPONENT_ANCHOR_LABEL + + test('basic dynamic component', 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
`) }) - describe('PROD mode', () => { - runTests(true) + test('dynamic component with anchor insertion', 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
` + + `` + + `
`, + ) }) - function runTests(isProd: boolean = false) { - const anchorLabel = IF_ANCHOR_LABEL - - test('basic toggle - true -> false', async () => { - runWithEnv(isProd, async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - undefined, - data, - ) - expect(container.innerHTML).toBe( - `
foo
`, - ) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe(``) - }) - }) - - test('basic toggle - false -> true', async () => { - runWithEnv(isProd, async () => { - if (isProd) __DEV__ = false - const data = ref(false) - const { container } = await testHydration( - ``, - undefined, - data, - ) - // v-if="false" is rendered as in the server-rendered HTML - // it reused as anchor, so the anchor label is empty - expect(container.innerHTML).toBe(``) - - data.value = true - await nextTick() - expect(container.innerHTML).toBe(`
foo
`) - }) - }) - - test('v-if/else-if/else chain - switch branches', async () => { - runWithEnv(isProd, async () => { - const data = ref('a') - const { container } = await testHydration( - ``, - undefined, - data, - ) - expect(container.innerHTML).toBe( - `
foo
`, - ) - - data.value = 'b' - await nextTick() - // In PROD, the anchor of v-else-if (DynamicFragment) is an empty text node, - // so it won't be rendered - expect(container.innerHTML).toBe( - `
bar
${isProd ? '' : ``}`, - ) - - data.value = 'c' - await nextTick() - // same as above - expect(container.innerHTML).toBe( - `
baz
${isProd ? '' : ``}`, - ) - - data.value = 'a' - await nextTick() - expect(container.innerHTML).toBe( - `
foo
`, - ) - }) - }) - - test('nested if', async () => { - runWithEnv(isProd, async () => { - const data = reactive({ outer: true, inner: true }) - const { container } = await testHydration( - ``, - undefined, - data, - ) - expect(container.innerHTML).toBe( - `
` + - `outer` + - `
inner
` + - `
`, - ) - - data.inner = false - await nextTick() - expect(container.innerHTML).toBe( - `
` + - `outer` + - `` + - `
`, - ) - - data.outer = false - await nextTick() - expect(container.innerHTML).toBe(``) - }) - }) - - test('on component', async () => { - runWithEnv(isProd, async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - { Child: `` }, - data, - ) - expect(container.innerHTML).toBe(`foo`) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe(``) - }) - }) - - test('v-if/else-if/else chain on component - switch branches', async () => { - runWithEnv(isProd, async () => { - const data = ref('a') - const { container } = await testHydration( - ``, - { - Child1: ``, - Child2: ``, - Child3: ``, - }, - data, - ) - expect(container.innerHTML).toBe( - `a child1`, - ) - - data.value = 'b' - await nextTick() - // In PROD, the anchor of v-else-if (DynamicFragment) is an empty text node, - // so it won't be rendered - expect(container.innerHTML).toBe( - `b child2${isProd ? '' : ``}`, - ) - - data.value = 'c' - await nextTick() - // same as above - expect(container.innerHTML).toBe( - `c child3${isProd ? '' : ``}`, - ) - - data.value = 'a' - await nextTick() - expect(container.innerHTML).toBe( - `a child1`, - ) - }) - }) - - test('on component with anchor insertion', async () => { - runWithEnv(isProd, async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - { Child: `` }, - data, - ) - expect(container.innerHTML).toBe( - `
` + - `` + - `foo` + - `` + - `
`, - ) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe( - `
` + - `` + - `` + - `` + - `
`, - ) - }) - }) - - test('consecutive v-if on component with anchor insertion', async () => { - runWithEnv(isProd, async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - { Child: `` }, - data, - ) - expect(container.innerHTML).toBe( - `
` + - `` + - `foo` + - `foo` + - `` + - `
`, - ) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe( - `
` + - `` + - `` + - `` + - `` + - `
`, - ) - }) - }) - - test('on fragment component', async () => { - runWithEnv(isProd, async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - { - Child: ``, - }, - data, - ) - expect(container.innerHTML).toBe( - `
` + - `
true
-true-` + - `` + - `
`, - ) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe( - `
` + `` + `` + `
`, - ) - }) - }) - - test('on fragment component with anchor insertion', async () => { - runWithEnv(isProd, async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - { - Child: ``, - }, - data, - ) - expect(container.innerHTML).toBe( - `
` + - `` + - `
true
-true-` + - `` + - `` + - `
`, - ) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe( - `
` + - `` + - `` + - `` + - `` + - `
`, - ) - }) - }) - - test('consecutive v-if on fragment component with anchor insertion', async () => { - runWithEnv(isProd, async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - { - Child: ``, - }, - data, - ) - expect(container.innerHTML).toBe( - `
` + - `` + - `
true
-true-` + - `
true
-true-` + - `` + - `
`, - ) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe( - `
` + - `` + - `` + - `` + - `` + - `
`, - ) - }) - }) - - test('on dynamic component with anchor insertion', async () => { - runWithEnv(isProd, async () => { - const dynamicComponentAnchorLabel = DYNAMIC_COMPONENT_ANCHOR_LABEL - const data = ref(true) - const { container } = await testHydration( - ``, - { Child: `` }, - data, - ) - expect(container.innerHTML).toBe( - `
` + - `` + - `foo` + - `` + - `
`, - ) - - data.value = false - await nextTick() - expect(container.innerHTML).toBe( - `
` + - `` + - `` + - `` + - `
`, - ) - }) - }) - } + test('consecutive dynamic components with anchor insertion', 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
` + + `` + + `
`, + ) + }) }) - describe('dynamic component', () => { - describe('DEV mode', () => { - runTests() + describe('if', () => { + const anchorLabel = IF_ANCHOR_LABEL + + test('basic toggle - true -> false', async () => { + const data = ref(true) + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(container.innerHTML).toBe(`
foo
`) + + data.value = false + await nextTick() + expect(container.innerHTML).toBe(``) + }) + + test('basic toggle - false -> true', async () => { + const data = ref(false) + const { container } = await testHydration( + ``, + undefined, + data, + ) + // v-if="false" is rendered as in the server-rendered HTML + // it reused as anchor, so the anchor label is empty + expect(container.innerHTML).toBe(``) + + data.value = true + await nextTick() + expect(container.innerHTML).toBe(`
foo
`) }) - describe('PROD mode', () => { - runTests(true) + + test('v-if/else-if/else chain - switch branches', async () => { + const data = ref('a') + const { container } = await testHydration( + ``, + undefined, + data, + ) + expect(container.innerHTML).toBe(`
foo
`) + + data.value = 'b' + await nextTick() + expect(container.innerHTML).toBe( + `
bar
`, + ) + + data.value = 'c' + await nextTick() + expect(container.innerHTML).toBe( + `
baz
`, + ) + + data.value = 'a' + await nextTick() + expect(container.innerHTML).toBe(`
foo
`) }) - function runTests(isProd: boolean = false) { - const anchorLabel = DYNAMIC_COMPONENT_ANCHOR_LABEL - - 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( - `