]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: prop and attr modifiers for v-bind (#79)
authorygj6 <7699524+ygj6@users.noreply.github.com>
Sat, 20 Jan 2024 05:31:16 +0000 (13:31 +0800)
committerGitHub <noreply@github.com>
Sat, 20 Jan 2024 05:31:16 +0000 (13:31 +0800)
packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transforms/vBind.ts
packages/runtime-vapor/src/dom.ts

index ae7ea0f2d1ea6167d701cc077cfb9b1786b655f4..05b85c443e26a761ccbad5827b1f37aa2c11c650 100644 (file)
@@ -121,7 +121,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > directives > v-pre > self-closing v-pre 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div><div><Comp></Comp></div>")
@@ -133,14 +133,14 @@ export function render(_ctx) {
     _setText(n1, undefined, _ctx.bar)
   })
   _renderEffect(() => {
-    _setAttr(n2, "id", undefined, _ctx.foo)
+    _setDynamicProp(n2, "id", undefined, _ctx.foo)
   })
   return n0
 }"
 `;
 
 exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div><div><Comp></Comp></div>")
@@ -152,7 +152,7 @@ export function render(_ctx) {
     _setText(n1, undefined, _ctx.bar)
   })
   _renderEffect(() => {
-    _setAttr(n2, "id", undefined, _ctx.foo)
+    _setDynamicProp(n2, "id", undefined, _ctx.foo)
   })
   return n0
 }"
@@ -179,7 +179,7 @@ export function render(_ctx) {
 `;
 
 exports[`compile > dynamic root nodes and interpolation 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, on as _on, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, on as _on, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<button>foo<!>foo</button>")
@@ -196,7 +196,7 @@ export function render(_ctx) {
     _setText(n1, undefined, _ctx.count)
     _setText(n2, undefined, _ctx.count)
     _setText(n3, undefined, _ctx.count)
-    _setAttr(n4, "id", undefined, _ctx.count)
+    _setDynamicProp(n4, "id", undefined, _ctx.count)
   })
   return n0
 }"
@@ -222,7 +222,7 @@ exports[`compile > expression parsing > v-bind 1`] = `
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, key.value+1, undefined, _unref(foo)[key.value+1]())
+    _setDynamicProp(n1, key.value+1, undefined, _unref(foo)[key.value+1]())
   })
   return n0
 })()"
index fa70b0f374c85e7e39c4e8ea64c72fee5336fdfd..0f5e5203cd8e9ed156d518d736b13539aa73de95 100644 (file)
@@ -1,14 +1,42 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`compiler v-bind > .attr modifier 1`] = `
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _renderEffect(() => {
+    _setDynamicProp(n1, "^foo-bar", undefined, _ctx.id)
+  })
+  return n0
+}"
+`;
+
+exports[`compiler v-bind > .attr modifier w/ no expression 1`] = `
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _renderEffect(() => {
+    _setDynamicProp(n1, "^foo-bar", undefined, _ctx.fooBar)
+  })
+  return n0
+}"
+`;
+
 exports[`compiler v-bind > .camel modifier 1`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, "fooBar", undefined, _ctx.id)
+    _setDynamicProp(n1, "fooBar", undefined, _ctx.id)
   })
   return n0
 }"
@@ -22,77 +50,147 @@ export function render(_ctx) {
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)
+    _setDynamicProp(n1, _camelize(_ctx.foo), undefined, _ctx.id)
   })
   return n0
 }"
 `;
 
 exports[`compiler v-bind > .camel modifier w/ no expression 1`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _renderEffect(() => {
+    _setDynamicProp(n1, "fooBar", undefined, _ctx.fooBar)
+  })
+  return n0
+}"
+`;
+
+exports[`compiler v-bind > .prop modifier (shortband) w/ no expression 1`] = `
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _renderEffect(() => {
+    _setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar)
+  })
+  return n0
+}"
+`;
+
+exports[`compiler v-bind > .prop modifier (shorthand) 1`] = `
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _renderEffect(() => {
+    _setDynamicProp(n1, ".fooBar", undefined, _ctx.id)
+  })
+  return n0
+}"
+`;
+
+exports[`compiler v-bind > .prop modifier 1`] = `
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _renderEffect(() => {
+    _setDynamicProp(n1, ".fooBar", undefined, _ctx.id)
+  })
+  return n0
+}"
+`;
+
+exports[`compiler v-bind > .prop modifier w/ dynamic arg 1`] = `
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _renderEffect(() => {
+    _setDynamicProp(n1, \`.\${_ctx.fooBar}\`, undefined, _ctx.id)
+  })
+  return n0
+}"
+`;
+
+exports[`compiler v-bind > .prop modifier w/ no expression 1`] = `
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, "fooBar", undefined, _ctx.fooBar)
+    _setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar)
   })
   return n0
 }"
 `;
 
 exports[`compiler v-bind > basic 1`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, "id", undefined, _ctx.id)
