提交 f209ff01 编写于 作者: fxy060608's avatar fxy060608

wip(mp): vBind

上级 e5f28c4e
......@@ -33,10 +33,10 @@ function assert(
describe('compiler', () => {
test('should wrap as function if expression is inline statement', () => {
assert(
`{{hello}}`,
`{{a}}`,
`<div v-on:click.prevent />`,
`<view id="{{id}}"/>`,
`(_ctx, _cache) => {
return { a: _toDisplayString(_ctx.hello) }
return { a: _vOn(() => {}) }
}`,
{}
)
......
import { ElementNode, ErrorCodes } from '@vue/compiler-core'
import { compile } from '../src'
import { MPErrorCodes } from '../src/errors'
import { CompilerOptions } from '../src/options'
import { assert } from './testUtils'
function parseWithVBind(template: string, options: CompilerOptions = {}) {
const { ast, code } = compile(template, options)
return {
code,
node: ast.children[0] as ElementNode,
}
}
describe('compiler: transform v-bind', () => {
test('basic', () => {
assert(
`<view v-bind:id="id"/>`,
`<view id="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.id }
}`,
{}
)
})
test('dynamic arg', () => {
const onError = jest.fn()
parseWithVBind(`<view v-bind:[id]="id" />`, {
onError,
filename: 'foo.vue',
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view/>`)
return ''
},
},
})
expect(onError.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 23,
},
},
})
parseWithVBind(`<view v-bind:[foo.id]="id" />`, {
onError,
filename: 'foo.vue',
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view/>`)
return ''
},
},
})
expect(onError.mock.calls[1][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 27,
},
},
})
})
test('should error if no expression', () => {
const onError = jest.fn()
parseWithVBind(`<view v-bind />`, { onError })
expect(onError.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_NO_ARGUMENT,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 13,
},
},
})
parseWithVBind(`<view v-bind:arg />`, { onError })
expect(onError.mock.calls[1][0]).toMatchObject({
code: ErrorCodes.X_V_BIND_NO_EXPRESSION,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 17,
},
},
})
})
test('.camel modifier', () => {
assert(
`<view v-bind:foo-bar.camel="id"/>`,
'<view fooBar="{{a}}"/>',
`(_ctx, _cache) => {
return { a: _ctx.id }
}`
)
})
test('.camel modifier w/ dynamic arg', () => {
const onError = jest.fn()
parseWithVBind(`<view v-bind:[foo].camel="id"/>`, {
onError,
prefixIdentifiers: false,
filename: 'foo.vue',
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view/>`)
return ''
},
},
})
expect(onError.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 30,
},
},
})
})
test('.camel modifier w/ dynamic arg + prefixIdentifiers', () => {
const onError = jest.fn()
parseWithVBind(`<view v-bind:[foo].camel="id"/>`, {
onError,
prefixIdentifiers: true,
filename: 'foo.vue',
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view/>`)
return ''
},
},
})
expect(onError.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 30,
},
},
})
})
test('.prop modifier', () => {
const onWarn = jest.fn()
parseWithVBind(`<view v-bind:fooBar.prop="id"/>`, { onWarn })
expect(onWarn.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_MODIFIER_PROP,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 30,
},
},
})
})
test('.prop modifier w/ dynamic arg', () => {
const onError = jest.fn()
parseWithVBind(`<view v-bind:[fooBar].prop="id"/>`, {
onError,
filename: 'foo.vue',
prefixIdentifiers: false,
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view/>`)
return ''
},
},
})
expect(onError.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 32,
},
},
})
})
test('.prop modifier w/ dynamic arg + prefixIdentifiers', () => {
const onError = jest.fn()
parseWithVBind(`<view v-bind:[foo(bar)].prop="id"/>`, {
onError,
prefixIdentifiers: true,
filename: 'foo.vue',
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view/>`)
return ''
},
},
})
expect(onError.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 34,
},
},
})
})
test('.prop modifier (shorthand)', () => {
const onWarn = jest.fn()
parseWithVBind(`<view .fooBar="id"/>`, {
onWarn,
filename: 'foo.vue',
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view fooBar="{{a}}"/>`)
return ''
},
},
})
expect(onWarn.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_MODIFIER_PROP,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 19,
},
},
})
})
test('.attr modifier', () => {
const onWarn = jest.fn()
parseWithVBind(`<view v-bind:foo-bar.attr="id"/>`, {
onWarn,
filename: 'foo.vue',
miniProgram: {
emitFile({ source }) {
expect(source).toBe(`<view foo-bar="{{a}}"/>`)
return ''
},
},
})
expect(onWarn.mock.calls[0][0]).toMatchObject({
code: MPErrorCodes.X_V_BIND_MODIFIER_ATTR,
loc: {
start: {
line: 1,
column: 7,
},
end: {
line: 1,
column: 31,
},
},
})
})
})
import { ElementNode } from '@vue/compiler-core'
import { compile } from '../src'
import { X_V_ON_DYNAMIC_EVENT } from '../src/errors'
import { MPErrorCodes } from '../src/errors'
import { CompilerOptions } from '../src/options'
function parseWithVOn(template: string, options: CompilerOptions = {}) {
......@@ -16,7 +16,7 @@ describe('compiler(mp): transform v-on', () => {
const onError = jest.fn()
parseWithVOn(`<div v-on:[event]="onClick" />`, { onError })
expect(onError.mock.calls[0][0]).toMatchObject({
code: X_V_ON_DYNAMIC_EVENT,
code: MPErrorCodes.X_V_ON_DYNAMIC_EVENT,
loc: {
start: {
line: 1,
......
......@@ -11,6 +11,7 @@ import { transformFor } from './transforms/vFor'
import { generate as genTemplate } from './template/codegen'
import { transformOn } from './transforms/vOn'
import { transformElement } from './transforms/transformElement'
import { transformBind } from './transforms/vBind'
export type TransformPreset = [
NodeTransform[],
......@@ -32,7 +33,7 @@ export function getBaseTransformPreset({
if (prefixIdentifiers) {
nodeTransforms.push(transformExpression)
}
return [nodeTransforms, { on: transformOn }]
return [nodeTransforms, { on: transformOn, bind: transformBind }]
}
export function baseCompile(template: string, options: CompilerOptions = {}) {
......
export const X_V_ON_DYNAMIC_EVENT = 0
export const enum MPErrorCodes {
X_V_ON_NO_ARGUMENT,
X_V_ON_DYNAMIC_EVENT,
X_V_BIND_NO_ARGUMENT,
X_V_BIND_DYNAMIC_ARGUMENT,
X_V_BIND_MODIFIER_PROP,
X_V_BIND_MODIFIER_ATTR,
}
export const errorMessages: Record<number, string> = {
[X_V_ON_DYNAMIC_EVENT]: 'v-on:[event] is not supported.',
[MPErrorCodes.X_V_ON_NO_ARGUMENT]: 'v-on="" is not supported',
[MPErrorCodes.X_V_ON_DYNAMIC_EVENT]: 'v-on:[event]="" is not supported.',
[MPErrorCodes.X_V_BIND_NO_ARGUMENT]: 'v-bind="" is not supported.',
[MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT]:
'v-bind:[name]="" is not supported.',
[MPErrorCodes.X_V_BIND_MODIFIER_PROP]: 'v-bind .prop is not supported',
[MPErrorCodes.X_V_BIND_MODIFIER_ATTR]: 'v-bind .attr is not supported',
}
......@@ -9,6 +9,7 @@ import {
Property,
ExpressionNode,
} from '@vue/compiler-core'
import { errorMessages, MPErrorCodes } from '../errors'
import { NodeTransform, TransformContext } from '../transform'
......@@ -46,7 +47,7 @@ function processProps(node: ElementNode, context: TransformContext) {
const prop = props[i]
if (prop.type === NodeTypes.DIRECTIVE) {
// directives
const { name, arg, exp, loc } = prop
const { name, arg, loc } = prop
const isVBind = name === 'bind'
const isVOn = name === 'on'
// skip v-slot - it is handled by its dedicated transform.
......@@ -70,25 +71,37 @@ function processProps(node: ElementNode, context: TransformContext) {
continue
}
// special case for v-bind and v-on with no argument
if (!arg && (isVBind || isVOn)) {
if (exp) {
if (isVOn) {
context.onError(
createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc)
if (isVBind || isVOn) {
// v-on=""
// v-bind=""
if (!arg) {
context.onError(
createCompilerError(
isVBind
? MPErrorCodes.X_V_BIND_NO_ARGUMENT
: MPErrorCodes.X_V_ON_NO_ARGUMENT,
loc,
errorMessages
)
}
} else {
)
continue
}
// v-on:[a]=""
// v-on:[a.b]=""
// v-bind:[a]=""
// v-bind:[a.b]=""
if (!(arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic)) {
context.onError(
createCompilerError(
isVBind
? ErrorCodes.X_V_BIND_NO_EXPRESSION
: ErrorCodes.X_V_ON_NO_EXPRESSION,
loc
? MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT
: MPErrorCodes.X_V_ON_DYNAMIC_EVENT,
loc,
errorMessages
)
)
continue
}
continue
}
const directiveTransform = context.directiveTransforms[name]
......
......@@ -37,11 +37,16 @@ export const transformIdentifier: NodeTransform = (node, context) => {
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
if (dir.type === NodeTypes.DIRECTIVE) {
const exp = dir.exp
const arg = dir.arg
if (arg) {
dir.arg = rewriteExpression(arg, context)
// TODO 指令暂不不支持动态参数,v-bind:[arg] v-on:[event]
if (!(arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic)) {
node.props.splice(i, 1)
i--
continue
}
}
const exp = dir.exp
if (exp) {
if (
vFor &&
......
import { camelize } from '@vue/shared'
import {
NodeTypes,
CAMELIZE,
createCompilerError,
ErrorCodes,
createObjectProperty,
createSimpleExpression,
} from '@vue/compiler-core'
import { DirectiveTransform } from '../transform'
import { errorMessages, MPErrorCodes } from '../errors'
export const transformBind: DirectiveTransform = (dir, _node, context) => {
const { exp, modifiers, loc } = dir
const arg = dir.arg!
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
arg.children.unshift(`(`)
arg.children.push(`) || ""`)
} else if (!arg.isStatic) {
arg.content = `${arg.content} || ""`
}
// .sync is replaced by v-model:arg
if (modifiers.includes('camel')) {
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
arg.content = camelize(arg.content)
} else {
arg.content = `${context.helperString(CAMELIZE)}(${arg.content})`
}
} else {
// arg.children.unshift(`${context.helperString(CAMELIZE)}(`)
// arg.children.push(`)`)
}
}
if (modifiers.includes('prop')) {
context.onWarn(
createCompilerError(
MPErrorCodes.X_V_BIND_MODIFIER_PROP,
loc,
errorMessages
)
)
}
if (modifiers.includes('attr')) {
context.onWarn(
createCompilerError(
MPErrorCodes.X_V_BIND_MODIFIER_ATTR,
loc,
errorMessages
)
)
}
if (
!exp ||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
) {
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
return {
props: [createObjectProperty(arg, createSimpleExpression('', true, loc))],
}
}
return {
props: [createObjectProperty(arg, exp)],
}
}
......@@ -14,8 +14,7 @@ import {
TO_HANDLER_KEY,
} from '@vue/compiler-core'
import { camelize, toHandlerKey } from '@vue/shared'
import { V_ON } from '..'
import { errorMessages, X_V_ON_DYNAMIC_EVENT } from '../errors'
import { V_ON } from '../runtimeHelpers'
import { DirectiveTransform, TransformContext } from '../transform'
import { DirectiveTransformResult } from './transformElement'
import { processExpression } from './transformExpression'
......@@ -54,10 +53,6 @@ export const transformOn: DirectiveTransform = (
arg.loc
)
} else {
// TODO 不支持动态事件
context.onError(
createCompilerError(X_V_ON_DYNAMIC_EVENT, loc, errorMessages)
)
// #2388
eventName = createCompoundExpression([
// `${context.helperString(TO_HANDLER_KEY)}(`,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册