Преглед изворни кода

feat: generate specific function when the prop key is static (#97)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
ygj6 пре 2 година
родитељ
комит
5028880e5b

+ 3 - 3
README.md

@@ -29,10 +29,10 @@ PR are welcome!
   - [x] `v-bind`
     - [x] simple expression
     - [x] compound expression
-    - [ ] modifiers
+    - [x] modifiers
       - [x] .camel
-      - [ ] .prop
-      - [ ] .attr
+      - [x] .prop
+      - [x] .attr
   - [x] `v-on`
     - [x] simple expression
     - [x] compound expression

+ 12 - 12
packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap

@@ -1,28 +1,28 @@
 // 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';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } 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)
+    _setAttr(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';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } 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)
+    _setAttr(n1, "foo-bar", undefined, _ctx.fooBar)
   })
   return n0
 }"
@@ -71,42 +71,42 @@ export function render(_ctx) {
 `;
 
 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';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDOMProp as _setDOMProp } 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)
+    _setDOMProp(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';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDOMProp as _setDOMProp } 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)
+    _setDOMProp(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';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDOMProp as _setDOMProp } 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)
+    _setDOMProp(n1, "fooBar", undefined, _ctx.id)
   })
   return n0
 }"
@@ -127,14 +127,14 @@ export function render(_ctx) {
 `;
 
 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';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setDOMProp as _setDOMProp } 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)
+    _setDOMProp(n1, "fooBar", undefined, _ctx.fooBar)
   })
   return n0
 }"

+ 2 - 2
packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap

@@ -13,7 +13,7 @@ export function render(_ctx) {
 `;
 
 exports[`compiler: v-once > basic 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, setText as _setText, setDynamicProp as _setDynamicProp, prepend as _prepend } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, setText as _setText, setClass as _setClass, 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)
-  _setDynamicProp(n2, "class", undefined, _ctx.clz)
+  _setClass(n2, "class", undefined, _ctx.clz)
   _prepend(n3, n1)
   return n0
 }"

+ 32 - 19
packages/compiler-vapor/__tests__/transforms/vBind.spec.ts

@@ -189,6 +189,8 @@ describe('compiler v-bind', () => {
         content: `id`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: undefined,
     })
 
     expect(code).matchSnapshot()
@@ -207,6 +209,8 @@ describe('compiler v-bind', () => {
         content: `fooBar`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: undefined,
     })
 
     expect(code).matchSnapshot()
