]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-vapor): correct execution order of operations (#13351)
authorzhiyuanzmj <260480378@qq.com>
Fri, 20 Jun 2025 00:55:50 +0000 (08:55 +0800)
committerGitHub <noreply@github.com>
Fri, 20 Jun 2025 00:55:50 +0000 (08:55 +0800)
packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-vapor/__tests__/compile.spec.ts
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
packages/compiler-vapor/src/compile.ts
packages/compiler-vapor/src/generators/operation.ts
packages/compiler-vapor/src/ir/index.ts
packages/compiler-vapor/src/transform.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/compiler-vapor/src/transforms/utils.ts

index d4a8b6827bb135edab82eb4ec07f448a305a3d61..b10a98d32cba702483ed45d2d0175ec8232ffc37 100644 (file)
@@ -149,7 +149,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
 `;
 
 exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
-"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
+"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
 const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
 const t1 = _template("<div> </div>")
 
@@ -161,8 +161,8 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
   _setInsertionState(n3, 0)
   const n1 = _createComponentWithFallback(_component_Comp)
   _renderEffect(() => {
-    _setText(n2, _toDisplayString(_ctx.bar))
     _setProp(n3, "id", _ctx.foo)
+    _setText(n2, _toDisplayString(_ctx.bar))
   })
   return [n0, n3]
 }"
@@ -180,7 +180,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > dynamic root nodes and interpolation 1`] = `
-"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue';
+"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue';
 const t0 = _template("<button> </button>", true)
 _delegateEvents("click")
 
@@ -190,13 +190,47 @@ export function render(_ctx) {
   n0.$evtclick = e => _ctx.handleClick(e)
   _renderEffect(() => {
     const _count = _ctx.count
-    _setText(x0, _toDisplayString(_count) + "foo" + _toDisplayString(_count) + "foo" + _toDisplayString(_count))
     _setProp(n0, "id", _count)
+    _setText(x0, _toDisplayString(_count) + "foo" + _toDisplayString(_count) + "foo" + _toDisplayString(_count))
   })
   return n0
 }"
 `;
 