+    _setDynamicProp(n1, "id", undefined, _ctx.id)
   })
   return n0
 }"
 `;
 
 exports[`compiler v-bind > dynamic arg 1`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, _ctx.id, undefined, _ctx.id)
+    _setDynamicProp(n1, _ctx.id, undefined, _ctx.id)
   })
   return n0
 }"
 `;
 
 exports[`compiler v-bind > no expression (shorthand) 1`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, "camel-case", undefined, _ctx.camelCase)
+    _setDynamicProp(n1, "camel-case", undefined, _ctx.camelCase)
   })
   return n0
 }"
 `;
 
 exports[`compiler v-bind > no expression 1`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
   _renderEffect(() => {
-    _setAttr(n1, "id", undefined, _ctx.id)
+    _setDynamicProp(n1, "id", undefined, _ctx.id)
   })
   return n0
 }"
index 4b8e89d6d6f4f7e33d4e730254d2f748f4e661e2..7a761ed3c66bce7242a5a6d8d322b594b5a83b17 100644 (file)
@@ -1,19 +1,19 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 exports[`compiler: v-once > as root node 1`] = `
-"import { template as _template, children as _children, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div></div>")
   const n0 = t0()
   const { 0: [n1],} = _children(n0)
-  _setAttr(n1, "id", undefined, _ctx.foo)
+  _setDynamicProp(n1, "id", undefined, _ctx.foo)
   return n0
 }"
 `;
 
 exports[`compiler: v-once > basic 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, setText as _setText, setAttr as _setAttr, prepend as _prepend } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, setText as _setText, setDynamicProp as _setDynamicProp, prepend as _prepend } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div> <span></span></div>")
@@ -21,7 +21,7 @@ export function render(_ctx) {
   const { 0: [n3, { 1: [n2],}],} = _children(n0)
   const n1 = _createTextNode(_ctx.msg)
   _setText(n1, undefined, _ctx.msg)
-  _setAttr(n2, "class", undefined, _ctx.clz)
+  _setDynamicProp(n2, "class", undefined, _ctx.clz)
   _prepend(n3, n1)
   return n0
 }"
