feat(compiler-vapor): support v-on for component

This commit is contained in:
三咲智子 Kevin Deng 2024-04-15 04:39:15 +08:00
parent 3787a430f6
commit 9e0cd20da0
No known key found for this signature in database
8 changed files with 107 additions and 163 deletions

View File

@ -36,17 +36,6 @@ export function render(_ctx) {
}"
`;
exports[`compiler: element transform > component > props merging: event handlers 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const n0 = _createComponent(_resolveComponent("Foo"), [{
onClick: () => (_ctx.a)
}], true)
return n0
}"
`;
exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
"(() => {
const n0 = _createComponent(Example, null, true)
@ -158,6 +147,16 @@ export function render(_ctx) {
}"
`;
exports[`compiler: element transform > component > v-on="obj" 1`] = `
"import { toHandlers as _toHandlers } from 'vue';
import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
export function render(_ctx) {
const n0 = _createComponent(_resolveComponent("Foo"), [() => (_toHandlers(_ctx.obj))], true)
return n0
}"
`;
exports[`compiler: element transform > props + children 1`] = `
"import { template as _template } from 'vue/vapor';
const t0 = _template("<div id=\\"foo\\"><span></span></div>")

View File

@ -252,7 +252,7 @@ describe('compiler: element transform', () => {
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [{ content: 'obj', isStatic: false }],
props: [{ value: { content: 'obj', isStatic: false } }],
},
])
})
@ -270,7 +270,7 @@ describe('compiler: element transform', () => {
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{ content: 'obj' },
{ value: { content: 'obj' } },
],
},
])
@ -288,7 +288,7 @@ describe('compiler: element transform', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
{ content: 'obj' },
{ value: { content: 'obj' } },
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
},
@ -309,19 +309,19 @@ describe('compiler: element transform', () => {
tag: 'Foo',
props: [
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{ content: 'obj' },
{ value: { content: 'obj' } },
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
},
])
})
test('props merging: event handlers', () => {
test.todo('props merging: event handlers', () => {
const { code, ir } = compileWithElementTransform(
`<Foo @click.foo="a" @click.bar="b" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains('onClick: () => (_ctx.a)')
expect(code).contains('onClick: () => [_ctx.a, _ctx.b]')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
@ -330,7 +330,7 @@ describe('compiler: element transform', () => {
[
{
key: { content: 'onClick', isStatic: true },
values: [{ content: 'a' }],
values: [{ content: 'a' }, { content: 'b' }],
},
],
],
@ -352,9 +352,17 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
})
test.todo('v-on="obj"', () => {
const { code } = compileWithElementTransform(`<Foo v-on="obj" />`)
test('v-on="obj"', () => {
const { code, ir } = compileWithElementTransform(`<Foo v-on="obj" />`)
expect(code).toMatchSnapshot()
expect(code).contains('[() => (_toHandlers(_ctx.obj))]')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [{ value: { content: 'obj' }, handler: true }],
},
])
})
})
@ -400,9 +408,11 @@ describe('compiler: element transform', () => {
element: 0,
props: [
{
type: 4,
content: 'obj',
isStatic: false,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
},
],
},
@ -431,26 +441,13 @@ describe('compiler: element transform', () => {
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
[
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'id',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
],
},
],
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
value: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
},
],
},
@ -467,39 +464,14 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
],
expressions: [{ content: 'obj' }],
operations: [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
[
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'id',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
],
},
],
{ value: { content: 'obj' } },
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
],
},
],
@ -515,55 +487,15 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
expect(ir.block.effect).toMatchObject([
{
expressions: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
],
expressions: [{ content: 'obj' }],
operations: [
{
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: 0,
props: [
[
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'id',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'foo',
isStatic: true,
},
],
},
],
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'obj',
isStatic: false,
},
[
{
key: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'class',
isStatic: true,
},
values: [
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'bar',
isStatic: true,
},
],
},
],
[{ key: { content: 'id' }, values: [{ content: 'foo' }] }],
{ value: { content: 'obj' } },
[{ key: { content: 'class' }, values: [{ content: 'bar' }] }],
],
},
],

View File

