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
|
// 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`] = `
|
exports[`compiler: vModel transform > modifiers > .lazy 1`] = `
|
||||||
"import { vModelText as _vModelText, withDirectives as _withDirectives, delegate as _delegate, template as _template } from 'vue/vapor';
|
"import { vModelText as _vModelText, withDirectives as _withDirectives, delegate as _delegate, template as _template } from 'vue/vapor';
|
||||||
const t0 = _template("<input>")
|
const t0 = _template("<input>")
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { makeCompile } from './_utils'
|
import { makeCompile } from './_utils'
|
||||||
import { transformChildren, transformElement, transformVModel } from '../../src'
|
import {
|
||||||
|
IRNodeTypes,
|
||||||
|
transformChildren,
|
||||||
|
transformElement,
|
||||||
|
transformVModel,
|
||||||
|
} from '../../src'
|
||||||
import { BindingTypes, DOMErrorCodes } from '@vue/compiler-dom'
|
import { BindingTypes, DOMErrorCodes } from '@vue/compiler-dom'
|
||||||
|
|
||||||
const compileWithVModel = makeCompile({
|
const compileWithVModel = makeCompile({
|
||||||
|
@ -198,4 +203,169 @@ describe('compiler: vModel transform', () => {
|
||||||
|
|
||||||
expect(code).toMatchSnapshot()
|
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 { CodegenContext } from '../generate'
|
||||||
import type { CreateComponentIRNode, IRProp } from '../ir'
|
import type { CreateComponentIRNode, IRProp } from '../ir'
|
||||||
import {
|
import {
|
||||||
|
@ -13,6 +13,8 @@ import { genExpression } from './expression'
|
||||||
import { genPropKey } from './prop'
|
import { genPropKey } from './prop'
|
||||||
import { createSimpleExpression } from '@vue/compiler-dom'
|
import { createSimpleExpression } from '@vue/compiler-dom'
|
||||||
import { genEventHandler } from './event'
|
import { genEventHandler } from './event'
|
||||||
|
import { genDirectiveModifiers } from './directive'
|
||||||
|
import { genModelHandler } from './modelValue'
|
||||||
|
|
||||||
// TODO: generate component slots
|
// TODO: generate component slots
|
||||||
export function genCreateComponent(
|
export function genCreateComponent(
|
||||||
|
@ -23,7 +25,7 @@ export function genCreateComponent(
|
||||||
|
|
||||||
const tag = genTag()
|
const tag = genTag()
|
||||||
const isRoot = oper.root
|
const isRoot = oper.root
|
||||||
const props = genProps()
|
const rawProps = genRawProps()
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
|
@ -31,7 +33,7 @@ export function genCreateComponent(
|
||||||
...genCall(
|
...genCall(
|
||||||
vaporHelper('createComponent'),
|
vaporHelper('createComponent'),
|
||||||
tag,
|
tag,
|
||||||
props || (isRoot ? 'null' : false),
|
rawProps || (isRoot ? 'null' : false),
|
||||||
isRoot && 'true',
|
isRoot && 'true',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -47,11 +49,11 @@ export function genCreateComponent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function genProps() {
|
function genRawProps() {
|
||||||
const props = oper.props
|
const props = oper.props
|
||||||
.map(props => {
|
.map(props => {
|
||||||
if (isArray(props)) {
|
if (isArray(props)) {
|
||||||
if (!props.length) return undefined
|
if (!props.length) return
|
||||||
return genStaticProps(props)
|
return genStaticProps(props)
|
||||||
} else {
|
} else {
|
||||||
let expr = genExpression(props.value, context)
|
let expr = genExpression(props.value, context)
|
||||||
|
@ -79,8 +81,34 @@ export function genCreateComponent(
|
||||||
...(prop.handler
|
...(prop.handler
|
||||||
? genEventHandler(context, prop.values[0])
|
? genEventHandler(context, prop.values[0])
|
||||||
: ['() => (', ...genExpression(prop.values[0], context), ')']),
|
: ['() => (', ...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
|
? NULL
|
||||||
: false
|
: false
|
||||||
const modifiers = dir.modifiers.length
|
const modifiers = dir.modifiers.length
|
||||||
? ['{ ', genDirectiveModifiers(), ' }']
|
? ['{ ', genDirectiveModifiers(dir.modifiers), ' }']
|
||||||
: false
|
: false
|
||||||
|
|
||||||
return genMulti(['[', ']', ', '], directive, value, argument, modifiers)
|
return genMulti(['[', ']', ', '], directive, value, argument, modifiers)
|
||||||
|
@ -61,14 +61,14 @@ export function genWithDirective(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function genDirectiveModifiers() {
|
export function genDirectiveModifiers(modifiers: string[]) {
|
||||||
return dir.modifiers
|
return modifiers
|
||||||
.map(
|
.map(
|
||||||
value =>
|
value =>
|
||||||
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
|
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
|
||||||
)
|
)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,28 +3,36 @@ import { genExpression } from './expression'
|
||||||
import type { SetModelValueIRNode } from '../ir'
|
import type { SetModelValueIRNode } from '../ir'
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodegenContext } from '../generate'
|
||||||
import { type CodeFragment, NEWLINE, genCall } from './utils'
|
import { type CodeFragment, NEWLINE, genCall } from './utils'
|
||||||
|
import type { SimpleExpressionNode } from '@vue/compiler-dom'
|
||||||
|
|
||||||
export function genSetModelValue(
|
export function genSetModelValue(
|
||||||
oper: SetModelValueIRNode,
|
oper: SetModelValueIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const {
|
const { vaporHelper } = context
|
||||||
vaporHelper,
|
|
||||||
|
|
||||||
options: { isTS },
|
|
||||||
} = context
|
|
||||||
|
|
||||||
const name = oper.key.isStatic
|
const name = oper.key.isStatic
|
||||||
? [JSON.stringify(`update:${camelize(oper.key.content)}`)]
|
? [JSON.stringify(`update:${camelize(oper.key.content)}`)]
|
||||||
: ['`update:${', ...genExpression(oper.key, context), '}`']
|
: ['`update:${', ...genExpression(oper.key, context), '}`']
|
||||||
const handler = [
|
|
||||||
`() => ${isTS ? `($event: any)` : `$event`} => (`,
|
const handler = genModelHandler(oper.value, context)
|
||||||
...genExpression(oper.value, context, '$event'),
|
|
||||||
')',
|
|
||||||
]
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
...genCall(vaporHelper('delegate'), `n${oper.element}`, name, handler),
|
...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?: '.' | '^'
|
modifier?: '.' | '^'
|
||||||
runtimeCamelize?: boolean
|
runtimeCamelize?: boolean
|
||||||
handler?: boolean
|
handler?: boolean
|
||||||
|
model?: boolean
|
||||||
|
modelModifiers?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// A structural directive transform is technically also a NodeTransform;
|
// A structural directive transform is technically also a NodeTransform;
|
||||||
|
|
|
@ -65,6 +65,12 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
|
||||||
let runtimeDirective: VaporHelper | undefined
|
let runtimeDirective: VaporHelper | undefined
|
||||||
|
|
||||||
if (isComponent) {
|
if (isComponent) {
|
||||||
|
return {
|
||||||
|
key: arg ? arg : createSimpleExpression('modelValue', true),
|
||||||
|
value: exp,
|
||||||
|
model: true,
|
||||||
|
modelModifiers: dir.modifiers,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (dir.arg)
|
if (dir.arg)
|
||||||
context.options.onError(
|
context.options.onError(
|
||||||
|
|
Loading…
Reference in New Issue