@@ -38,13 +38,13 @@ export function render(_ctx) {
 `;
 
 exports[`compiler: v-once > on nested plain element 1`] = `
-"import { template as _template, children as _children, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, setDynamicProp as _setDynamicProp } from 'vue/vapor';
 
 export function render(_ctx) {
   const t0 = _template("<div><div></div></div>")
   const n0 = t0()
   const { 0: [, { 0: [n1],}],} = _children(n0)
-  _setAttr(n1, "id", undefined, _ctx.foo)
+  _setDynamicProp(n1, "id", undefined, _ctx.foo)
   return n0
 }"
 `;
index 3c7932684b3b997ef9abaf4691ba7625620a6929..de35a24094d033e05726b8c0a5c1a93c4af54ab2 100644 (file)
@@ -83,7 +83,7 @@ describe('compiler v-bind', () => {
     })
 
     expect(code).matchSnapshot()
-    expect(code).contains('_setAttr(n1, "id", undefined, _ctx.id)')
+    expect(code).contains('_setDynamicProp(n1, "id", undefined, _ctx.id)')
   })
 
   test('no expression', () => {
@@ -110,7 +110,7 @@ describe('compiler v-bind', () => {
     })
 
     expect(code).matchSnapshot()
-    expect(code).contains('_setAttr(n1, "id", undefined, _ctx.id)')
+    expect(code).contains('_setDynamicProp(n1, "id", undefined, _ctx.id)')
   })
 
   test('no expression (shorthand)', () => {
@@ -130,7 +130,7 @@ describe('compiler v-bind', () => {
 
     expect(code).matchSnapshot()
     expect(code).contains(
-      '_setAttr(n1, "camel-case", undefined, _ctx.camelCase)',
+      '_setDynamicProp(n1, "camel-case", undefined, _ctx.camelCase)',
     )
   })
 
@@ -152,7 +152,7 @@ describe('compiler v-bind', () => {
     })
 
     expect(code).matchSnapshot()
-    expect(code).contains('_setAttr(n1, _ctx.id, undefined, _ctx.id)')
+    expect(code).contains('_setDynamicProp(n1, _ctx.id, undefined, _ctx.id)')
   })
 
   test('should error if empty expression', () => {
@@ -192,7 +192,7 @@ describe('compiler v-bind', () => {
     })
 
     expect(code).matchSnapshot()
-    expect(code).contains('_setAttr(n1, "fooBar", undefined, _ctx.id)')
+    expect(code).contains('_setDynamicProp(n1, "fooBar", undefined, _ctx.id)')
   })
 
   test('.camel modifier w/ no expression', () => {
@@ -211,7 +211,9 @@ describe('compiler v-bind', () => {
 
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
-    expect(code).contains('_setAttr(n1, "fooBar", undefined, _ctx.fooBar)')
+    expect(code).contains(
+      '_setDynamicProp(n1, "fooBar", undefined, _ctx.fooBar)',
+    )
   })
 
   test('.camel modifier w/ dynamic arg', () => {
@@ -232,18 +234,152 @@ describe('compiler v-bind', () => {
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
     expect(code).contains(
-      `_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)`,
+      `_setDynamicProp(n1, _camelize(_ctx.foo), undefined, _ctx.id)`,
     )
   })
 
   test.todo('.camel modifier w/ dynamic arg + prefixIdentifiers')
 
-  test.todo('.prop modifier')
-  test.todo('.prop modifier w/ no expression')
-  test.todo('.prop modifier w/ dynamic arg')
+  test('.prop modifier', () => {
+    const { ir, code } = compileWithVBind(`<div v-bind:fooBar.prop="id"/>`)
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      key: {
+        content: `.fooBar`,
+        isStatic: true,
+      },
+      value: {
+        content: `id`,
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+    expect(code).contains('renderEffect')
+    expect(code).contains('_setDynamicProp(n1, ".fooBar", undefined, _ctx.id)')
+  })
+
+  test('.prop modifier w/ no expression', () => {
+    const { ir, code } = compileWithVBind(`<div v-bind:fooBar.prop />`)
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      key: {
+        content: `.fooBar`,
+        isStatic: true,
+      },
+      value: {
+        content: `fooBar`,
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+    expect(code).contains('renderEffect')
+    expect(code).contains(
+      '_setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar)',
+    )
+  })
+
+  test('.prop modifier w/ dynamic arg', () => {
+    const { ir, code } = compileWithVBind(`<div v-bind:[fooBar].prop="id"/>`)
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      key: {
+        content: `fooBar`,
+        isStatic: false,
+      },
+      value: {
+        content: `id`,
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+    expect(code).contains('renderEffect')
+    expect(code).contains(
+      '_setDynamicProp(n1, `.${_ctx.fooBar}`, undefined, _ctx.id)',
+    )
+  })
+
   test.todo('.prop modifier w/ dynamic arg + prefixIdentifiers')
-  test.todo('.prop modifier (shorthand)')
-  test.todo('.prop modifier (shortband) w/ no expression')
-  test.todo('.attr modifier')
-  test.todo('.attr modifier w/ no expression')
+
+  test('.prop modifier (shorthand)', () => {
+    const { ir, code } = compileWithVBind(`<div .fooBar="id"/>`)
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      key: {
+        content: `.fooBar`,
+        isStatic: true,
+      },
+      value: {
+        content: `id`,
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+    expect(code).contains('renderEffect')
+    expect(code).contains('_setDynamicProp(n1, ".fooBar", undefined, _ctx.id)')
+  })
+
+  test('.prop modifier (shortband) w/ no expression', () => {
+    const { ir, code } = compileWithVBind(`<div .fooBar />`)
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      key: {
+        content: `.fooBar`,
+        isStatic: true,
+      },
+      value: {
+        content: `fooBar`,
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+    expect(code).contains('renderEffect')
+    expect(code).contains(
+      '_setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar)',
+    )
+  })
+
+  test('.attr modifier', () => {
+    const { ir, code } = compileWithVBind(`<div v-bind:foo-bar.attr="id"/>`)
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      key: {
+        content: `^foo-bar`,
+        isStatic: true,
+      },
+      value: {
+        content: `id`,
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+    expect(code).contains('renderEffect')
+    expect(code).contains('_setDynamicProp(n1, "^foo-bar", undefined, _ctx.id)')
+  })
+
+  test('.attr modifier w/ no expression', () => {
+    const { ir, code } = compileWithVBind(`<div v-bind:foo-bar.attr />`)
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      key: {
+        content: `^foo-bar`,
+        isStatic: true,
+      },
+      value: {
+        content: `fooBar`,
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+    expect(code).contains('renderEffect')
+    expect(code).contains(
+      '_setDynamicProp(n1, "^foo-bar", undefined, _ctx.fooBar)',
+    )
+  })
 })
index 9b6543e1e25b418ae8c5fe99cb93270dd99fc107..d5f6215104a66acb1be07faa2bc5de218065f9fa 100644 (file)
@@ -403,16 +403,20 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
 }
 
 function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
-  const { pushFnCall, newline, vaporHelper, helper } = context
+  const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
 
   newline()
   pushFnCall(
-    vaporHelper('setAttr'),
+    vaporHelper('setDynamicProp'),
     `n${oper.element}`,
     // 2. key name
     () => {
       if (oper.runtimeCamelize) {
         pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
+      } else if (oper.runtimePrefix) {
+        pushMulti([`\`${oper.runtimePrefix}\${`, `}\``], () =>
+          genExpression(oper.key, context),
+        )
       } else {
         genExpression(oper.key, context)
       }
