mirror of https://github.com/vuejs/core.git
feat(compiler-vapor): v-model for component (#180)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
37df043adc
commit
1f28ae15cd
|
@ -1,5 +1,86 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`compiler: vModel transform > component > v-model for component should generate modelModifiers 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_resolveComponent("Comp"), [{
|
||||
modelValue: () => (_ctx.foo),
|
||||
"onUpdate:modelValue": () => $event => (_ctx.foo = $event),
|
||||
modelModifiers: () => ({ trim: true, "bar-baz": true })
|
||||
}], true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: vModel transform > component > v-model for component should work 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_resolveComponent("Comp"), [{
|
||||
modelValue: () => (_ctx.foo),
|
||||
"onUpdate:modelValue": () => $event => (_ctx.foo = $event)
|
||||
}], true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: vModel transform > component > v-model with arguments for component should generate modelModifiers 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_resolveComponent("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
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: vModel transform > component > v-model with arguments for component should work 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_resolveComponent("Comp"), [{
|
||||
bar: () => (_ctx.foo),
|
||||
"onUpdate:bar": () => $event => (_ctx.foo = $event)
|
||||
}], true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: vModel transform > component > v-model with dynamic arguments for component should generate modelModifiers 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_resolveComponent("Comp"), [{
|
||||
[_ctx.foo]: () => (_ctx.foo),
|
||||
["onUpdate:" + _ctx.foo]: () => $event => (_ctx.foo = $event),
|
||||
[_ctx.foo + "Modifiers"]: () => ({ trim: true }),
|
||||
[_ctx.bar]: () => (_ctx.bar),
|
||||
["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event),
|
||||
[_ctx.bar + "Modifiers"]: () => ({ number: true })
|
||||
}], true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: vModel transform > component > v-model with dynamic arguments for component should work 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const n0 = _createComponent(_resolveComponent("Comp"), [{
|
||||
[_ctx.arg]: () => (_ctx.foo),
|
||||
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event)
|
||||
}], true)
|
||||
return n0
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: vModel transform > modifiers > .lazy 1`] = `
|
||||
"import { vModelText as _vModelText, withDirectives as _withDirectives, delegate as _delegate, template as _template } from 'vue/vapor';
|
||||
const t0 = _template("<input>")
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { makeCompile } from './_utils'
|
||||
import { transformChildren, transformElement, transformVModel } from '../../src'
|
||||
import {
|
||||
IRNodeTypes,
|
||||
transformChildren,
|
||||
transformElement,
|
||||
transformVModel,
|
||||
} from '../../src'
|
||||
import { BindingTypes, DOMErrorCodes } from '@vue/compiler-dom'
|
||||
|
||||
const compileWithVModel = makeCompile({
|
||||
|
@ -198,4 +203,169 @@ describe('compiler: vModel transform', () => {
|
|||
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('component', () => {
|
||||
test('v-model for component should work', () => {
|
||||
const { code, ir } = compileWithVModel('<Comp v-model="foo" />')
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(code).contains(
|
||||
`modelValue: () => (_ctx.foo),
|
||||
"onUpdate:modelValue": () => $event => (_ctx.foo = $event)`,
|
||||
)
|
||||
expect(ir.block.operation).toMatchObject([
|
||||
{
|
||||
type: IRNodeTypes.CREATE_COMPONENT_NODE,
|
||||
tag: 'Comp',
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: { content: 'modelValue', isStatic: true },
|
||||
model: true,
|
||||
modelModifiers: [],
|
||||
values: [{ content: 'foo', isStatic: false }],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('v-model with arguments for component should work', () => {
|
||||
const { code, ir } = compileWithVModel('<Comp v-model:bar="foo" />')
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(code).contains(
|
||||
`bar: () => (_ctx.foo),
|
||||
"onUpdate:bar": () => $event => (_ctx.foo = $event)`,
|
||||
)
|
||||
expect(ir.block.operation).toMatchObject([
|
||||
{
|
||||
type: IRNodeTypes.CREATE_COMPONENT_NODE,
|
||||
tag: 'Comp',
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: { content: 'bar', isStatic: true },
|
||||
model: true,
|
||||
modelModifiers: [],
|
||||
values: [{ content: 'foo', isStatic: false }],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('v-model with dynamic arguments for component should work', () => {
|
||||
const { code, ir } = compileWithVModel('<Comp v-model:[arg]="foo" />')
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(code).contains(
|
||||
`[_ctx.arg]: () => (_ctx.foo),
|
||||
["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event)`,
|
||||
)
|
||||
expect(ir.block.operation).toMatchObject([
|
||||
{
|
||||
type: IRNodeTypes.CREATE_COMPONENT_NODE,
|
||||
tag: 'Comp',
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: { content: 'arg', isStatic: false },
|
||||
values: [{ content: 'foo', isStatic: false }],
|
||||
model: true,
|
||||
modelModifiers: [],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('v-model for component should generate modelModifiers', () => {
|
||||
const { code, ir } = compileWithVModel(
|
||||
'<Comp v-model.trim.bar-baz="foo" />',
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(code).contain(
|
||||
`modelModifiers: () => ({ trim: true, "bar-baz": true })`,
|
||||
)
|
||||
expect(ir.block.operation).toMatchObject([
|
||||
{
|
||||
type: IRNodeTypes.CREATE_COMPONENT_NODE,
|
||||
tag: 'Comp',
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: { content: 'modelValue', isStatic: true },
|
||||
values: [{ content: 'foo', isStatic: false }],
|
||||
model: true,
|
||||
modelModifiers: ['trim', 'bar-baz'],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('v-model with arguments for component should generate modelModifiers', () => {
|
||||
const { code, ir } = compileWithVModel(
|
||||
'<Comp v-model:foo.trim="foo" v-model:bar.number="bar" />',
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(code).contain(`fooModifiers: () => ({ trim: true })`)
|
||||
expect(code).contain(`barModifiers: () => ({ number: true })`)
|
||||
expect(ir.block.operation).toMatchObject([
|
||||
{
|
||||
type: IRNodeTypes.CREATE_COMPONENT_NODE,
|
||||
tag: 'Comp',
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: { content: 'foo', isStatic: true },
|
||||
values: [{ content: 'foo', isStatic: false }],
|
||||
model: true,
|
||||
modelModifiers: ['trim'],
|
||||
},
|
||||
{
|
||||
key: { content: 'bar', isStatic: true },
|
||||
values: [{ content: 'bar', isStatic: false }],
|
||||
model: true,
|
||||
modelModifiers: ['number'],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('v-model with dynamic arguments for component should generate modelModifiers ', () => {
|
||||
const { code, ir } = compileWithVModel(
|
||||
'<Comp v-model:[foo].trim="foo" v-model:[bar].number="bar" />',
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(code).contain(`[_ctx.foo + "Modifiers"]: () => ({ trim: true })`)
|
||||
expect(code).contain(`[_ctx.bar + "Modifiers"]: () => ({ number: true })`)
|
||||
expect(ir.block.operation).toMatchObject([
|
||||
{
|
||||
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'],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { extend, isArray } from '@vue/shared'
|
||||
import { camelize, extend, isArray } from '@vue/shared'
|
||||
import type { CodegenContext } from '../generate'
|
||||
import type { CreateComponentIRNode, IRProp } from '../ir'
|
||||
import {
|
||||
|
@ -13,6 +13,8 @@ import { genExpression } from './expression'
|
|||
import { genPropKey } from './prop'
|
||||
import { createSimpleExpression } from '@vue/compiler-dom'
|
||||
import { genEventHandler } from './event'
|
||||
import { genDirectiveModifiers } from './directive'
|
||||
import { genModelHandler } from './modelValue'
|
||||
|
||||
// TODO: generate component slots
|
||||
export function genCreateComponent(
|
||||
|
@ -23,7 +25,7 @@ export function genCreateComponent(
|
|||
|
||||
const tag = genTag()
|
||||
const isRoot = oper.root
|
||||
const props = genProps()
|
||||
const rawProps = genRawProps()
|
||||
|
||||
return [
|
||||
NEWLINE,
|
||||
|
@ -31,7 +33,7 @@ export function genCreateComponent(
|
|||
...genCall(
|
||||
vaporHelper('createComponent'),
|
||||
tag,
|
||||
props || (isRoot ? 'null' : false),
|
||||
rawProps || (isRoot ? 'null' : false),
|
||||
isRoot && 'true',
|
||||
),
|
||||
]
|
||||
|
@ -47,11 +49,11 @@ export function genCreateComponent(
|
|||
}
|
||||
}
|
||||
|
||||
function genProps() {
|
||||
function genRawProps() {
|
||||
const props = oper.props
|
||||
.map(props => {
|
||||
if (isArray(props)) {
|
||||
if (!props.length) return undefined
|
||||
if (!props.length) return
|
||||
return genStaticProps(props)
|
||||
} else {
|
||||
let expr = genExpression(props.value, context)
|
||||
|
@ -79,8 +81,34 @@ export function genCreateComponent(
|
|||
...(prop.handler
|
||||
? genEventHandler(context, prop.values[0])
|
||||
: ['() => (', ...genExpression(prop.values[0], context), ')']),
|
||||
...(prop.model
|
||||
? [...genModelEvent(prop), ...genModelModifiers(prop)]
|
||||
: []),
|
||||
]
|
||||
}),
|
||||
)
|
||||
|
||||
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} })`]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ export function genWithDirective(
|
|||
? NULL
|
||||
: false
|
||||
const modifiers = dir.modifiers.length
|
||||
? ['{ ', genDirectiveModifiers(), ' }']
|
||||
? ['{ ', genDirectiveModifiers(dir.modifiers), ' }']
|
||||
: false
|
||||
|
||||
return genMulti(['[', ']', ', '], directive, value, argument, modifiers)
|
||||
|
@ -61,14 +61,14 @@ export function genWithDirective(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function genDirectiveModifiers() {
|
||||
return dir.modifiers
|
||||
export function genDirectiveModifiers(modifiers: string[]) {
|
||||
return modifiers
|
||||
.map(
|
||||
value =>
|
||||
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
|
||||
)
|
||||
.join(', ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,28 +3,36 @@ import { genExpression } from './expression'
|
|||
import type { SetModelValueIRNode } from '../ir'
|
||||
import type { CodegenContext } from '../generate'
|
||||
import { type CodeFragment, NEWLINE, genCall } from './utils'
|
||||
import type { SimpleExpressionNode } from '@vue/compiler-dom'
|
||||
|
||||
export function genSetModelValue(
|
||||
oper: SetModelValueIRNode,
|
||||
context: CodegenContext,
|
||||
): CodeFragment[] {
|
||||
const {
|
||||
vaporHelper,
|
||||
|
||||
options: { isTS },
|
||||
} = context
|
||||
|
||||
const { vaporHelper } = context
|
||||
const name = oper.key.isStatic
|
||||
? [JSON.stringify(`update:${camelize(oper.key.content)}`)]
|
||||
: ['`update:${', ...genExpression(oper.key, context), '}`']
|
||||
const handler = [
|
||||
`() => ${isTS ? `($event: any)` : `$event`} => (`,
|
||||
...genExpression(oper.value, context, '$event'),
|
||||
')',
|
||||
]
|
||||
|
||||
const handler = genModelHandler(oper.value, context)
|
||||
|
||||
return [
|
||||
NEWLINE,
|
||||
...genCall(vaporHelper('delegate'), `n${oper.element}`, name, handler),
|
||||
]
|
||||
}
|
||||
|
||||
export function genModelHandler(
|
||||
value: SimpleExpressionNode,
|
||||
context: CodegenContext,
|
||||
) {
|
||||
const {
|
||||
options: { isTS },
|
||||
} = context
|
||||
|
||||
return [
|
||||
`() => ${isTS ? `($event: any)` : `$event`} => (`,
|
||||
...genExpression(value, context, '$event'),
|
||||
')',
|
||||
]
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ export interface DirectiveTransformResult {
|
|||
modifier?: '.' | '^'
|
||||
runtimeCamelize?: boolean
|
||||
handler?: boolean
|
||||
model?: boolean
|
||||
modelModifiers?: string[]
|
||||
}
|
||||
|
||||
// A structural directive transform is technically also a NodeTransform;
|
||||
|
|
|
@ -65,6 +65,12 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
|
|||
let runtimeDirective: VaporHelper | undefined
|
||||
|
||||
if (isComponent) {
|
||||
return {
|
||||
key: arg ? arg : createSimpleExpression('modelValue', true),
|
||||
value: exp,
|
||||
model: true,
|
||||
modelModifiers: dir.modifiers,
|
||||
}
|
||||
} else {
|
||||
if (dir.arg)
|
||||
context.options.onError(
|
||||
|
|
Loading…
Reference in New Issue