diff --git a/README.md b/README.md index 9a546d189..8ff103f92 100644 --- a/README.md +++ b/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 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap index 0f5e5203c..1484e4ea7 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap +++ b/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("
") 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("
") 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("
") 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("
") 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("
") 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("
") const n0 = t0() const { 0: [n1],} = _children(n0) _renderEffect(() => { - _setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar) + _setDOMProp(n1, "fooBar", undefined, _ctx.fooBar) }) return n0 }" diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 7a761ed3c..4fb9d979d 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/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("
") @@ -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 }" diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts index de35a2409..b8f5d335d 100644 --- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts +++ b/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(`
`) 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)') }) }) diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts index 89e0fc6df..4dd4a1c95 100644 --- a/packages/compiler-vapor/src/generators/prop.ts +++ b/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 { diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 00fcdb587..1c2f6ee30 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/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 { diff --git a/packages/compiler-vapor/src/transforms/vBind.ts b/packages/compiler-vapor/src/transforms/vBind.ts index 1379db91c..344324721 100644 --- a/packages/compiler-vapor/src/transforms/vBind.ts +++ b/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 -}