]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: Merge branch 'minor' into edison/feat/svgAndMathML edison/feat/svgAndMathML 13703/head
authordaiwei <daiwei521@126.com>
Thu, 6 Nov 2025 07:41:17 +0000 (15:41 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 6 Nov 2025 07:41:17 +0000 (15:41 +0800)
24 files changed:
1  2 
packages/compiler-core/src/parser.ts
packages/compiler-dom/src/transforms/stringifyStatic.ts
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts
packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts
packages/compiler-vapor/__tests__/transforms/transformText.spec.ts
packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts
packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
packages/compiler-vapor/__tests__/transforms/vText.spec.ts
packages/compiler-vapor/src/generators/prop.ts
packages/compiler-vapor/src/generators/template.ts
packages/compiler-vapor/src/ir/index.ts
packages/compiler-vapor/src/transform.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/runtime-dom/__tests__/patchAttrs.spec.ts
packages/runtime-dom/src/components/TransitionGroup.ts
packages/runtime-dom/src/index.ts
packages/runtime-vapor/__tests__/dom/prop.spec.ts
packages/runtime-vapor/src/dom/prop.ts
packages/runtime-vapor/src/dom/template.ts

Simple merge
index 71eaea876b13cd44aff7634c36d16391623c9127,8d9df60dfa131cda9cad23b18cfa14affa1d2e3d..517c282a2cfffb12f8b881a64f329cdcc1d7b47f
@@@ -1,15 -1,16 +1,26 @@@
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
  
 +exports[`compiler: element transform > MathML 1`] = `
 +"import { template as _template } from 'vue';
 +const t0 = _template("<math><mrow><mi>x</mi></mrow></math>", true, 2)
 +
 +export function render(_ctx) {
 +  const n0 = t0()
 +  return n0
 +}"
 +`;
 +
+ exports[`compiler: element transform > checkbox with static indeterminate 1`] = `
+ "import { setProp as _setProp, template as _template } from 'vue';
+ const t0 = _template("<input type=\\"checkbox\\">", true)
+ export function render(_ctx) {
+   const n0 = t0()
+   _setProp(n0, "indeterminate", "")
+   return n0
+ }"
+ `;
  exports[`compiler: element transform > component > cache v-on expression with unique handler name 1`] = `
  "import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
  
index 429bc440f1a61fa75d2a8584653d16a49a3fe190,4bbf1884d94128ac79bd61452d3827a9b50bc8d9..6fecc45962a615e6b2f3afacf82b9c1567a9cac2
@@@ -642,20 -631,9 +642,20 @@@ export function render(_ctx) 
  }"
  `;
  
 +exports[`compiler v-bind > v-bind w/ svg elements 1`] = `
 +"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
 +const t0 = _template("<svg></svg>", true, 1)
 +
 +export function render(_ctx) {
 +  const n0 = t0()
 +  _renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true, true))
 +  return n0
 +}"
 +`;
 +
  exports[`compiler v-bind > with constant value 1`] = `
  "import { setProp as _setProp, template as _template } from 'vue';
- const t0 = _template("<div f=\\"foo1\\" h=\\"1\\"></div>", true)
+ const t0 = _template("<div e=\\"2\\" f=\\"foo1\\" g=\\"1\\" h=\\"1\\"></div>", true)
  
  export function render(_ctx, $props, $emit, $attrs, $slots) {
    const n0 = t0()
index 499c5be9747d3dcfb54a0a18c8e2f673366b2288,1c929f0f206296aab6f0548f01c25a771172c9ef..fa51d085eb7c3d5f3b41b6eea441625dc2e31e28
@@@ -51,7 -51,22 +51,22 @@@ describe('compiler: text transform', (
  
    it('escapes raw static text when generating the template string', () => {
      const { ir } = compileWithTextTransform('<code>&lt;script&gt;</code>')
 -    expect(ir.template).toContain('<code>&lt;script&gt;</code>')
 -    expect(ir.template).not.toContain('<code><script></code>')
 +    expect([...ir.template.keys()]).toContain('<code>&lt;script&gt;</code>')
 +    expect([...ir.template.keys()]).not.toContain('<code><script></code>')
    })
+   test('constant text', () => {
+     const { code } = compileWithTextTransform(
+       `
+         <div>
+           {{ (2) }}
+           {{ \`foo\${1}\` }}
+           {{ 1 }}
+           {{ 1n }}
+           {{ '1' }}
+         </div>`,
+     )
+     expect(code).includes(`_template("<div>2 foo1 1 1 1</div>", true)`)
+     expect(code).toMatchSnapshot()
+   })
  })
