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

wip(mp): v-model (Component)

上级 4084674e
......@@ -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
}
import { assert } from './testUtils'
describe('compiler: transform v-model', () => {
test(`component v-model`, () => {
assert(
`<Comp v-model="model" />`,
`<comp class="vue-ref" modelValue="{{a}}" bindupdateModelValue="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.model, b: _vOn($event => _ctx.model = $event.detail.__args__[0]) }
}`
)
})
test(`component v-model with cache`, () => {
assert(
`<Comp v-model="model" />`,
`<comp class="vue-ref" modelValue="{{a}}" bindupdateModelValue="{{b}}"/>`,
`(_ctx, _cache) => {
return { a: _ctx.model, b: _vOn($event => _ctx.model = $event.detail.__args__[0]) }
}`,
{
cacheHandlers: true,
}
)
})
// test(`input,textarea v-model`, () => {
// assert(
// `<input v-model="model" />`,
// `<input value="{{a}}" bindinput="{{b}}" />`,
// `(_ctx, _cache) => {
// return { a: _ctx.model, b: _vOn(($event)=>_ctx.model = $event.detail.value) }
// }`
// )
// })
})
......@@ -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"
}
}
......@@ -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
......
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<number, string> = {
const MPErrorMessages: Record<number, string> = {
[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<number, string> = {
[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
}
......@@ -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) {
......
......@@ -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) {
......
......@@ -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)
)
}
......
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]
}
......@@ -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,
......
......@@ -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==
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册