@@ -220,7 +224,6 @@ describe('compiler v-bind', () => {
     const { ir, code } = compileWithVBind(`<div v-bind:[foo].camel="id"/>`)
 
     expect(ir.effect[0].operations[0]).toMatchObject({
-      runtimeCamelize: true,
       key: {
         content: `foo`,
         isStatic: false,
@@ -229,6 +232,8 @@ describe('compiler v-bind', () => {
         content: `id`,
         isStatic: false,
       },
+      runtimeCamelize: true,
+      modifier: undefined,
     })
 
     expect(code).matchSnapshot()
@@ -245,18 +250,20 @@ describe('compiler v-bind', () => {
 
     expect(ir.effect[0].operations[0]).toMatchObject({
       key: {
-        content: `.fooBar`,
+        content: `fooBar`,
         isStatic: true,
       },
       value: {
         content: `id`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: '.',
     })
 
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
-    expect(code).contains('_setDynamicProp(n1, ".fooBar", undefined, _ctx.id)')
+    expect(code).contains('_setDOMProp(n1, "fooBar", undefined, _ctx.id)')
   })
 
   test('.prop modifier w/ no expression', () => {
@@ -264,20 +271,20 @@ describe('compiler v-bind', () => {
 
     expect(ir.effect[0].operations[0]).toMatchObject({
       key: {
-        content: `.fooBar`,
+        content: `fooBar`,
         isStatic: true,
       },
       value: {
         content: `fooBar`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: '.',
     })
 
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
-    expect(code).contains(
-      '_setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar)',
-    )
+    expect(code).contains('_setDOMProp(n1, "fooBar", undefined, _ctx.fooBar)')
   })
 
   test('.prop modifier w/ dynamic arg', () => {
@@ -292,6 +299,8 @@ describe('compiler v-bind', () => {
         content: `id`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: '.',
     })
 
     expect(code).matchSnapshot()
@@ -308,18 +317,20 @@ describe('compiler v-bind', () => {
 
     expect(ir.effect[0].operations[0]).toMatchObject({
       key: {
-        content: `.fooBar`,
+        content: `fooBar`,
         isStatic: true,
       },
       value: {
         content: `id`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: '.',
     })
 
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
-    expect(code).contains('_setDynamicProp(n1, ".fooBar", undefined, _ctx.id)')
+    expect(code).contains('_setDOMProp(n1, "fooBar", undefined, _ctx.id)')
   })
 
   test('.prop modifier (shortband) w/ no expression', () => {
@@ -327,20 +338,20 @@ describe('compiler v-bind', () => {
 
     expect(ir.effect[0].operations[0]).toMatchObject({
       key: {
-        content: `.fooBar`,
+        content: `fooBar`,
         isStatic: true,
       },
       value: {
         content: `fooBar`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: '.',
     })
 
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
-    expect(code).contains(
-      '_setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar)',
-    )
+    expect(code).contains('_setDOMProp(n1, "fooBar", undefined, _ctx.fooBar)')
   })
 
   test('.attr modifier', () => {
@@ -348,18 +359,20 @@ describe('compiler v-bind', () => {
 
     expect(ir.effect[0].operations[0]).toMatchObject({
       key: {
-        content: `^foo-bar`,
+        content: `foo-bar`,
         isStatic: true,
       },
       value: {
         content: `id`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: '^',
     })
 
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
-    expect(code).contains('_setDynamicProp(n1, "^foo-bar", undefined, _ctx.id)')
+    expect(code).contains('_setAttr(n1, "foo-bar", undefined, _ctx.id)')
   })
 
   test('.attr modifier w/ no expression', () => {
@@ -367,19 +380,19 @@ describe('compiler v-bind', () => {
 
     expect(ir.effect[0].operations[0]).toMatchObject({
       key: {
-        content: `^foo-bar`,
+        content: `foo-bar`,
         isStatic: true,
       },
       value: {
         content: `fooBar`,
         isStatic: false,
       },
+      runtimeCamelize: false,
+      modifier: '^',
     })
 
     expect(code).matchSnapshot()
     expect(code).contains('renderEffect')
-    expect(code).contains(
-      '_setDynamicProp(n1, "^foo-bar", undefined, _ctx.fooBar)',
-    )
+    expect(code).contains('_setAttr(n1, "foo-bar", undefined, _ctx.fooBar)')
   })
 })

+ 39 - 3
packages/compiler-vapor/src/generators/prop.ts

@@ -1,20 +1,56 @@
 import type { CodegenContext } from '../generate'
 import type { SetPropIRNode } from '../ir'
 import { genExpression } from './expression'
+import { isString } from '@vue/shared'
 
 export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
   const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
 
   newline()
+
+  const element = `n${oper.element}`
+
+  // fast path for static props
+  if (isString(oper.key) || oper.key.isStatic) {
+    const keyName = isString(oper.key) ? oper.key : oper.key.content
+
+    let helperName: string | undefined
+    if (keyName === 'class') {
+      helperName = 'setClass'
+    } else if (keyName === 'style') {
+      helperName = 'setStyle'
+    } else if (oper.modifier) {
+      helperName = oper.modifier === '.' ? 'setDOMProp' : 'setAttr'
+    }
+
+    if (helperName) {
+      pushFnCall(
+        vaporHelper(helperName),
+        element,
+        () => {
+          const expr = () => genExpression(oper.key, context)
+          if (oper.runtimeCamelize) {
+            pushFnCall(helper('camelize'), expr)
+          } else {
+            expr()
+          }
+        },
+        'undefined',
+        () => genExpression(oper.value, context),
+      )
+      return
+    }
+  }
+
   pushFnCall(
     vaporHelper('setDynamicProp'),
-    `n${oper.element}`,
+    element,
     // 2. key name
     () => {
       if (oper.runtimeCamelize) {
         pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
-      } else if (oper.runtimePrefix) {
-        pushMulti([`\`${oper.runtimePrefix}\${`, `}\``], () =>
+      } else if (oper.modifier) {
+        pushMulti([`\`${oper.modifier}\${`, `}\``], () =>
           genExpression(oper.key, context),
         )
       } else {

+ 1 - 1
packages/compiler-vapor/src/ir.ts

@@ -63,8 +63,8 @@ export interface SetPropIRNode extends BaseIRNode {
   element: number
   key: IRExpression
   value: IRExpression
+  modifier?: '.' | '^'
   runtimeCamelize: boolean
-  runtimePrefix?: string
 }
 
 export interface SetTextIRNode extends BaseIRNode {

+ 5 - 16
packages/compiler-vapor/src/transforms/vBind.ts

@@ -38,14 +38,6 @@ 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),
@@ -64,15 +56,12 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
         key: arg,
         value: exp,
         runtimeCamelize: camel,
-        runtimePrefix: prefix,
+        modifier: modifiers.includes('prop')
+          ? '.'
+          : modifiers.includes('attr')
+            ? '^'
+            : undefined,
       },
     ],
   )
 }
-
-const injectPrefix = (arg: SimpleExpressionNode, prefix: string) => {
-  if (!arg.isStatic) {
-    return prefix
-  }
-  arg.content = prefix + arg.content
-}