@ -14,71 +14,68 @@ describe('compiler: vModel transform', () => {
const { code, vaporHelpers } = compileWithVModel(
'<input v-model="model" />',
)
expect(vaporHelpers).toContain('vModelText')
expect(code).toMatchSnapshot()
expect(vaporHelpers).toContain('vModelText')
})
test('should support input (text)', () => {
const { code, vaporHelpers } = compileWithVModel(
'<input type="text" v-model="model" />',
)
expect(vaporHelpers).toContain('vModelText')
expect(code).toMatchSnapshot()
expect(vaporHelpers).toContain('vModelText')
})
test('should support input (radio)', () => {
const { code, vaporHelpers } = compileWithVModel(
'<input type="radio" v-model="model" />',
)
expect(vaporHelpers).toContain('vModelRadio')
expect(code).toMatchSnapshot()
expect(vaporHelpers).toContain('vModelRadio')
})
test('should support input (checkbox)', () => {
const { code, vaporHelpers } = compileWithVModel(
'<input type="checkbox" v-model="model" />',
)
expect(vaporHelpers).toContain('vModelCheckbox')
expect(code).toMatchSnapshot()
expect(vaporHelpers).toContain('vModelCheckbox')
})
test('should support select', () => {
const { code, vaporHelpers } = compileWithVModel(
'<select v-model="model" />',
)
expect(vaporHelpers).toContain('vModelSelect')
expect(code).toMatchSnapshot()
expect(vaporHelpers).toContain('vModelSelect')
})
test('should support textarea', () => {
const { code, vaporHelpers } = compileWithVModel(
'<textarea v-model="model" />',
)
expect(vaporHelpers).toContain('vModelText')
expect(code).toMatchSnapshot()
expect(vaporHelpers).toContain('vModelText')
})
test('should support input (dynamic type)', () => {
const { code, vaporHelpers } = compileWithVModel(
'<input :type="foo" v-model="model" />',
)
expect(vaporHelpers).toContain('vModelDynamic')
expect(code).toMatchSnapshot()
expect(vaporHelpers).toContain('vModelDynamic')
})
test('should support w/ dynamic v-bind', () => {
const root1 = compileWithVModel('<input v-bind="obj" v-model="model" />')
expect(root1.vaporHelpers).toContain('vModelDynamic')
expect(root1.code).toMatchSnapshot()
expect(root1.vaporHelpers).toContain('vModelDynamic')
const root2 = compileWithVModel(
'<input v-bind:[key]="val" v-model="model" />',
)
expect(root2.vaporHelpers).toContain('vModelDynamic')
expect(root2.code).toMatchSnapshot()
expect(root2.vaporHelpers).toContain('vModelDynamic')
})
describe('errors', () => {

View File

@ -18,7 +18,7 @@ export function genCreateComponent(
oper: CreateComponentIRNode,
context: CodegenContext,
): CodeFragment[] {
const { vaporHelper } = context
const { helper, vaporHelper } = context
const tag = genTag()
const isRoot = oper.root
@ -53,7 +53,9 @@ export function genCreateComponent(
if (!props.length) return undefined
return genStaticProps(props)
} else {
return ['() => (', ...genExpression(props, context), ')']
let expr = genExpression(props.value, context)
if (props.handler) expr = genCall(helper('toHandlers'), expr)
return ['() => (', ...expr, ')']
}
})
.filter(Boolean)

View File

@ -65,7 +65,7 @@ export function genDynamicProps(
props =>
Array.isArray(props)
? genLiteralObjectProps(props, context) // static and dynamic arg props
: genExpression(props, context), // v-bind="{}"
: genExpression(props.value, context), // v-bind=""
),
),
]

View File

@ -82,7 +82,12 @@ export interface ForIRNode extends BaseIRNode {
export interface IRProp extends Omit<DirectiveTransformResult, 'value'> {
values: SimpleExpressionNode[]
}
export type IRProps = IRProp[] | SimpleExpressionNode
export type IRProps =
| IRProp[]
| {
value: SimpleExpressionNode
handler?: boolean
}
export interface SetPropIRNode extends BaseIRNode {
type: IRNodeTypes.SET_PROP

View File

@ -53,6 +53,7 @@ export const transformElement: NodeTransform = (node, context) => {
const propsResult = buildProps(
node,
context as TransformContext<ElementNode>,
isComponent,
)
;(isComponent ? transformComponentElement : transformNativeElement)(
@ -168,6 +169,7 @@ export type PropsResult =
function buildProps(
node: ElementNode,
context: TransformContext<ElementNode>,
isComponent: boolean,
): PropsResult {
const props = node.props as (VaporDirectiveNode | AttributeNode)[]
if (props.length === 0) return [false, []]
@ -184,21 +186,45 @@ function buildProps(
}
for (const prop of props) {
if (
prop.type === NodeTypes.DIRECTIVE &&
prop.name === 'bind' &&
!prop.arg
) {
if (prop.exp) {
dynamicExpr.push(prop.exp)
pushMergeArg()
dynamicArgs.push(prop.exp)
} else {
context.options.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc),
)
if (prop.type === NodeTypes.DIRECTIVE && !prop.arg) {
if (prop.name === 'bind') {
// v-bind="obj"
if (prop.exp) {
dynamicExpr.push(prop.exp)
pushMergeArg()
dynamicArgs.push({ value: prop.exp })
} else {
context.options.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc),
)
}
continue
} else if (prop.name === 'on') {
// v-on="obj"
if (prop.exp) {
if (isComponent) {
dynamicExpr.push(prop.exp)
pushMergeArg()
dynamicArgs.push({ value: prop.exp, handler: true })
} else {
context.registerEffect(
[prop.exp],
[
{
type: IRNodeTypes.SET_DYNAMIC_EVENTS,
element: context.reference(),
event: prop.exp,
},
],
)
}
} else {
context.options.onError(
createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, prop.loc),
)
}
continue
}
continue
}
const result = transformProp(prop, node, context)

View File

@ -21,30 +21,13 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
let { arg, exp, loc, modifiers } = dir
const isComponent = node.tagType === ElementTypes.COMPONENT
if (!exp && (!modifiers.length || !arg)) {
if (!exp && !modifiers.length) {
context.options.onError(
createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc),
)
}
arg = resolveExpression(arg!)
if (!arg) {
// v-on="obj"
if (exp) {
context.registerEffect(
[exp],
[
{
type: IRNodeTypes.SET_DYNAMIC_EVENTS,
element: context.reference(),
event: exp,
},
],
)
}
return
}
arg = resolveExpression(arg)
const { keyModifiers, nonKeyModifiers, eventOptionModifiers } =
resolveModifiers(
arg.isStatic ? `on${arg.content}` : arg,