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

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
ygj6 2024-01-22 23:03:39 +08:00 committed by GitHub
parent 51098cff94
commit 5028880e5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 94 additions and 56 deletions

View File

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

View File

@ -1,28 +1,28 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler v-bind > .attr modifier 1`] = ` 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) { export function render(_ctx) {
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
const n0 = t0() const n0 = t0()
const { 0: [n1],} = _children(n0) const { 0: [n1],} = _children(n0)
_renderEffect(() => { _renderEffect(() => {
_setDynamicProp(n1, "^foo-bar", undefined, _ctx.id) _setAttr(n1, "foo-bar", undefined, _ctx.id)
}) })
return n0 return n0
}" }"
`; `;
exports[`compiler v-bind > .attr modifier w/ no expression 1`] = ` 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) { export function render(_ctx) {
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
const n0 = t0() const n0 = t0()
const { 0: [n1],} = _children(n0) const { 0: [n1],} = _children(n0)
_renderEffect(() => { _renderEffect(() => {
_setDynamicProp(n1, "^foo-bar", undefined, _ctx.fooBar) _setAttr(n1, "foo-bar", undefined, _ctx.fooBar)
}) })
return n0 return n0
}" }"
@ -71,42 +71,42 @@ export function render(_ctx) {
`; `;
exports[`compiler v-bind > .prop modifier (shortband) w/ no expression 1`] = ` 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) { export function render(_ctx) {
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
const n0 = t0() const n0 = t0()
const { 0: [n1],} = _children(n0) const { 0: [n1],} = _children(n0)
_renderEffect(() => { _renderEffect(() => {
_setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar) _setDOMProp(n1, "fooBar", undefined, _ctx.fooBar)
}) })
return n0 return n0
}" }"
`; `;
exports[`compiler v-bind > .prop modifier (shorthand) 1`] = ` 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) { export function render(_ctx) {
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
const n0 = t0() const n0 = t0()
const { 0: [n1],} = _children(n0) const { 0: [n1],} = _children(n0)
_renderEffect(() => { _renderEffect(() => {
_setDynamicProp(n1, ".fooBar", undefined, _ctx.id) _setDOMProp(n1, "fooBar", undefined, _ctx.id)
}) })
return n0 return n0
}" }"
`; `;
exports[`compiler v-bind > .prop modifier 1`] = ` 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) { export function render(_ctx) {
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
const n0 = t0() const n0 = t0()
const { 0: [n1],} = _children(n0) const { 0: [n1],} = _children(n0)
_renderEffect(() => { _renderEffect(() => {
_setDynamicProp(n1, ".fooBar", undefined, _ctx.id) _setDOMProp(n1, "fooBar", undefined, _ctx.id)
}) })
return n0 return n0
}" }"
@ -127,14 +127,14 @@ export function render(_ctx) {
`; `;
exports[`compiler v-bind > .prop modifier w/ no expression 1`] = ` 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) { export function render(_ctx) {
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
const n0 = t0() const n0 = t0()
const { 0: [n1],} = _children(n0) const { 0: [n1],} = _children(n0)
_renderEffect(() => { _renderEffect(() => {
_setDynamicProp(n1, ".fooBar", undefined, _ctx.fooBar) _setDOMProp(n1, "fooBar", undefined, _ctx.fooBar)
}) })
return n0 return n0
}" }"

View File

@ -13,7 +13,7 @@ export function render(_ctx) {
`; `;
exports[`compiler: v-once > basic 1`] = ` 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) { export function render(_ctx) {
const t0 = _template("<div> <span></span></div>") const t0 = _template("<div> <span></span></div>")
@ -21,7 +21,7 @@ export function render(_ctx) {
const { 0: [n3, { 1: [n2],}],} = _children(n0) const { 0: [n3, { 1: [n2],}],} = _children(n0)
const n1 = _createTextNode(_ctx.msg) const n1 = _createTextNode(_ctx.msg)
_setText(n1, undefined, _ctx.msg) _setText(n1, undefined, _ctx.msg)
_setDynamicProp(n2, "class", undefined, _ctx.clz) _setClass(n2, "class", undefined, _ctx.clz)
_prepend(n3, n1) _prepend(n3, n1)
return n0 return n0
}" }"

View File

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

View File

@ -1,20 +1,56 @@
import type { CodegenContext } from '../generate' import type { CodegenContext } from '../generate'
import type { SetPropIRNode } from '../ir' import type { SetPropIRNode } from '../ir'
import { genExpression } from './expression' import { genExpression } from './expression'
import { isString } from '@vue/shared'
export function genSetProp(oper: SetPropIRNode, context: CodegenContext) { export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
newline() 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( pushFnCall(
vaporHelper('setDynamicProp'), vaporHelper('setDynamicProp'),
`n${oper.element}`, element,
// 2. key name // 2. key name
() => { () => {
if (oper.runtimeCamelize) { if (oper.runtimeCamelize) {
pushFnCall(helper('camelize'), () => genExpression(oper.key, context)) pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
} else if (oper.runtimePrefix) { } else if (oper.modifier) {
pushMulti([`\`${oper.runtimePrefix}\${`, `}\``], () => pushMulti([`\`${oper.modifier}\${`, `}\``], () =>
genExpression(oper.key, context), genExpression(oper.key, context),
) )
} else { } else {

View File

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

View File

@ -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()) { if (!exp.content.trim()) {
context.options.onError( context.options.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc), createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
@ -64,15 +56,12 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
key: arg, key: arg,
value: exp, value: exp,
runtimeCamelize: camel, 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
}