]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: Merge branch 'minor' into edison/feat/vaporHydration
authordaiwei <daiwei521@126.com>
Fri, 18 Jul 2025 03:48:22 +0000 (11:48 +0800)
committerdaiwei <daiwei521@126.com>
Fri, 18 Jul 2025 03:51:57 +0000 (11:51 +0800)
1  2 
packages/compiler-ssr/__tests__/ssrVModel.spec.ts
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/src/index.ts
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/component.ts
packages/runtime-vapor/src/vdomInterop.ts
packages/shared/src/index.ts

index c88f6ba3182bab47d3cd1108096d9fd81e57918c,8a439dbf4b5cc6063d0a74d7f1859539b1d8082a..f7b7a3241b22042423b22155a3b749523a9259b7
@@@ -167,6 -166,132 +167,135 @@@ describe('ssr: v-model', () => 
          _push(\`</optgroup></select></div>\`)
        }"
      `)
 -        _push(\`<!--]--></optgroup></select></div>\`)
+     expect(
+       compileWithWrapper(`
+         <select multiple v-model="model">
+           <optgroup>
+             <option v-for="item in items" :value="item">{{item}}</option>
+           </optgroup>
+         </select>`).code,
+     ).toMatchInlineSnapshot(`
+       "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
+       return function ssrRender(_ctx, _push, _parent, _attrs) {
+         _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
+         _ssrRenderList(_ctx.items, (item) => {
+           _push(\`<option\${
+             _ssrRenderAttr("value", item)
+           }\${
+             (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+               ? _ssrLooseContain(_ctx.model, item)
+               : _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
+           }>\${
+             _ssrInterpolate(item)
+           }</option>\`)
+         })
 -          _push(\`<!--]-->\`)
++        _push(\`<!--]--><!--for--></optgroup></select></div>\`)
+       }"
+     `)
+     expect(
+       compileWithWrapper(`
+         <select multiple v-model="model">
+           <optgroup>
+             <option v-if="true" :value="item">{{item}}</option>
+           </optgroup>
+         </select>`).code,
+     ).toMatchInlineSnapshot(`
+       "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
+       return function ssrRender(_ctx, _push, _parent, _attrs) {
+         _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
+         if (true) {
+           _push(\`<option\${
+             _ssrRenderAttr("value", _ctx.item)
+           }\${
+             (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+               ? _ssrLooseContain(_ctx.model, _ctx.item)
+               : _ssrLooseEqual(_ctx.model, _ctx.item))) ? " selected" : ""
+           }>\${
+             _ssrInterpolate(_ctx.item)
+           }</option>\`)
++          _push(\`<!--if-->\`)
+         } else {
+           _push(\`<!---->\`)
+         }
+         _push(\`</optgroup></select></div>\`)
+       }"
+     `)
+     expect(
+       compileWithWrapper(`
+         <select multiple v-model="model">
+           <optgroup>
+             <template v-if="ok">
+               <option v-for="item in items" :value="item">{{item}}</option>
+             </template>
+           </optgroup>
+         </select>`).code,
+     ).toMatchInlineSnapshot(`
+       "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
+       return function ssrRender(_ctx, _push, _parent, _attrs) {
+         _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
+         if (_ctx.ok) {
+           _push(\`<!--[-->\`)
+           _ssrRenderList(_ctx.items, (item) => {
+             _push(\`<option\${
+               _ssrRenderAttr("value", item)
+             }\${
+               (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+                 ? _ssrLooseContain(_ctx.model, item)
+                 : _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
+             }>\${
+               _ssrInterpolate(item)
+             }</option>\`)
+           })
 -        _push(\`<!--]--></optgroup></select></div>\`)
++          _push(\`<!--]--><!--for-->\`)
++          _push(\`<!--if-->\`)
+         } else {
+           _push(\`<!---->\`)
+         }
+         _push(\`</optgroup></select></div>\`)
+       }"
+     `)
+     expect(
+       compileWithWrapper(`
+         <select multiple v-model="model">
+           <optgroup>
+             <template v-for="item in items" :value="item">
+               <option v-if="item===1" :value="item">{{item}}</option>
+             </template>
+           </optgroup>
+         </select>`).code,
+     ).toMatchInlineSnapshot(`
+       "const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
+       return function ssrRender(_ctx, _push, _parent, _attrs) {
+         _push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
+         _ssrRenderList(_ctx.items, (item) => {
+           _push(\`<!--[-->\`)
+           if (item===1) {
+             _push(\`<option\${
+               _ssrRenderAttr("value", item)
+             }\${
+               (_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
+                 ? _ssrLooseContain(_ctx.model, item)
+                 : _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
+             }>\${
+               _ssrInterpolate(item)
+             }</option>\`)
++            _push(\`<!--if-->\`)
+           } else {
+             _push(\`<!---->\`)
+           }
+           _push(\`<!--]-->\`)
+         })
++        _push(\`<!--]--><!--for--></optgroup></select></div>\`)
+       }"
+     `)
    })
  
    test('<input type="radio">', () => {
index 62bcb7686744738d359ea459a6130cb5d96dd78a,15b3c7512bebb18395ff33ddb5d2c082a6e4d299..5db9a3bdd5218bfacf02223643853f47ea081ec8
@@@ -32,8 -31,8 +32,9 @@@ import 
    isRenderableAttrValue,
    isReservedProp,
    isString,
 +  isVaporAnchors,
    normalizeClass,
+   normalizeCssVarValue,
    normalizeStyle,
    stringifyStyle,
  } from '@vue/shared'
Simple merge
Simple merge
index 19346f6f5d4e5e34697593ef26b1c3d34c06c251,426a5c56b5b1ba226f352a31985c524ee1b87cf7..c6adfccb047a9ef9456d4acef3bcac1e2b10af52
@@@ -10,14 -9,9 +9,9 @@@ import 
    shallowRef,
    toReactive,
    toReadonly,
+   watch,
  } from '@vue/reactivity'
- import {
-   FOR_ANCHOR_LABEL,
-   getSequence,
-   isArray,
-   isObject,
-   isString,
- } from '@vue/shared'
 -import { isArray, isObject, isString } from '@vue/shared'
++import { FOR_ANCHOR_LABEL, isArray, isObject, isString } from '@vue/shared'
  import { createComment, createTextNode } from './dom/node'
  import {
    type Block,
@@@ -98,24 -87,18 +92,30 @@@ export const createFor = 
    let oldBlocks: ForBlock[] = []
    let newBlocks: ForBlock[]
    let parent: ParentNode | undefined | null
 -  // TODO handle this in hydration
 -  const parentAnchor = __DEV__ ? createComment('for') : createTextNode()
+   // useSelector only
+   let currentKey: any
 +  let parentAnchor: Node
 +  if (isHydrating) {
 +    parentAnchor = locateVaporFragmentAnchor(
 +      currentHydrationNode!,
 +      FOR_ANCHOR_LABEL,
 +    )!
 +    if (__DEV__ && !parentAnchor) {
 +      // this should not happen
 +      throw new Error(`v-for fragment anchor node was not found.`)
 +    }
 +  } else {
 +    parentAnchor = __DEV__ ? createComment('for') : createTextNode()
 +  }
 +
    const frag = new VaporFragment(oldBlocks)
    const instance = currentInstance!
-   const canUseFastRemove = flags & VaporVForFlags.FAST_REMOVE
-   const isComponent = flags & VaporVForFlags.IS_COMPONENT
+   const canUseFastRemove = !!(flags & VaporVForFlags.FAST_REMOVE)
+   const isComponent = !!(flags & VaporVForFlags.IS_COMPONENT)
+   const selectors: {
+     deregister: (key: any) => void
+     cleanup: () => void
+   }[] = []
  
    if (__DEV__ && !instance) {
      warn('createFor() can only be used inside setup()')
index f501e6f5d299d810ac22c1edcc49c1337353c8b1,e021ce84b051432f5bda391d9459f04453ac9a13..b1eaaf4968af527fe47ec63a73982cfa0d2b8f3d
@@@ -6,14 -6,8 +6,14 @@@ import 
    unmountComponent,
  } from './component'
  import { createComment, createTextNode } from './dom/node'
- import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
+ import { EffectScope, setActiveSub } from '@vue/reactivity'
 -import { isHydrating } from './dom/hydration'
 +import {
 +  currentHydrationNode,
 +  isComment,
 +  isHydrating,
 +  locateHydrationNode,
 +  locateVaporFragmentAnchor,
 +} from './dom/hydration'
  
  export type Block =
    | Node
@@@ -84,24 -73,8 +84,24 @@@ export class DynamicFragment extends Va
        parent && insert(this.nodes, parent, this.anchor)
      }
  
-     resetTracking()
+     setActiveSub(prevSub)
    }
 +
 +  hydrate(label: string): void {
 +    // for `v-if="false"` the node will be an empty comment, use it as the anchor.
 +    // otherwise, find next sibling vapor fragment anchor
 +    if (isComment(currentHydrationNode!, '')) {
 +      this.anchor = currentHydrationNode
 +    } else {
 +      const anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
 +      if (anchor) {
 +        this.anchor = anchor
 +      } else if (__DEV__) {
 +        // this should not happen
 +        throw new Error(`${label} fragment anchor node was not found.`)
 +      }
 +    }
 +  }
  }
  
  export function isFragment(val: NonNullable<unknown>): val is VaporFragment {
index fe23dcc262ebb6abb4f50e5057ccfd482483dc1d,1573a306922aacd6813fc8cdfe8f7c7a84ec4f4a..a330a56f04a4972d822d7f03dfa9a38658a26a72
@@@ -38,15 -36,9 +38,17 @@@ import type { RawSlots, VaporSlot } fro
  import { renderEffect } from './renderEffect'
  import { createTextNode } from './dom/node'
  import { optimizePropertyLookup } from './dom/prop'
 +import {
 +  currentHydrationNode,
 +  isHydrating,
 +  locateHydrationNode,
 +  locateVaporFragmentAnchor,
 +  setCurrentHydrationNode,
 +  hydrateNode as vaporHydrateNode,
 +} from './dom/hydration'
  
+ export const interopKey: unique symbol = Symbol(`interop`)
  // mounting vapor components and slots in vdom
  const vaporInteropImpl: Omit<
    VaporInteropInterface,
index 674bcdf96cd70277532ff858ff7d281af70ced5a,0c38d640ba0982f7a8439279624ca9f71018042a..9372b8e1a96ab3f6acdf89c968bf61f18d994152
@@@ -13,4 -13,4 +13,5 @@@ export * from './looseEqual
  export * from './toDisplayString'
  export * from './typeUtils'
  export * from './subSequence'
 +export * from './domAnchors'
+ export * from './cssVars'