+exports[`compile > execution order > basic 1`] = `
+"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div> </div>", true)
+
+export function render(_ctx) {
+  const n0 = t0()
+  const x0 = _child(n0)
+  _renderEffect(() => {
+    _setProp(n0, "id", _ctx.foo)
+    _setText(x0, _toDisplayString(_ctx.bar))
+  })
+  return n0
+}"
+`;
+
+exports[`compile > execution order > with v-once 1`] = `
+"import { child as _child, next as _next, nthChild as _nthChild, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("<div><span> </span> <br> </div>", true)
+
+export function render(_ctx) {
+  const n3 = t0()
+  const n0 = _child(n3)
+  const n1 = _next(n0)
+  const n2 = _nthChild(n3, 3)
+  const x0 = _child(n0)
+  _setText(x0, _toDisplayString(_ctx.foo))
+  _renderEffect(() => {
+    _setText(n1, " " + _toDisplayString(_ctx.bar))
+    _setText(n2, " " + _toDisplayString(_ctx.baz))
+  })
+  return n3
+}"
+`;
+
 exports[`compile > expression parsing > interpolation 1`] = `
 "
   const n0 = t0()
index 3a2ce41f0cdacdeeeaede0b7cca67e28742f41d9..178021d13dd21896f55f6fbbce381ccb4a082e3d 100644 (file)
@@ -237,4 +237,29 @@ describe('compile', () => {
       expect(code).toMatchSnapshot()
     })
   })
+
+  describe('execution order', () => {
+    test('basic', () => {
+      const code = compile(`<div :id="foo">{{ bar }}</div>`)
+      expect(code).matchSnapshot()
+      expect(code).contains(
+        `_setProp(n0, "id", _ctx.foo)
+    _setText(x0, _toDisplayString(_ctx.bar))`,
+      )
+    })
+    test('with v-once', () => {
+      const code = compile(
+        `<div>
+          <span v-once>{{ foo }}</span>
+          {{ bar }}<br>
+          {{ baz }}
+        </div>`,
+      )
+      expect(code).matchSnapshot()
+      expect(code).contains(
+        `_setText(n1, " " + _toDisplayString(_ctx.bar))
+    _setText(n2, " " + _toDisplayString(_ctx.baz))`,
+      )
+    })
+  })
 })
index 5ae8a94f5b1270948377e0f9d0969a96a0e27cc8..4a8caa659484883507e508218e5b2688137ef038 100644 (file)
@@ -67,7 +67,6 @@ export function render(_ctx) {
   const x2 = _child(n2)
   _renderEffect(() => {
     const _msg = _ctx.msg
-    
     _setText(x0, _toDisplayString(_msg))
     _setText(x1, _toDisplayString(_msg))
     _setText(x2, _toDisplayString(_msg))
index 9ffac2fb9328cf8af3c55322741a835886ea87b8..4e34c1818b1b6115a2df69014e946539a599f890 100644 (file)
@@ -55,7 +55,6 @@ export function render(_ctx) {
     const _foo = _ctx.foo
     const _bar = _ctx.bar
     const _foo_bar_baz = _foo[_bar(_ctx.baz)]
-    
     _setProp(n0, "id", _foo_bar_baz)
     _setProp(n1, "id", _foo_bar_baz)
     _setProp(n2, "id", _bar() + _foo)
@@ -107,7 +106,6 @@ export function render(_ctx) {
   _renderEffect(() => {
     const _obj = _ctx.obj
     const _obj_foo_baz_obj_bar = _obj['foo']['baz'] + _obj.bar
-    
     _setProp(n0, "id", _obj_foo_baz_obj_bar)
     _setProp(n1, "id", _obj_foo_baz_obj_bar)
   })
@@ -126,7 +124,6 @@ export function render(_ctx) {
   _renderEffect(() => {
     const _foo = _ctx.foo
     const _foo_bar = _foo + _ctx.bar
-    
     _setProp(n0, "id", _foo_bar)
     _setProp(n1, "id", _foo_bar)
     _setProp(n2, "id", _foo + _foo_bar)
@@ -144,7 +141,6 @@ export function render(_ctx) {
   const n1 = t0()
   _renderEffect(() => {
     const _foo_bar = _ctx.foo + _ctx.bar
-    
     _setProp(n0, "id", _foo_bar)
     _setProp(n1, "id", _foo_bar)
   })
@@ -177,7 +173,6 @@ export function render(_ctx) {
   const n1 = t0()
   _renderEffect(() => {
     const _foo = _ctx.foo
-    
     _setClass(n0, _foo)
     _setClass(n1, _foo)
   })
@@ -498,15 +493,13 @@ export function render(_ctx) {
     _setAttr(n0, "form", _ctx.form)
     _setAttr(n1, "list", _ctx.list)
     _setAttr(n2, "type", _ctx.type)
-    
     _setAttr(n3, "width", _width)
-    _setAttr(n4, "width", _width)
-    _setAttr(n5, "width", _width)
-    _setAttr(n6, "width", _width)
-    
     _setAttr(n3, "height", _height)
+    _setAttr(n4, "width", _width)
     _setAttr(n4, "height", _height)
+    _setAttr(n5, "width", _width)
     _setAttr(n5, "height", _height)
+    _setAttr(n6, "width", _width)
     _setAttr(n6, "height", _height)
   })
   return [n0, n1, n2, n3, n4, n5, n6]
index f84eafcbe0bfc9b0fe3e2c98071ca7f56f7ad5a7..c39037a47d864367c1ac56ff39e359a1aa0a9e0e 100644 (file)
@@ -81,8 +81,8 @@ export function getBaseTransformPreset(): TransformPreset {
       transformVFor,
       transformSlotOutlet,
       transformTemplateRef,
-      transformText,
       transformElement,
+      transformText,
       transformVSlot,
       transformComment,
       transformChildren,
index a3bf5cc21937a7ee271712c36071228cf906ae4d..563d72f1ee1c22c4ac0db8850e166631e3defc4a 100644 (file)
@@ -99,10 +99,8 @@ export function genEffects(
   effects: IREffect[],
   context: CodegenContext,
 ): CodeFragment[] {
-  const {
-    helper,
-    block: { expressions },
-  } = context
+  const { helper } = context
+  const expressions = effects.flatMap(effect => effect.expressions)
   const [frag, push, unshift] = buildCodeFragment()
   let operationsCount = 0
   const { ids, frag: declarationFrags } = processExpressions(
index da636113224c3bc7685b9c4de6d95ddfcd4f623e..18f0139ab568e4f082c88241bca7f6a87b53bb98 100644 (file)
@@ -52,7 +52,6 @@ export interface BlockIRNode extends BaseIRNode {
   tempId: number
   effect: IREffect[]
   operation: OperationNode[]
-  expressions: SimpleExpressionNode[]
   returns: number[]
 }
 
index 287bf671af39ade5d2d394a3aa62dd830fb62299..946c89b734afecb32ebdc9a0c2a7c39a9f51f597 100644 (file)
@@ -140,8 +140,10 @@ export class TransformContext<T extends AllNode = AllNode> {
 
   registerEffect(
     expressions: SimpleExpressionNode[],
-    ...operations: OperationNode[]
+    operation: OperationNode | OperationNode[],
+    getIndex = (): number => this.block.effect.length,
   ): void {
+    const operations = [operation].flat()
     expressions = expressions.filter(exp => !isConstantExpression(exp))
     if (
       this.inVOnce ||
@@ -153,26 +155,10 @@ export class TransformContext<T extends AllNode = AllNode> {
       return this.registerOperation(...operations)
     }
 
-    this.block.expressions.push(...expressions)
-    const existing = this.block.effect.find(e =>
-      isSameExpression(e.expressions, expressions),
-    )
-    if (existing) {
-      existing.operations.push(...operations)
-    } else {
-      this.block.effect.push({
-        expressions,
-        operations,
-      })
-    }
-
-    function isSameExpression(
-      a: SimpleExpressionNode[],
-      b: SimpleExpressionNode[],
-    ) {
-      if (a.length !== b.length) return false
-      return a.every((exp, i) => exp.content === b[i].content)
-    }
+    this.block.effect.splice(getIndex(), 0, {
+      expressions,
+      operations,
+    })
   }
 
   registerOperation(...node: OperationNode[]): void {
index 9839fb808075c390e0510198cd473c6985e63c55..05153e729aff475dd609bc8bb5bedfe3f1cde0d3 100644 (file)
@@ -44,6 +44,8 @@ export const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap(
 )
 
 export const transformElement: NodeTransform = (node, context) => {
+  let effectIndex = context.block.effect.length
+  const getEffectIndex = () => effectIndex++
   return function postTransformElement() {
     ;({ node } = context)
     if (
@@ -62,6 +64,7 @@ export const transformElement: NodeTransform = (node, context) => {
       context as TransformContext<ElementNode>,
       isComponent,
       isDynamicComponent,
+      getEffectIndex,
     )
 
     let { parent } = context
@@ -78,13 +81,23 @@ export const transformElement: NodeTransform = (node, context) => {
       parent.node.children.filter(child => child.type !== NodeTypes.COMMENT)
         .length === 1
 
-    ;(isComponent ? transformComponentElement : transformNativeElement)(
-      node as any,
-      propsResult,
-      singleRoot,
-      context as TransformContext<ElementNode>,
-      isDynamicComponent,
-    )
+    if (isComponent) {
+      transformComponentElement(
+        node as ComponentNode,
+        propsResult,
+        singleRoot,
+        context,
+        isDynamicComponent,
+      )
+    } else {
+      transformNativeElement(
+        node as PlainElementNode,
+        propsResult,
+        singleRoot,
+        context,
+        getEffectIndex,
+      )
+    }
   }
 }
 
@@ -183,7 +196,8 @@ function transformNativeElement(
   node: PlainElementNode,
   propsResult: PropsResult,
   singleRoot: boolean,
-  context: TransformContext<ElementNode>,
+  context: TransformContext,
+  getEffectIndex: () => number,
 ) {
   const { tag } = node
   const { scopeId } = context.options
@@ -196,12 +210,16 @@ function transformNativeElement(
   const dynamicProps: string[] = []
   if (propsResult[0] /* dynamic props */) {
     const [, dynamicArgs, expressions] = propsResult
-    context.registerEffect(expressions, {
-      type: IRNodeTypes.SET_DYNAMIC_PROPS,
-      element: context.reference(),
-      props: dynamicArgs,
-      root: singleRoot,
-    })
+    context.registerEffect(
+      expressions,
+      {
+        type: IRNodeTypes.SET_DYNAMIC_PROPS,
+        element: context.reference(),
+        props: dynamicArgs,
+        root: singleRoot,
+      },
+      getEffectIndex,
+    )
   } else {
     for (const prop of propsResult[1]) {
       const { key, values } = prop
@@ -210,13 +228,17 @@ function transformNativeElement(
         if (values[0].content) template += `="${values[0].content}"`
       } else {
         dynamicProps.push(key.content)
-        context.registerEffect(values, {
-          type: IRNodeTypes.SET_PROP,
-          element: context.reference(),
-          prop,
-          root: singleRoot,
-          tag,
-        })
+        context.registerEffect(
+          values,
+          {
+            type: IRNodeTypes.SET_PROP,
+            element: context.reference(),
+            prop,
+            root: singleRoot,
+            tag,
+          },
+          getEffectIndex,
+        )
       }
     }
   }
@@ -253,6 +275,7 @@ export function buildProps(
   context: TransformContext<ElementNode>,
   isComponent: boolean,
   isDynamicComponent?: boolean,
+  getEffectIndex?: () => number,
 ): PropsResult {
   const props = node.props as (VaporDirectiveNode | AttributeNode)[]
   if (props.length === 0) return [false, []]
@@ -299,12 +322,12 @@ export function buildProps(
           } else {
             context.registerEffect(
               [prop.exp],
-
               {
                 type: IRNodeTypes.SET_DYNAMIC_EVENTS,
                 element: context.reference(),
                 event: prop.exp,
               },
+              getEffectIndex,
             )
           }
         } else {
index b8e7adc60596dace721318181395ba43abf35c44..f7d0594fe58151ac73eae39833d1978343d7116b 100644 (file)
@@ -29,7 +29,6 @@ export const newBlock = (node: BlockIRNode['node']): BlockIRNode => ({
   effect: [],
   operation: [],
   returns: [],
-  expressions: [],
   tempId: 0,
 })