feat(compiler-vapor): support v-on for component
This commit is contained in:
parent
3787a430f6
commit
9e0cd20da0
|
@ -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>")
|
||||
|
|
|
@ -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' }] }],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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=""
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue