]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: force hydrate v-bind with .prop modifiers
authordaiwei <daiwei521@126.com>
Thu, 9 Oct 2025 03:44:25 +0000 (11:44 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 9 Oct 2025 03:44:25 +0000 (11:44 +0800)
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/dom/prop.ts

index 3068a3fc75a18f3a3e42a099bbfd9fd98c7e4dea..eeda18af3062f6a9a916ae2106a3bcd63db6fd2a 100644 (file)
@@ -2983,7 +2983,7 @@ describe('Vapor Mode hydration', () => {
       expect((container.firstChild as any)._trueValue).toBe(true)
     })
 
-    test.todo('force hydrate checkbox with indeterminate', async () => {
+    test('force hydrate checkbox with indeterminate', async () => {
       const { container } = await mountWithHydration(
         '<input type="checkbox" indeterminate/>',
         `<input type="checkbox" :indeterminate="true"/>`,
@@ -2991,11 +2991,24 @@ describe('Vapor Mode hydration', () => {
       expect((container.firstChild! as any).indeterminate).toBe(true)
     })
 
-    test.todo(
-      'force hydrate select option with non-string value bindings',
-      () => {},
-    )
+    test('force hydrate select option with non-string value bindings', async () => {
+      const { container } = await mountWithHydration(
+        '<select><option value="true">ok</option></select>',
+        `<select><option :value="true">ok</option></select>`,
+      )
+      expect((container.firstChild!.firstChild as any)._value).toBe(true)
+    })
+
+    test('force hydrate v-bind with .prop modifiers', async () => {
+      const { container } = await mountWithHydration(
+        '<div .foo="true"/>',
+        `<div v-bind="data"/>`,
+        ref({ '.foo': true }),
+      )
+      expect((container.firstChild! as any).foo).toBe(true)
+    })
 
+    // vapor custom element not implemented yet
     test.todo('force hydrate custom element with dynamic props', () => {})
   })
 
index eb48cdef96e8b36793724684961cdefd314c4617..899d7f0caa5dfcec244bf993d67ddee068b1b0d0 100644 (file)
@@ -89,7 +89,12 @@ export function setAttr(el: any, key: string, value: any): void {
   }
 }
 
-export function setDOMProp(el: any, key: string, value: any): void {
+export function setDOMProp(
+  el: any,
+  key: string,
+  value: any,
+  forceHydrate: boolean = false,
+): void {
   if (!isApplyingFallthroughProps && el.$root && hasFallthroughKey(key)) {
     return
   }
@@ -98,7 +103,8 @@ export function setDOMProp(el: any, key: string, value: any): void {
     (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
     isHydrating &&
     !attributeHasMismatch(el, key, value) &&
-    !shouldForcePatch(el, key)
+    !shouldForceHydrate(el, key) &&
+    !forceHydrate
   ) {
     return
   }
@@ -225,7 +231,11 @@ function setStyleIncremental(el: any, value: any): NormalizedStyle | undefined {
   patchStyle(el, el[cacheKey], (el[cacheKey] = normalizedValue))
 }
 
-export function setValue(el: TargetElement, value: any): void {
+export function setValue(
+  el: TargetElement,
+  value: any,
+  forceHydrate: boolean = false,
+): void {
   if (!isApplyingFallthroughProps && el.$root && hasFallthroughKey('value')) {
     return
   }
@@ -237,7 +247,9 @@ export function setValue(el: TargetElement, value: any): void {
   if (
     (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
     isHydrating &&
-    !attributeHasMismatch(el, 'value', getClientText(el, value))
+    !attributeHasMismatch(el, 'value', getClientText(el, value)) &&
+    !shouldForceHydrate(el, 'value') &&
+    !forceHydrate
   ) {
     return
   }
@@ -350,6 +362,7 @@ export function setDynamicProp(
 ): void {
   // TODO
   const isSVG = false
+  let forceHydrate = false
   if (key === 'class') {
     setClass(el, value)
   } else if (key === 'style') {
@@ -357,7 +370,8 @@ export function setDynamicProp(
   } else if (isOn(key)) {
     on(el, key[2].toLowerCase() + key.slice(3), value, { effect: true })
   } else if (
-    key[0] === '.'
+    // force hydrate v-bind with .prop modifiers
+    (forceHydrate = key[0] === '.')
       ? ((key = key.slice(1)), true)
       : key[0] === '^'
         ? ((key = key.slice(1)), false)
@@ -368,12 +382,11 @@ export function setDynamicProp(
     } else if (key === 'textContent') {
       setElementText(el, value)
     } else if (key === 'value' && canSetValueDirectly(el.tagName)) {
-      setValue(el, value)
+      setValue(el, value, forceHydrate)
     } else {
-      setDOMProp(el, key, value)
+      setDOMProp(el, key, value, forceHydrate)
     }
   } else {
-    // TODO special case for <input v-model type="checkbox">
     setAttr(el, key, value)
   }
   return value
@@ -491,8 +504,12 @@ function getClientText(el: Node, value: string): string {
   return value
 }
 
-function shouldForcePatch(el: Element, key: string): boolean {
+function shouldForceHydrate(el: Element, key: string): boolean {
   const { tagName } = el
-  const forcePatch = tagName === 'INPUT' || tagName === 'OPTION'
-  return forcePatch && (key.endsWith('value') || key === 'indeterminate')
+  return (
+    ((tagName === 'INPUT' || tagName === 'OPTION') &&
+      (key.endsWith('value') || key === 'indeterminate')) ||
+    // force hydrate custom element dynamic props
+    tagName.includes('-')
+  )
 }