diff --git a/packages/uni-cli-shared/src/mp/event.ts b/packages/uni-cli-shared/src/mp/event.ts
index 7eaf95a8d7c72be50b9e6697c0e50e0d8e5442a4..b66bdccb32148c6b1f3e3ba9c0eac82f644e2942 100644
--- a/packages/uni-cli-shared/src/mp/event.ts
+++ b/packages/uni-cli-shared/src/mp/event.ts
@@ -19,5 +19,15 @@ export function formatMiniProgramEvent(
return `capture-${eventType}:${eventName}`
}
// bind:foo-bar
- return eventType + (eventName.indexOf('-') > -1 ? ':' : '') + eventName
+ return eventType + (isSimpleExpr(eventName) ? '' : ':') + eventName
+}
+
+function isSimpleExpr(name: string) {
+ if (name.indexOf('-') > -1) {
+ return false
+ }
+ if (name.indexOf(':') > -1) {
+ return false
+ }
+ return true
}
diff --git a/packages/uni-mp-compiler/__tests__/vModel.spec.ts b/packages/uni-mp-compiler/__tests__/vModel.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..976fa217b40c792718a7b6d99de0dffbda5dc13a
--- /dev/null
+++ b/packages/uni-mp-compiler/__tests__/vModel.spec.ts
@@ -0,0 +1,34 @@
+import { assert } from './testUtils'
+
+describe('compiler: transform v-model', () => {
+ test(`component v-model`, () => {
+ assert(
+ ``,
+ ``,
+ `(_ctx, _cache) => {
+ return { a: _ctx.model, b: _vOn($event => _ctx.model = $event.detail.__args__[0]) }
+}`
+ )
+ })
+ test(`component v-model with cache`, () => {
+ assert(
+ ``,
+ ``,
+ `(_ctx, _cache) => {
+ return { a: _ctx.model, b: _vOn($event => _ctx.model = $event.detail.__args__[0]) }
+}`,
+ {
+ cacheHandlers: true,
+ }
+ )
+ })
+ // test(`input,textarea v-model`, () => {
+ // assert(
+ // ``,
+ // ``,
+ // `(_ctx, _cache) => {
+ // return { a: _ctx.model, b: _vOn(($event)=>_ctx.model = $event.detail.value) }
+ // }`
+ // )
+ // })
+})
diff --git a/packages/uni-mp-compiler/package.json b/packages/uni-mp-compiler/package.json
index b91850562af9713699bdf0e050ae7eec5db3c7d2..9b3180338ba986780d7c7d0e06ae273f512c9d57 100644
--- a/packages/uni-mp-compiler/package.json
+++ b/packages/uni-mp-compiler/package.json
@@ -23,6 +23,7 @@
"@babel/parser": "^7.15.0",
"@babel/types": "^7.15.0",
"@vue/compiler-core": "^3.2.20",
+ "@vue/compiler-dom": "^3.2.20",
"estree-walker": "^2.0.2"
}
}
diff --git a/packages/uni-mp-compiler/src/ast.ts b/packages/uni-mp-compiler/src/ast.ts
index fa8cc95958609130cadd36b0225f5def990a777e..d2ace24082a9f990d59111ff29a405120d925f31 100644
--- a/packages/uni-mp-compiler/src/ast.ts
+++ b/packages/uni-mp-compiler/src/ast.ts
@@ -199,20 +199,29 @@ export function parseStringLiteral(
return stringLiteral('')
}
-export function createBindDirectiveNode(
+function createDirectiveNode(
name: string,
- value: string
+ arg: string,
+ exp: string
): DirectiveNode {
return {
type: NodeTypes.DIRECTIVE,
- name: 'bind',
+ name,
modifiers: [],
loc: locStub,
- arg: createSimpleExpression(name, true),
- exp: createSimpleExpression(value, false),
+ arg: createSimpleExpression(arg, true),
+ exp: createSimpleExpression(exp, false),
}
}
+export function createOnDirectiveNode(name: string, value: string) {
+ return createDirectiveNode('on', name, value)
+}
+
+export function createBindDirectiveNode(name: string, value: string) {
+ return createDirectiveNode('bind', name, value)
+}
+
export function createAttributeNode(
name: string,
content: string
diff --git a/packages/uni-mp-compiler/src/errors.ts b/packages/uni-mp-compiler/src/errors.ts
index c237dcc005ce45ea064ea5633dd9b7b3ac31502c..cbe0884317ce1d566bfe9de6997a15563e3e17f3 100644
--- a/packages/uni-mp-compiler/src/errors.ts
+++ b/packages/uni-mp-compiler/src/errors.ts
@@ -1,3 +1,9 @@
+import {
+ CompilerError,
+ createCompilerError,
+ SourceLocation,
+} from '@vue/compiler-core'
+
export const enum MPErrorCodes {
X_V_ON_NO_ARGUMENT,
X_V_ON_DYNAMIC_EVENT,
@@ -10,7 +16,7 @@ export const enum MPErrorCodes {
X_DYNAMIC_COMPONENT_NOT_SUPPORTED,
}
-export const errorMessages: Record = {
+const MPErrorMessages: Record = {
[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.',
@@ -23,3 +29,20 @@ export const errorMessages: Record = {
[MPErrorCodes.X_NOT_SUPPORTED]: 'not supported: ',
[MPErrorCodes.X_V_IS_NOT_SUPPORTED]: 'v-is not supported',
}
+
+export interface MPCompilerError extends CompilerError {
+ code: MPErrorCodes
+}
+
+export function createMPCompilerError(
+ code: MPErrorCodes,
+ loc?: SourceLocation,
+ additionalMessage?: string
+) {
+ return createCompilerError(
+ code,
+ loc,
+ MPErrorMessages,
+ additionalMessage
+ ) as MPCompilerError
+}
diff --git a/packages/uni-mp-compiler/src/template/codegen.ts b/packages/uni-mp-compiler/src/template/codegen.ts
index c942e65b086a7e14610ec7b06264f762d2d68e9c..2b7800c7820fc25acb63808dc85782fa7075b595 100644
--- a/packages/uni-mp-compiler/src/template/codegen.ts
+++ b/packages/uni-mp-compiler/src/template/codegen.ts
@@ -303,8 +303,6 @@ function genDirectiveNode(
if (prop.arg) {
push(`slot="${(prop.arg as SimpleExpressionNode).content}"`)
}
- } else if (prop.name === 'model') {
- // TODO
} else if (prop.name === 'show') {
push(`hidden="{{!${(prop.exp as SimpleExpressionNode).content}}}"`)
} else if (prop.arg && prop.exp) {
diff --git a/packages/uni-mp-compiler/src/transforms/transformElement.ts b/packages/uni-mp-compiler/src/transforms/transformElement.ts
index 1fed011b6850de1b0a0e4169980af793584aeecc..be88d0c98e1d7334434a8e7c38d88e621c30b705 100644
--- a/packages/uni-mp-compiler/src/transforms/transformElement.ts
+++ b/packages/uni-mp-compiler/src/transforms/transformElement.ts
@@ -16,8 +16,9 @@ import {
findDir,
locStub,
AttributeNode,
+ DirectiveNode,
} from '@vue/compiler-core'
-import { errorMessages, MPErrorCodes } from '../errors'
+import { createMPCompilerError, MPErrorCodes } from '../errors'
import {
BindingComponentTypes,
@@ -25,6 +26,7 @@ import {
TransformContext,
} from '../transform'
import { createAttributeNode } from '../ast'
+import { transformModel } from './vModel'
export interface DirectiveTransformResult {
props: Property[]
@@ -101,20 +103,15 @@ function processComponent(node: ElementNode, context: TransformContext) {
// 1. dynamic component
if (isComponentTag(tag)) {
return context.onError(
- createCompilerError(
+ createMPCompilerError(
MPErrorCodes.X_DYNAMIC_COMPONENT_NOT_SUPPORTED,
- node.loc,
- errorMessages
+ node.loc
)
)
}
if (findDir(node, 'is')) {
return context.onError(
- createCompilerError(
- MPErrorCodes.X_V_IS_NOT_SUPPORTED,
- node.loc,
- errorMessages
- )
+ createMPCompilerError(MPErrorCodes.X_V_IS_NOT_SUPPORTED, node.loc)
)
}
// TODO not supported
@@ -125,12 +122,7 @@ function processComponent(node: ElementNode, context: TransformContext) {
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
if (builtIn) {
return context.onError(
- createCompilerError(
- MPErrorCodes.X_NOT_SUPPORTED,
- node.loc,
- errorMessages,
- tag
- )
+ createMPCompilerError(MPErrorCodes.X_NOT_SUPPORTED, node.loc, tag)
)
}
@@ -147,12 +139,7 @@ function processComponent(node: ElementNode, context: TransformContext) {
const dotIndex = tag.indexOf('.')
if (dotIndex > 0) {
return context.onError(
- createCompilerError(
- MPErrorCodes.X_NOT_SUPPORTED,
- node.loc,
- errorMessages,
- tag
- )
+ createMPCompilerError(MPErrorCodes.X_NOT_SUPPORTED, node.loc, tag)
)
}
@@ -253,12 +240,11 @@ function processProps(node: ElementNode, context: TransformContext) {
// v-bind=""
if (!arg) {
context.onError(
- createCompilerError(
+ createMPCompilerError(
isVBind
? MPErrorCodes.X_V_BIND_NO_ARGUMENT
: MPErrorCodes.X_V_ON_NO_ARGUMENT,
- loc,
- errorMessages
+ loc
)
)
continue
@@ -269,12 +255,11 @@ function processProps(node: ElementNode, context: TransformContext) {
// v-bind:[a.b]=""
if (!(arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic)) {
context.onError(
- createCompilerError(
+ createMPCompilerError(
isVBind
? MPErrorCodes.X_V_BIND_DYNAMIC_ARGUMENT
: MPErrorCodes.X_V_ON_DYNAMIC_EVENT,
- loc,
- errorMessages
+ loc
)
)
continue
@@ -293,11 +278,28 @@ function processProps(node: ElementNode, context: TransformContext) {
const directiveTransform = context.directiveTransforms[name]
if (directiveTransform) {
- prop.exp = directiveTransform(prop, node, context).props[0]
- .value as ExpressionNode
+ const { props } = directiveTransform(prop, node, context)
+ if (props.length) {
+ prop.exp = props[0].value as ExpressionNode
+ }
}
}
}
+ processVModel(node, context)
+}
+
+function processVModel(node: ElementNode, context: TransformContext) {
+ const { props } = node
+ const dirs: DirectiveNode[] = []
+ for (let i = 0; i < props.length; i++) {
+ const prop = props[i]
+ if (prop.type === NodeTypes.DIRECTIVE && prop.name === 'model') {
+ dirs.push(...transformModel(prop, node, context))
+ props.splice(i, 1)
+ i--
+ }
+ }
+ props.push(...dirs)
}
function isComponentTag(tag: string) {
diff --git a/packages/uni-mp-compiler/src/transforms/vBind.ts b/packages/uni-mp-compiler/src/transforms/vBind.ts
index 05967400e3d49989b8e5bccbcc25ae6b52949556..923c5254e5f2c135f15c4c6997bcfe76e893a521 100644
--- a/packages/uni-mp-compiler/src/transforms/vBind.ts
+++ b/packages/uni-mp-compiler/src/transforms/vBind.ts
@@ -8,7 +8,7 @@ import {
createSimpleExpression,
} from '@vue/compiler-core'
import { DirectiveTransform } from '../transform'
-import { errorMessages, MPErrorCodes } from '../errors'
+import { createMPCompilerError, MPErrorCodes } from '../errors'
export const transformBind: DirectiveTransform = (dir, _node, context) => {
const { exp, modifiers, loc } = dir
@@ -37,20 +37,12 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
if (modifiers.includes('prop')) {
context.onWarn(
- createCompilerError(
- MPErrorCodes.X_V_BIND_MODIFIER_PROP,
- loc,
- errorMessages
- )
+ createMPCompilerError(MPErrorCodes.X_V_BIND_MODIFIER_PROP, loc)
)
}
if (modifiers.includes('attr')) {
context.onWarn(
- createCompilerError(
- MPErrorCodes.X_V_BIND_MODIFIER_ATTR,
- loc,
- errorMessages
- )
+ createMPCompilerError(MPErrorCodes.X_V_BIND_MODIFIER_ATTR, loc)
)
}
diff --git a/packages/uni-mp-compiler/src/transforms/vModel.ts b/packages/uni-mp-compiler/src/transforms/vModel.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4043c85687d0c827e7c708c1697539fab5f62548
--- /dev/null
+++ b/packages/uni-mp-compiler/src/transforms/vModel.ts
@@ -0,0 +1,111 @@
+import {
+ Property,
+ transformModel as baseTransform,
+ ElementTypes,
+ findProp,
+ NodeTypes,
+ DirectiveNode,
+ ElementNode,
+ ExpressionNode,
+} from '@vue/compiler-core'
+import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
+import { camelize } from '@vue/shared'
+import { createBindDirectiveNode, createOnDirectiveNode } from '../ast'
+import { genExpr } from '../codegen'
+import { TransformContext } from '../transform'
+import { wrapperVOn } from './vOn'
+
+export const transformModel = (
+ dir: DirectiveNode,
+ node: ElementNode,
+ context: TransformContext
+) => {
+ const baseResult = baseTransform(dir, node, context as any)
+ // base transform has errors OR component v-model (only need props)
+ if (!baseResult.props.length || node.tagType === ElementTypes.COMPONENT) {
+ return transformComponentVModel(baseResult.props, context)
+ }
+
+ if (dir.arg) {
+ context.onError(
+ createDOMCompilerError(
+ DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
+ dir.arg.loc
+ )
+ )
+ }
+
+ function checkDuplicatedValue() {
+ const value = findProp(node, 'value')
+ if (value) {
+ context.onError(
+ createDOMCompilerError(
+ DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
+ value.loc
+ )
+ )
+ }
+ }
+
+ const { tag } = node
+ if (tag === 'input' || tag === 'textarea') {
+ checkDuplicatedValue()
+ } else {
+ context.onError(
+ createDOMCompilerError(
+ DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
+ dir.loc
+ )
+ )
+ }
+
+ // native vmodel doesn't need the `modelValue` props since they are also
+ // passed to the runtime as `binding.value`. removing it reduces code size.
+ baseResult.props = baseResult.props.filter(
+ (p) =>
+ !(
+ p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
+ p.key.content === 'modelValue'
+ )
+ )
+
+ return []
+}
+
+function transformComponentVModel(
+ props: Property[],
+ context: TransformContext
+): DirectiveNode[] {
+ if (props.length !== 2) {
+ return []
+ }
+ const { key: modelValueArg, value: modelValeExpr } = props[0]
+ const { key: onUpdateArg, value: onUpdateExpr } = props[1]
+
+ if (modelValueArg.type !== NodeTypes.SIMPLE_EXPRESSION) {
+ return []
+ }
+ if (
+ onUpdateArg.type !== NodeTypes.SIMPLE_EXPRESSION ||
+ !onUpdateArg.content.startsWith('onUpdate:')
+ ) {
+ return []
+ }
+ const vBindModelValue = createBindDirectiveNode(
+ modelValueArg.content,
+ genExpr(modelValeExpr as ExpressionNode)
+ )
+ const vOnUpdate = createOnDirectiveNode(
+ camelize(onUpdateArg.content.replace('onUpdate:', 'update-')),
+ genExpr(
+ wrapperVOn(
+ // onUpdateExpr 通常是 ExpressionNode 或者被 cache 的 ExpressionNode
+ (onUpdateExpr.type === NodeTypes.JS_CACHE_EXPRESSION
+ ? onUpdateExpr.value
+ : onUpdateExpr) as ExpressionNode,
+ context
+ )
+ ).replace(`= $event`, `= $event.detail.__args__[0]`)
+ )
+ return [vBindModelValue, vOnUpdate]
+}
diff --git a/packages/uni-mp-compiler/src/transforms/vOn.ts b/packages/uni-mp-compiler/src/transforms/vOn.ts
index 33a3fec0bd5cf1b99dc6c6f59c5e3c4bedd4b1c8..317dd7ec7afdd7a21e89747e6dfac4e5e04453ba 100644
--- a/packages/uni-mp-compiler/src/transforms/vOn.ts
+++ b/packages/uni-mp-compiler/src/transforms/vOn.ts
@@ -159,9 +159,15 @@ export const transformOn: DirectiveTransform = (
// context.cache(ret.props[0].value) as ExpressionNode,
// context
// )
- ret.props[0].value = wrapper(ret.props[0].value as ExpressionNode, context)
+ ret.props[0].value = wrapperVOn(
+ ret.props[0].value as ExpressionNode,
+ context
+ )
} else {
- ret.props[0].value = wrapper(ret.props[0].value as ExpressionNode, context)
+ ret.props[0].value = wrapperVOn(
+ ret.props[0].value as ExpressionNode,
+ context
+ )
}
// mark the key as handler for props normalization check
@@ -169,7 +175,7 @@ export const transformOn: DirectiveTransform = (
return ret
}
-function wrapper(value: ExpressionNode, context: TransformContext) {
+export function wrapperVOn(value: ExpressionNode, context: TransformContext) {
return createCompoundExpression([
`${context.helperString(V_ON)}(`,
value,
diff --git a/yarn.lock b/yarn.lock
index 41c0657ebd46947ffe15d66c06d5176791e8be01..927144079dbd49c3371919fe855eedb076d8a63f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2774,7 +2774,7 @@
estree-walker "^2.0.2"
source-map "^0.6.1"
-"@vue/compiler-dom@3.2.20":
+"@vue/compiler-dom@3.2.20", "@vue/compiler-dom@^3.2.20":
version "3.2.20"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.20.tgz#8e0ef354449c0faf41519b00bfc2045eae01dcb5"
integrity sha512-QnI77ec/JtV7R0YBbcVayYTDCRcI9OCbxiUQK6izVyqQO0658n0zQuoNwe+bYgtqnvGAIqTR3FShTd5y4oOjdg==