index 2af5455d628eec80cc19dbf25f325a7cff3a767c..ae76aa92a03bbdfacdd2ffd27d348790eff8e737 100644 (file)
@@ -61,6 +61,7 @@ export interface SetPropIRNode extends BaseIRNode {
   key: IRExpression
   value: IRExpression
   runtimeCamelize: boolean
+  runtimePrefix?: string
 }
 
 export interface SetTextIRNode extends BaseIRNode {
index 081b9eef1f64ae0c0acca99bff04d1a71e154417..4e013c83619b7e2b9ecaa55b41fd9801e51904fb 100644 (file)
@@ -1,5 +1,6 @@
 import {
   ErrorCodes,
+  type SimpleExpressionNode,
   createCompilerError,
   createSimpleExpression,
 } from '@vue/compiler-core'
@@ -30,6 +31,14 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
     }
   }
 
+  let prefix: string | undefined
+  if (modifiers.includes('prop')) {
+    prefix = injectPrefix(arg, '.')
+  }
+  if (modifiers.includes('attr')) {
+    prefix = injectPrefix(arg, '^')
+  }
+
   if (!exp.content.trim()) {
     context.options.onError(
       createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
@@ -48,7 +57,15 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
         key: arg,
         value: exp,
         runtimeCamelize: camel,
+        runtimePrefix: prefix,
       },
     ],
   )
 }
+
+const injectPrefix = (arg: SimpleExpressionNode, prefix: string) => {
+  if (!arg.isStatic) {
+    return prefix
+  }
+  arg.content = prefix + arg.content
+}
index 1dda46f2170a77f62c77675ae8ce8c0fc276c2f8..595349df017b82d1e258014657579d1b50859189 100644 (file)
@@ -88,16 +88,34 @@ export function setAttr(el: Element, key: string, oldVal: any, newVal: any) {
   }
 }
 
-export function setDynamicProp(el: Element, key: string, val: any) {
+export function setDOMProp(el: any, key: string, oldVal: any, newVal: any) {
+  // TODO special checks
+  if (newVal !== oldVal) {
+    el[key] = newVal
+  }
+}
+
+export function setDynamicProp(
+  el: Element,
+  key: string,
+  oldVal: any,
+  newVal: any,
+) {
   if (key === 'class') {
-    setClass(el, void 0, val)
+    setClass(el, oldVal, newVal)
   } else if (key === 'style') {
-    setStyle(el as HTMLElement, void 0, val)
-  } else if (key in el) {
-    ;(el as any)[key] = val
+    setStyle(el as HTMLElement, oldVal, newVal)
+  } else if (
+    key[0] === '.'
+      ? ((key = key.slice(1)), true)
+      : key[0] === '^'
+        ? ((key = key.slice(1)), false)
+        : key in el
+  ) {
+    setDOMProp(el, key, oldVal, newVal)
   } else {
     // TODO special checks
-    setAttr(el, key, void 0, val)
+    setAttr(el, key, oldVal, newVal)
   }
 }