feat(compiler-vapor): component with dynamic arguments (#192)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
Rizumu Ayaka 2024-05-01 02:11:32 +08:00 committed by GitHub
parent 6f7d219654
commit 8dea04bd7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 365 additions and 171 deletions

View File

@ -100,9 +100,9 @@ exports[`compiler: element transform > component > should wrap as function if v-
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [{
onBar: () => $event => (_ctx.handleBar($event))
}], true)
const n0 = _createComponent(_component_Foo, [
{ onBar: () => $event => (_ctx.handleBar($event)) }
], true)
return n0
}"
`;
@ -112,10 +112,12 @@ exports[`compiler: element transform > component > static props 1`] = `
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [{
id: () => ("foo"),
class: () => ("bar")
}], true)
const n0 = _createComponent(_component_Foo, [
{
id: () => ("foo"),
class: () => ("bar")
}
], true)
return n0
}"
`;
@ -125,7 +127,9 @@ exports[`compiler: element transform > component > v-bind="obj" 1`] = `
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [() => (_ctx.obj)], true)
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj)
], true)
return n0
}"
`;
@ -135,9 +139,10 @@ exports[`compiler: element transform > component > v-bind="obj" after static pro
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [{
id: () => ("foo")
}, () => (_ctx.obj)], true)
const n0 = _createComponent(_component_Foo, [
{ id: () => ("foo") },
() => (_ctx.obj)
], true)
return n0
}"
`;
@ -147,9 +152,10 @@ exports[`compiler: element transform > component > v-bind="obj" before static pr
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [() => (_ctx.obj), {
id: () => ("foo")
}], true)
const n0 = _createComponent(_component_Foo, [
() => (_ctx.obj),
{ id: () => ("foo") }
], true)
return n0
}"
`;
@ -159,11 +165,11 @@ exports[`compiler: element transform > component > v-bind="obj" between static p
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [{
id: () => ("foo")
}, () => (_ctx.obj), {
class: () => ("bar")
}], true)
const n0 = _createComponent(_component_Foo, [
{ id: () => ("foo") },
() => (_ctx.obj),
{ class: () => ("bar") }
], true)
return n0
}"
`;
@ -174,7 +180,36 @@ import { resolveComponent as _resolveComponent, createComponent as _createCompon
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [() => (_toHandlers(_ctx.obj))], true)
const n0 = _createComponent(_component_Foo, [
() => (_toHandlers(_ctx.obj))
], true)
return n0
}"
`;
exports[`compiler: element transform > component with dynamic event arguments 1`] = `
"import { toHandlerKey as _toHandlerKey } from 'vue';
import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
() => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }),
() => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux })
], true)
return n0
}"
`;
exports[`compiler: element transform > component with dynamic prop arguments 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponent(_component_Foo, [
() => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }),
() => ({ [_ctx.baz]: _ctx.qux })
], true)
return n0
}"
`;

View File

@ -5,11 +5,11 @@ exports[`compiler: vModel transform > component > v-model for component should g
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponent(_component_Comp, [{
modelValue: () => (_ctx.foo),
const n0 = _createComponent(_component_Comp, [
{ modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
modelModifiers: () => ({ trim: true, "bar-baz": true })
}], true)
modelModifiers: () => ({ trim: true, "bar-baz": true }) }
], true)
return n0
}"
`;
@ -19,10 +19,10 @@ exports[`compiler: vModel transform > component > v-model for component should w
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponent(_component_Comp, [{
modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event)
}], true)
const n0 = _createComponent(_component_Comp, [
{ modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => $event => (_ctx.foo = $event) }
], true)
return n0
}"
`;
@ -32,14 +32,16 @@ exports[`compiler: vModel transform > component > v-model with arguments for com
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponent(_component_Comp, [{
foo: () => (_ctx.foo),
"onUpdate:foo": () => $event => (_ctx.foo = $event),
fooModifiers: () => ({ trim: true }),
bar: () => (_ctx.bar),
"onUpdate:bar": () => $event => (_ctx.bar = $event),
barModifiers: () => ({ number: true })
}], true)
const n0 = _createComponent(_component_Comp, [
{
foo: () => (_ctx.foo),
"onUpdate:foo": () => $event => (_ctx.foo = $event),
fooModifiers: () => ({ trim: true }),
bar: () => (_ctx.bar),
"onUpdate:bar": () => $event => (_ctx.bar = $event),
barModifiers: () => ({ number: true })
}
], true)
return n0
}"
`;
@ -49,10 +51,10 @@ exports[`compiler: vModel transform > component > v-model with arguments for com
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponent(_component_Comp, [{
bar: () => (_ctx.foo),
"onUpdate:bar": () => $event => (_ctx.foo = $event)
}], true)
const n0 = _createComponent(_component_Comp, [
{ bar: () => (_ctx.foo),
"onUpdate:bar": () => $event => (_ctx.foo = $event) }
], true)
return n0
}"
`;
@ -62,14 +64,14 @@ exports[`compiler: vModel transform > component > v-model with dynamic arguments
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponent(_component_Comp, [{
[_ctx.foo]: () => (_ctx.foo),
const n0 = _createComponent(_component_Comp, [
() => ({ [_ctx.foo]: _ctx.foo,
["onUpdate:" + _ctx.foo]: () => $event => (_ctx.foo = $event),
[_ctx.foo + "Modifiers"]: () => ({ trim: true }),
[_ctx.bar]: () => (_ctx.bar),
[_ctx.foo + "Modifiers"]: () => ({ trim: true }) }),
() => ({ [_ctx.bar]: _ctx.bar,
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
[_ctx.bar + "Modifiers"]: () => ({ number: true })
}], true)
[_ctx.bar + "Modifiers"]: () => ({ number: true }) })
], true)
return n0
}"
`;
@ -79,10 +81,10 @@ exports[`compiler: vModel transform > component > v-model with dynamic arguments
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponent(_component_Comp, [{
[_ctx.arg]: () => (_ctx.foo),
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event)
}], true)
const n0 = _createComponent(_component_Comp, [
() => ({ [_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) })
], true)
return n0
}"
`;

View File

@ -1,5 +1,6 @@
import { makeCompile } from './_utils'
import {
IRDynamicPropsKind,
IRNodeTypes,
transformChildren,
transformElement,
@ -198,10 +199,12 @@ describe('compiler: element transform', () => {
)
expect(code).toMatchSnapshot()
expect(code).contains('_createComponent(_component_Foo, [{')
expect(code).contains(' id: () => ("foo")')
expect(code).contains(' class: () => ("bar")')
expect(code).contains('}], true)')
expect(code).contains(`[
{
id: () => ("foo"),
class: () => ("bar")
}
]`)
expect(ir.block.operation).toMatchObject([
{
@ -248,12 +251,19 @@ describe('compiler: element transform', () => {
test('v-bind="obj"', () => {
const { code, ir } = compileWithElementTransform(`<Foo v-bind="obj" />`)
expect(code).toMatchSnapshot()
expect(code).contains('[() => (_ctx.obj)]')
expect(code).contains(`[
() => (_ctx.obj)
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [{ value: { content: 'obj', isStatic: false } }],
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj', isStatic: false },
},
],
},
])
})
@ -263,15 +273,20 @@ describe('compiler: element transform', () => {
`<Foo id="foo" v-bind="obj" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains('id: () => ("foo")')
expect(code).contains('}, () => (_ctx.obj)]')
expect(code).contains(`[
{ id: () => ("foo") },
() => (_ctx.obj)
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{ value: { content: 'obj' } },
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
],
},
])
@ -282,14 +297,19 @@ describe('compiler: element transform', () => {
`<Foo v-bind="obj" id="foo" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains('[() => (_ctx.obj), {')
expect(code).contains('id: () => ("foo")')
expect(code).contains(`[
() => (_ctx.obj),
{ id: () => ("foo") }
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{ value: { content: 'obj' } },
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
},
@ -301,16 +321,21 @@ describe('compiler: element transform', () => {
`<Foo id="foo" v-bind="obj" class="bar" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains('id: () => ("foo")')
expect(code).contains('}, () => (_ctx.obj), {')
expect(code).contains('class: () => ("bar")')
expect(code).contains(`[
{ id: () => ("foo") },
() => (_ctx.obj),
{ class: () => ("bar") }
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{ value: { content: 'obj' } },
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
},
@ -356,12 +381,20 @@ describe('compiler: element transform', () => {
test('v-on="obj"', () => {
const { code, ir } = compileWithElementTransform(`<Foo v-on="obj" />`)
expect(code).toMatchSnapshot()
expect(code).contains('[() => (_toHandlers(_ctx.obj))]')
expect(code).contains(`[
() => (_toHandlers(_ctx.obj))
]`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [{ value: { content: 'obj' }, handler: true }],
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
handler: true,
},
],
},
])
})
@ -432,6 +465,7 @@ describe('compiler: element transform', () => {
element: 0,
props: [
{
kind: IRDynamicPropsKind.EXPRESSION,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
@ -467,6 +501,7 @@ describe('compiler: element transform', () => {
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
kind: IRDynamicPropsKind.EXPRESSION,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
@ -494,7 +529,10 @@ describe('compiler: element transform', () => {
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
{ value: { content: 'obj' } },
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
},
@ -518,7 +556,10 @@ describe('compiler: element transform', () => {
element: 0,
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{ value: { content: 'obj' } },
{
kind: IRDynamicPropsKind.EXPRESSION,
value: { content: 'obj' },
},
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
},
@ -691,6 +732,58 @@ describe('compiler: element transform', () => {
expect(code).contains('_setDynamicEvents(n0, _ctx.obj)')
})
test('component with dynamic prop arguments', () => {
const { code, ir } = compileWithElementTransform(
`<Foo :[foo-bar]="bar" :[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
},
],
},
])
})
test('component with dynamic event arguments', () => {
const { code, ir } = compileWithElementTransform(
`<Foo @[foo-bar]="bar" @[baz]="qux" />`,
)
expect(code).toMatchSnapshot()
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'foo-bar' },
values: [{ content: 'bar' }],
handler: true,
},
{
kind: IRDynamicPropsKind.ATTRIBUTE,
key: { content: 'baz' },
values: [{ content: 'qux' }],
handler: true,
},
],
},
])
})
test('invalid html nesting', () => {
const { code, ir } = compileWithElementTransform(
`<p><div>123</div></p>

View File

@ -259,7 +259,7 @@ describe('compiler: vModel transform', () => {
const { code, ir } = compileWithVModel('<Comp v-model:[arg]="foo" />')
expect(code).toMatchSnapshot()
expect(code).contains(
`[_ctx.arg]: () => (_ctx.foo),
`[_ctx.arg]: _ctx.foo,
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event)`,
)
expect(ir.block.operation).toMatchObject([
@ -267,14 +267,12 @@ describe('compiler: vModel transform', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'arg', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: [],
},
],
{
key: { content: 'arg', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: [],
},
],
},
])
@ -349,20 +347,18 @@ describe('compiler: vModel transform', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Comp',
props: [
[
{
key: { content: 'foo', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim'],
},
{
key: { content: 'bar', isStatic: false },
values: [{ content: 'bar', isStatic: false }],
model: true,
modelModifiers: ['number'],
},
],
{
key: { content: 'foo', isStatic: false },
values: [{ content: 'foo', isStatic: false }],
model: true,
modelModifiers: ['trim'],
},
{
key: { content: 'bar', isStatic: false },
values: [{ content: 'bar', isStatic: false }],
model: true,
modelModifiers: ['number'],
},
],
},
])

View File

@ -1,10 +1,17 @@
import { camelize, extend, isArray } from '@vue/shared'
import type { CodegenContext } from '../generate'
import type { CreateComponentIRNode, IRProp } from '../ir'
import {
type CreateComponentIRNode,
IRDynamicPropsKind,
type IRProp,
type IRProps,
type IRPropsStatic,
} from '../ir'
import {
type CodeFragment,
NEWLINE,
SEGMENTS_ARRAY,
SEGMENTS_ARRAY_NEWLINE,
SEGMENTS_OBJECT,
SEGMENTS_OBJECT_NEWLINE,
genCall,
genMulti,
@ -21,11 +28,11 @@ export function genCreateComponent(
oper: CreateComponentIRNode,
context: CodegenContext,
): CodeFragment[] {
const { helper, vaporHelper } = context
const { vaporHelper } = context
const tag = genTag()
const isRoot = oper.root
const rawProps = genRawProps()
const rawProps = genRawProps(oper.props, context)
return [
NEWLINE,
@ -49,63 +56,80 @@ export function genCreateComponent(
)
}
}
}
function genRawProps() {
const props = oper.props
.map(props => {
if (isArray(props)) {
if (!props.length) return
return genStaticProps(props)
} else {
let expr = genExpression(props.value, context)
if (props.handler) expr = genCall(helper('toHandlers'), expr)
return ['() => (', ...expr, ')']
export function genRawProps(props: IRProps[], context: CodegenContext) {
const frag = props
.map(props => {
if (isArray(props)) {
if (!props.length) return
return genStaticProps(props, context)
} else {
let expr: CodeFragment[]
if (props.kind === IRDynamicPropsKind.ATTRIBUTE)
expr = genMulti(SEGMENTS_OBJECT, genProp(props, context))
else {
expr = genExpression(props.value, context)
if (props.handler) expr = genCall(context.helper('toHandlers'), expr)
}
})
.filter(Boolean)
if (props.length) {
return genMulti(SEGMENTS_ARRAY, ...props)
}
}
function genStaticProps(props: IRProp[]) {
return genMulti(
SEGMENTS_OBJECT_NEWLINE,
...props.map(prop => {
return [
...genPropKey(prop, context),
': ',
...(prop.handler
? genEventHandler(context, prop.values[0])
: ['() => (', ...genExpression(prop.values[0], context), ')']),
...(prop.model
? [...genModelEvent(prop), ...genModelModifiers(prop)]
: []),
]
}),
return ['() => (', ...expr, ')']
}
})
.filter(
Boolean as any as (v: CodeFragment[] | undefined) => v is CodeFragment[],
)
function genModelEvent(prop: IRProp): CodeFragment[] {
const name = prop.key.isStatic
? [JSON.stringify(`onUpdate:${camelize(prop.key.content)}`)]
: ['["onUpdate:" + ', ...genExpression(prop.key, context), ']']
const handler = genModelHandler(prop.values[0], context)
return [',', NEWLINE, ...name, ': ', ...handler]
}
function genModelModifiers(prop: IRProp): CodeFragment[] {
const { key, modelModifiers } = prop
if (!modelModifiers || !modelModifiers.length) return []
const modifiersKey = key.isStatic
? key.content === 'modelValue'
? [`modelModifiers`]
: [`${key.content}Modifiers`]
: ['[', ...genExpression(key, context), ' + "Modifiers"]']
const modifiersVal = genDirectiveModifiers(modelModifiers)
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
}
if (frag.length) {
return genMulti(SEGMENTS_ARRAY_NEWLINE, ...frag)
}
}
function genStaticProps(
props: IRPropsStatic,
context: CodegenContext,
): CodeFragment[] {
return genMulti(
props.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT,
...props.map(prop => genProp(prop, context, true)),
)
}
function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) {
return [
...genPropKey(prop, context),
': ',
...(prop.handler
? genEventHandler(context, prop.values[0])
: isStatic
? ['() => (', ...genExpression(prop.values[0], context), ')']
: genExpression(prop.values[0], context)),
...(prop.model
? [...genModelEvent(prop, context), ...genModelModifiers(prop, context)]
: []),
]
}
function genModelEvent(prop: IRProp, context: CodegenContext): CodeFragment[] {
const name = prop.key.isStatic
? [JSON.stringify(`onUpdate:${camelize(prop.key.content)}`)]
: ['["onUpdate:" + ', ...genExpression(prop.key, context), ']']
const handler = genModelHandler(prop.values[0], context)
return [',', NEWLINE, ...name, ': ', ...handler]
}
function genModelModifiers(
prop: IRProp,
context: CodegenContext,
): CodeFragment[] {
const { key, modelModifiers } = prop
if (!modelModifiers || !modelModifiers.length) return []
const modifiersKey = key.isStatic
? key.content === 'modelValue'
? [`modelModifiers`]
: [`${key.content}Modifiers`]
: ['[', ...genExpression(key, context), ' + "Modifiers"]']
const modifiersVal = genDirectiveModifiers(modelModifiers)
return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`]
}

View File

@ -4,11 +4,12 @@ import {
isSimpleIdentifier,
} from '@vue/compiler-core'
import type { CodegenContext } from '../generate'
import type {
IRProp,
SetDynamicPropsIRNode,
SetPropIRNode,
VaporHelper,
import {
IRDynamicPropsKind,
type IRProp,
type SetDynamicPropsIRNode,
type SetPropIRNode,
type VaporHelper,
} from '../ir'
import { genExpression } from './expression'
import {
@ -73,7 +74,9 @@ export function genDynamicProps(
props =>
Array.isArray(props)
? genLiteralObjectProps(props, context) // static and dynamic arg props
: genExpression(props.value, context), // v-bind=""
: props.kind === IRDynamicPropsKind.ATTRIBUTE
? genLiteralObjectProps([props], context) // dynamic arg props
: genExpression(props.value, context), // v-bind=""
),
),
]

View File

@ -57,6 +57,11 @@ export function genMulti(
}
}
export const SEGMENTS_ARRAY: Segments = ['[', ']', ', ']
export const SEGMENTS_ARRAY_NEWLINE: Segments = [
['[', INDENT_START, NEWLINE],
[INDENT_END, NEWLINE, ']'],
[', ', NEWLINE],
]
export const SEGMENTS_OBJECT: Segments = ['{ ', ' }', ', ']
export const SEGMENTS_OBJECT_NEWLINE: Segments = [
['{', INDENT_START, NEWLINE],

View File

@ -83,12 +83,25 @@ export interface ForIRNode extends BaseIRNode {
export interface IRProp extends Omit<DirectiveTransformResult, 'value'> {
values: SimpleExpressionNode[]
}
export enum IRDynamicPropsKind {
EXPRESSION, // v-bind="value"
ATTRIBUTE, // v-bind:[foo]="value"
}
export type IRPropsStatic = IRProp[]
export interface IRPropsDynamicExpression {
kind: IRDynamicPropsKind.EXPRESSION
value: SimpleExpressionNode
handler?: boolean
}
export interface IRPropsDynamicAttribute extends IRProp {
kind: IRDynamicPropsKind.ATTRIBUTE
}
export type IRProps =
| IRProp[]
| {
value: SimpleExpressionNode
handler?: boolean
}
| IRPropsStatic
| IRPropsDynamicAttribute
| IRPropsDynamicExpression
export interface SetPropIRNode extends BaseIRNode {
type: IRNodeTypes.SET_PROP

View File

@ -24,9 +24,11 @@ import type {
} from '../transform'
import {
DynamicFlag,
IRDynamicPropsKind,
IRNodeTypes,
type IRProp,
type IRProps,
type IRPropsDynamicAttribute,
type VaporDirectiveNode,
} from '../ir'
import { EMPTY_EXPRESSION } from './utils'
@ -205,7 +207,10 @@ function buildProps(
if (prop.exp) {
dynamicExpr.push(prop.exp)
pushMergeArg()
dynamicArgs.push({ value: prop.exp })
dynamicArgs.push({
kind: IRDynamicPropsKind.EXPRESSION,
value: prop.exp,
})
} else {
context.options.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc),
@ -218,7 +223,11 @@ function buildProps(
if (isComponent) {
dynamicExpr.push(prop.exp)
pushMergeArg()
dynamicArgs.push({ value: prop.exp, handler: true })
dynamicArgs.push({
kind: IRDynamicPropsKind.EXPRESSION,
value: prop.exp,
handler: true,
})
} else {
context.registerEffect(
[prop.exp],
@ -241,8 +250,19 @@ function buildProps(
const result = transformProp(prop, node, context)
if (result) {
results.push(result)
dynamicExpr.push(result.key, result.value)
if (isComponent && !result.key.isStatic) {
// v-bind:[name]="value" or v-on:[name]="value"
pushMergeArg()
dynamicArgs.push(
extend(resolveDirectiveResult(result), {
kind: IRDynamicPropsKind.ATTRIBUTE,
}) as IRPropsDynamicAttribute,
)
} else {
// other static props
results.push(result)
}
}
}
@ -297,7 +317,7 @@ function dedupeProperties(results: DirectiveTransformResult[]): IRProp[] {
const deduped: IRProp[] = []
for (const result of results) {
const prop = normalizeIRProp(result)
const prop = resolveDirectiveResult(result)
// dynamic keys are always allowed
if (!prop.key.isStatic) {
deduped.push(prop)
@ -307,7 +327,7 @@ function dedupeProperties(results: DirectiveTransformResult[]): IRProp[] {
const existing = knownProps.get(name)
if (existing) {
if (name === 'style' || name === 'class') {
mergeAsArray(existing, prop)
mergePropValues(existing, prop)
}
// unexpected duplicate, should have emitted error during parse
} else {
@ -318,11 +338,14 @@ function dedupeProperties(results: DirectiveTransformResult[]): IRProp[] {
return deduped
}
function normalizeIRProp(prop: DirectiveTransformResult): IRProp {
return extend({}, prop, { value: undefined, values: [prop.value] })
function resolveDirectiveResult(prop: DirectiveTransformResult): IRProp {
return extend({}, prop, {
value: undefined,
values: [prop.value],
})
}
function mergeAsArray(existing: IRProp, incoming: IRProp) {
function mergePropValues(existing: IRProp, incoming: IRProp) {
const newValues = incoming.values
existing.values.push(...newValues)
}