index 76a1bde6123c779a213f76e0d0a45757f122fb52,6a8148b7c187c058253e61f3e7ce51194c969021..cfac122885efed55b081deef6ae49404a765b89a
@@@ -186,10 -182,10 +186,10 @@@ describe('compiler: v-if', () => 
  
    test('v-if + v-else-if + v-else', () => {
      const { code, ir } = compileWithVIf(
-       `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
+       `<div v-if="ok"/><p v-else-if="orNot"/><p v-else-if="false"/><template v-else>fine</template>`,
      )
      expect(code).matchSnapshot()
 -    expect(ir.template).toEqual(['<div></div>', '<p></p>', 'fine'])
 +    expect([...ir.template.keys()]).toEqual(['<div></div>', '<p></p>', 'fine'])
  
      expect(ir.block.returns).toEqual([0])
      expect(ir.block.dynamic.children[0].operation).toMatchObject({
index 350e4930025529ad5b43e6222db8e111666076ef,1bf99ec3834a3a86d39cc81665257195d07d863c..adb7cef1b623624773001b91fcfcbc9a5d64eb61
@@@ -5,21 -9,18 +9,21 @@@ import { genOperationWithInsertionStat
  import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
  
  export function genTemplates(
 -  templates: string[],
 +  templates: Map<string, number>,
    rootIndex: number | undefined,
-   { helper }: CodegenContext,
+   context: CodegenContext,
  ): string {
 -  return templates
 -    .map(
 -      (template, i) =>
 -        `const ${context.tName(i)} = ${context.helper('template')}(${JSON.stringify(
 -          template,
 -        )}${i === rootIndex ? ', true' : ''})\n`,
 +  const result: string[] = []
 +  let i = 0
 +  templates.forEach((ns, template) => {
 +    result.push(
-       `const t${i} = ${helper('template')}(${JSON.stringify(
++      `const ${context.tName(i)} = ${context.helper('template')}(${JSON.stringify(
 +        template,
 +      )}${i === rootIndex ? ', true' : ns ? ', false' : ''}${ns ? `, ${ns}` : ''})\n`,
      )
 -    .join('')
 +    i++
 +  })
 +  return result.join('')
  }
  
  export function genSelf(
index 72af535d385e0a837df6fccea72e9f0bae6e7213,b7ba56f99fcc83c7e03d0ed8e37b3eb209e12ff2..7556c54049c3c15fbeb7baff002d29917fc57793
@@@ -30,9 -30,14 +30,14 @@@ import 
  } from '@vue/runtime-core'
  import { extend } from '@vue/shared'
  
- const positionMap = new WeakMap<VNode, DOMRect>()
- const newPositionMap = new WeakMap<VNode, DOMRect>()
- const moveCbKey = Symbol('_moveCb')
+ interface Position {
+   top: number
+   left: number
+ }
+ const positionMap = new WeakMap<VNode, Position>()
+ const newPositionMap = new WeakMap<VNode, Position>()
 -export const moveCbKey: unique symbol = Symbol('_moveCb')
++const moveCbKey: unique symbol = Symbol('_moveCb')
  const enterCbKey = Symbol('_enterCb')
  
  export type TransitionGroupProps = Omit<TransitionProps, 'mode'> & {
index 6f2492af27b1f33451057020c553e514fece6b73,c4518b00291d268752ceba871571223984e63373..f275d65a82d5de3edcc707918ea25adcd9fbec82
@@@ -348,14 -347,27 +347,32 @@@ export 
    vModelSelectInit,
    vModelSetSelected,
  } from './directives/vModel'
- export { svgNS, mathmlNS } from './nodeOps'
 +/**
 + * @internal
 + */
 -  addTransitionClass,
 -  removeTransitionClass,
++export { svgNS } from './nodeOps'
 +/**
 + * @internal
 + */
 +export { xlinkNS } from './modules/attrs'
+ /**
+  * @internal
+  */
+ export {
+   resolveTransitionProps,
+   TransitionPropsValidators,
+   forceReflow,
 -  moveCbKey,
+   type ElementWithTransition,
+ } from './components/Transition'
+ /**
+  * @internal
+  */
+ export {
+   hasCSSTransform,
+   callPendingCbs,
+   handleMovedChildren,
+   baseApplyTranslation,
+ } from './components/TransitionGroup'
  /**
   * @internal
   */
index 066634e0295819a923c6fb54f100d08e76bc3cb8,f185e0e067bf4de1f269f289f026d45a0c5c561a..dd29281bac8e1d64e2ed9bf1caecaffd3e439f04
@@@ -11,8 -13,15 +13,15 @@@ import 
    setValue,
  } from '../../src/dom/prop'
  import { setStyle } from '../../src/dom/prop'
- import { VaporComponentInstance } from '../../src/component'
+ import { VaporComponentInstance, createComponent } from '../../src/component'
 -import { ref, setCurrentInstance } from '@vue/runtime-dom'
 +import { ref, setCurrentInstance, svgNS, xlinkNS } from '@vue/runtime-dom'
+ import { makeRender } from '../_utils'
+ import {
+   createDynamicComponent,
+   defineVaporComponent,
+   renderEffect,
+   template,
+ } from '../../src'
  
  let removeComponentInstance = NOOP
  beforeEach(() => {
index 6e3de7fe592879a1865e000ee0033d03a3474722,b104b20900d7687c46b7d654954e33273f7eedc7..6d41a7c1bce2b3406352016bf698e4e9e24a41c4
@@@ -14,9 -23,12 +23,13 @@@ import 
    mergeProps,
    patchStyle,
    shouldSetAsProp,
+   toClassSet,
+   toStyleMap,
    unsafeToTrustedHTML,
+   vShowHidden,
    warn,
+   warnPropMismatch,
 +  xlinkNS,
  } from '@vue/runtime-dom'
  import {
    type VaporComponentInstance,
@@@ -73,12 -74,21 +80,29 @@@ export function setAttr
      ;(el as any)._falseValue = value
    }
  
+   if (
+     (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
+     isHydrating &&
+     !attributeHasMismatch(el, key, value)
+   ) {
+     el[`$${key}`] = value
+     return
+   }
    if (value !== el[`$${key}`]) {
      el[`$${key}`] = value
--    if (value != null) {
--      el.setAttribute(key, value)
++    if (isSVG && key.startsWith('xlink:')) {
++      if (value != null) {
++        el.setAttributeNS(xlinkNS, key, value)
++      } else {
++        el.removeAttributeNS(xlinkNS, key.slice(6, key.length))
++      }
      } else {
--      el.removeAttribute(key)
++      if (value != null) {
++        el.setAttribute(key, value)
++      } else {
++        el.removeAttribute(key)
++      }
      }
    }
  }
@@@ -125,18 -152,22 +166,30 @@@ export function setDOMProp
    needRemove && el.removeAttribute(key)
  }
  
 -export function setClass(el: TargetElement, value: any): void {
 +export function setClass(
 +  el: TargetElement,
 +  value: any,
 +  isSVG: boolean = false,
 +): void {
    if (el.$root) {
      setClassIncremental(el, value)
-   } else if ((value = normalizeClass(value)) !== el.$cls) {
-     if (isSVG) {
-       el.setAttribute('class', (el.$cls = value))
-     } else {
-       el.className = el.$cls = value
+   } else {
+     value = normalizeClass(value)
+     if (
+       (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
+       isHydrating &&
+       !classHasMismatch(el, value, false)
+     ) {
+       el.$cls = value
+       return
+     }
+     if (value !== el.$cls) {
 -      el.className = el.$cls = value
++      if (isSVG) {
++        el.setAttribute('class', (el.$cls = value))
++      } else {
++        el.className = el.$cls = value
++      }
      }
    }
  }
@@@ -227,12 -377,35 +399,40 @@@ export function setHtml(el: TargetEleme
    }
  }
  
 -export function setDynamicProps(el: any, args: any[]): void {
+ export function setBlockHtml(
+   block: Block & { $html?: string },
+   value: any,
+ ): void {
+   value = value == null ? '' : unsafeToTrustedHTML(value)
+   if (block.$html !== value) {
+     setHtmlToBlock(block, (block.$html = value))
+   }
+ }
+ function setHtmlToBlock(block: Block, value: any): void {
+   if (block instanceof Node) {
+     if (block instanceof Element) {
+       block.innerHTML = value
+     } else if (__DEV__) {
+       warnCannotSetProp('innerHTML')
+     }
+   } else if (isVaporComponent(block)) {
+     setHtmlToBlock(block.block, value)
+   } else if (isArray(block)) {
+     if (__DEV__) {
+       warnCannotSetProp('innerHTML')
+     }
+   } else {
+     setHtmlToBlock(block.nodes, value)
+   }
+ }
 +export function setDynamicProps(
 +  el: any,
 +  args: any[],
 +  root?: boolean,
 +  isSVG?: boolean,
 +): void {
    const props = args.length > 1 ? mergeProps(...args) : args[0]
    const cacheKey = `$dprops${isApplyingFallthroughProps ? '$' : ''}`
    const prevKeys = el[cacheKey] as string[]
@@@ -258,10 -430,12 +458,11 @@@ export function setDynamicProp
    el: TargetElement,
    key: string,
    value: any,
 +  isSVG: boolean = false,
  ): void {
 -  // TODO
 -  const isSVG = false
+   let forceHydrate = false
    if (key === 'class') {
 -    setClass(el, value)
 +    setClass(el, value, isSVG)
    } else if (key === 'style') {
      setStyle(el, value)
    } else if (isOn(key)) {
      } 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 {
 -    setAttr(el, key, value)
 +    setAttr(el, key, value, isSVG)
    }
    return value
  }
index d4659abea29978e2fa8f892725a7744c1be74390,5db46476f71415da96139410e2b65257a8fe3773..23c56e0ca14f294dd2e3aeb88bb50dd0b954b05f
@@@ -1,6 -1,5 +1,6 @@@
  import { adoptTemplate, currentHydrationNode, isHydrating } from './hydration'
- import { child, createTextNode } from './node'
 -import { _child, createElement, createTextNode } from './node'
 +import { type Namespace, Namespaces } from '@vue/shared'
++import { _child, createTextNode } from './node'
  
  let t: HTMLTemplateElement
  
@@@ -20,15 -20,9 +21,15 @@@ export function template(html: string, 
        return createTextNode(html)
      }
      if (!node) {
 -      t = t || createElement('template')
 -      t.innerHTML = html
 -      node = _child(t.content)
 +      t = t || document.createElement('template')
 +      if (ns) {
 +        const tag = ns === Namespaces.SVG ? 'svg' : 'math'
 +        t.innerHTML = `<${tag}>${html}</${tag}>`
-         node = child(child(t.content) as ParentNode)
++        node = _child(_child(t.content) as ParentNode)
 +      } else {
 +        t.innerHTML = html
-         node = child(t.content)
++        node = _child(t.content)
 +      }
      }
      const ret = node.cloneNode(true)
      if (root) (ret as any).$root = true