wip(compiler-dom): v-model runtime

This commit is contained in:
Evan You 2019-10-10 18:02:51 -04:00
parent 145559e170
commit d376439167
21 changed files with 245 additions and 95 deletions

View File

@ -271,7 +271,7 @@ describe('compiler: element transform', () => {
foo(dir) { foo(dir) {
_dir = dir _dir = dir
return { return {
props: createObjectProperty(dir.arg!, dir.exp!), props: [createObjectProperty(dir.arg!, dir.exp!)],
needRuntime: false needRuntime: false
} }
} }

View File

@ -2,7 +2,6 @@ import { isString } from '@vue/shared'
import { ForParseResult } from './transforms/vFor' import { ForParseResult } from './transforms/vFor'
import { import {
CREATE_VNODE, CREATE_VNODE,
RuntimeHelper,
APPLY_DIRECTIVES, APPLY_DIRECTIVES,
RENDER_SLOT, RENDER_SLOT,
CREATE_SLOTS, CREATE_SLOTS,
@ -88,7 +87,7 @@ export type TemplateChildNode =
export interface RootNode extends Node { export interface RootNode extends Node {
type: NodeTypes.ROOT type: NodeTypes.ROOT
children: TemplateChildNode[] children: TemplateChildNode[]
helpers: RuntimeHelper[] helpers: symbol[]
components: string[] components: string[]
directives: string[] directives: string[]
hoists: JSChildNode[] hoists: JSChildNode[]
@ -184,7 +183,7 @@ export interface CompoundExpressionNode extends Node {
| InterpolationNode | InterpolationNode
| TextNode | TextNode
| string | string
| RuntimeHelper)[] | symbol)[]
// an expression parsed as the params of a function will track // an expression parsed as the params of a function will track
// the identifiers declared inside the function body. // the identifiers declared inside the function body.
identifiers?: string[] identifiers?: string[]
@ -226,10 +225,10 @@ export type JSChildNode =
export interface CallExpression extends Node { export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION type: NodeTypes.JS_CALL_EXPRESSION
callee: string | RuntimeHelper callee: string | symbol
arguments: ( arguments: (
| string | string
| RuntimeHelper | symbol
| JSChildNode | JSChildNode
| TemplateChildNode | TemplateChildNode
| TemplateChildNode[])[] | TemplateChildNode[])[]
@ -276,17 +275,17 @@ export interface ConditionalExpression extends Node {
export interface PlainElementCodegenNode extends CallExpression { export interface PlainElementCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // tag, props, children, patchFlag, dynamicProps arguments: // tag, props, children, patchFlag, dynamicProps
| [string | RuntimeHelper] | [string | symbol]
| [string | RuntimeHelper, PropsExpression] | [string | symbol, PropsExpression]
| [string | RuntimeHelper, 'null' | PropsExpression, TemplateChildNode[]] | [string | symbol, 'null' | PropsExpression, TemplateChildNode[]]
| [ | [
string | RuntimeHelper, string | symbol,
'null' | PropsExpression, 'null' | PropsExpression,
'null' | TemplateChildNode[], 'null' | TemplateChildNode[],
string string
] ]
| [ | [
string | RuntimeHelper, string | symbol,
'null' | PropsExpression, 'null' | PropsExpression,
'null' | TemplateChildNode[], 'null' | TemplateChildNode[],
string, string,
@ -302,17 +301,17 @@ export type ElementCodegenNode =
export interface PlainComponentCodegenNode extends CallExpression { export interface PlainComponentCodegenNode extends CallExpression {
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
arguments: // Comp, props, slots, patchFlag, dynamicProps arguments: // Comp, props, slots, patchFlag, dynamicProps
| [string | RuntimeHelper] | [string | symbol]
| [string | RuntimeHelper, PropsExpression] | [string | symbol, PropsExpression]
| [string | RuntimeHelper, 'null' | PropsExpression, SlotsExpression] | [string | symbol, 'null' | PropsExpression, SlotsExpression]
| [ | [
string | RuntimeHelper, string | symbol,
'null' | PropsExpression, 'null' | PropsExpression,
'null' | SlotsExpression, 'null' | SlotsExpression,
string string
] ]
| [ | [
string | RuntimeHelper, string | symbol,
'null' | PropsExpression, 'null' | PropsExpression,
'null' | SlotsExpression, 'null' | SlotsExpression,
string, string,

View File

@ -33,8 +33,7 @@ import {
COMMENT, COMMENT,
helperNameMap, helperNameMap,
RESOLVE_COMPONENT, RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE, RESOLVE_DIRECTIVE
RuntimeHelper
} from './runtimeHelpers' } from './runtimeHelpers'
type CodegenNode = TemplateChildNode | JSChildNode type CodegenNode = TemplateChildNode | JSChildNode
@ -74,7 +73,7 @@ export interface CodegenContext extends Required<CodegenOptions> {
offset: number offset: number
indentLevel: number indentLevel: number
map?: SourceMapGenerator map?: SourceMapGenerator
helper(key: RuntimeHelper): string helper(key: symbol): string
push(code: string, node?: CodegenNode, openOnly?: boolean): void push(code: string, node?: CodegenNode, openOnly?: boolean): void
resetMapping(loc: SourceLocation): void resetMapping(loc: SourceLocation): void
indent(): void indent(): void
@ -338,7 +337,7 @@ function genNodeListAsArray(
} }
function genNodeList( function genNodeList(
nodes: (string | RuntimeHelper | CodegenNode | TemplateChildNode[])[], nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
context: CodegenContext, context: CodegenContext,
multilines: boolean = false multilines: boolean = false
) { ) {
@ -363,10 +362,7 @@ function genNodeList(
} }
} }
function genNode( function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
node: CodegenNode | RuntimeHelper | string,
context: CodegenContext
) {
if (isString(node)) { if (isString(node)) {
context.push(node) context.push(node)
return return

View File

@ -98,3 +98,9 @@ export {
createCompilerError createCompilerError
} from './errors' } from './errors'
export * from './ast' export * from './ast'
export * from './utils'
export { registerRuntimeHelpers } from './runtimeHelpers'
// expose transforms so higher-order compilers can import and extend them
export { transformModel } from './transforms/vModel'
export { transformOn } from './transforms/vOn'

View File

@ -18,30 +18,10 @@ export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``) export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``) export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
export type RuntimeHelper =
| typeof FRAGMENT
| typeof PORTAL
| typeof COMMENT
| typeof TEXT
| typeof SUSPENSE
| typeof EMPTY
| typeof OPEN_BLOCK
| typeof CREATE_BLOCK
| typeof CREATE_VNODE
| typeof RESOLVE_COMPONENT
| typeof RESOLVE_DIRECTIVE
| typeof APPLY_DIRECTIVES
| typeof RENDER_LIST
| typeof RENDER_SLOT
| typeof CREATE_SLOTS
| typeof TO_STRING
| typeof MERGE_PROPS
| typeof TO_HANDLERS
| typeof CAMELIZE
// Name mapping for runtime helpers that need to be imported from 'vue' in // Name mapping for runtime helpers that need to be imported from 'vue' in
// generated code. Make sure these are correctly exported in the runtime! // generated code. Make sure these are correctly exported in the runtime!
export const helperNameMap = { // Using `any` here because TS doesn't allow symbols as index type.
export const helperNameMap: any = {
[FRAGMENT]: `Fragment`, [FRAGMENT]: `Fragment`,
[PORTAL]: `Portal`, [PORTAL]: `Portal`,
[COMMENT]: `Comment`, [COMMENT]: `Comment`,
@ -62,3 +42,9 @@ export const helperNameMap = {
[TO_HANDLERS]: `toHandlers`, [TO_HANDLERS]: `toHandlers`,
[CAMELIZE]: `camelize` [CAMELIZE]: `camelize`
} }
export function registerRuntimeHelpers(helpers: any) {
Object.getOwnPropertySymbols(helpers).forEach(s => {
helperNameMap[s] = helpers[s]
})
}

View File

@ -22,7 +22,6 @@ import {
COMMENT, COMMENT,
CREATE_VNODE, CREATE_VNODE,
FRAGMENT, FRAGMENT,
RuntimeHelper,
helperNameMap, helperNameMap,
APPLY_DIRECTIVES, APPLY_DIRECTIVES,
CREATE_BLOCK CREATE_BLOCK
@ -48,8 +47,8 @@ export type DirectiveTransform = (
node: ElementNode, node: ElementNode,
context: TransformContext context: TransformContext
) => { ) => {
props: Property | Property[] props: Property[]
needRuntime: boolean needRuntime: boolean | symbol
} }
// A structural directive transform is a technically a NodeTransform; // A structural directive transform is a technically a NodeTransform;
@ -70,7 +69,7 @@ export interface TransformOptions {
export interface TransformContext extends Required<TransformOptions> { export interface TransformContext extends Required<TransformOptions> {
root: RootNode root: RootNode
helpers: Set<RuntimeHelper> helpers: Set<symbol>
components: Set<string> components: Set<string>
directives: Set<string> directives: Set<string>
hoists: JSChildNode[] hoists: JSChildNode[]
@ -84,8 +83,8 @@ export interface TransformContext extends Required<TransformOptions> {
parent: ParentNode | null parent: ParentNode | null
childIndex: number childIndex: number
currentNode: RootNode | TemplateChildNode | null currentNode: RootNode | TemplateChildNode | null
helper<T extends RuntimeHelper>(name: T): T helper<T extends symbol>(name: T): T
helperString(name: RuntimeHelper): string helperString(name: symbol): string
replaceNode(node: TemplateChildNode): void replaceNode(node: TemplateChildNode): void
removeNode(node?: TemplateChildNode): void removeNode(node?: TemplateChildNode): void
onNodeRemoved: () => void onNodeRemoved: () => void

View File

@ -15,7 +15,7 @@ import {
createObjectExpression, createObjectExpression,
Property Property
} from '../ast' } from '../ast'
import { isArray, PatchFlags, PatchFlagNames } from '@vue/shared' import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { import {
CREATE_VNODE, CREATE_VNODE,
@ -28,6 +28,10 @@ import {
import { getInnerRange, isVSlot, toValidAssetId } from '../utils' import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
import { buildSlots } from './vSlot' import { buildSlots } from './vSlot'
// some directive transforms (e.g. v-model) may return a symbol for runtime
// import, which should be used instead of a resolveDirective call.
const directiveImportMap = new WeakMap<DirectiveNode, symbol>()
// generate a JavaScript AST for this element's codegen // generate a JavaScript AST for this element's codegen
export const transformElement: NodeTransform = (node, context) => { export const transformElement: NodeTransform = (node, context) => {
if (node.type === NodeTypes.ELEMENT) { if (node.type === NodeTypes.ELEMENT) {
@ -137,9 +141,7 @@ export const transformElement: NodeTransform = (node, context) => {
[ [
vnode, vnode,
createArrayExpression( createArrayExpression(
runtimeDirectives.map(dir => { runtimeDirectives.map(dir => createDirectiveArgs(dir, context)),
return createDirectiveArgs(dir, context)
}),
loc loc
) )
], ],
@ -274,15 +276,13 @@ export function buildProps(
if (directiveTransform) { if (directiveTransform) {
// has built-in directive transform. // has built-in directive transform.
const { props, needRuntime } = directiveTransform(prop, node, context) const { props, needRuntime } = directiveTransform(prop, node, context)
if (isArray(props)) { props.forEach(analyzePatchFlag)
properties.push(...props) properties.push(...props)
properties.forEach(analyzePatchFlag)
} else {
properties.push(props)
analyzePatchFlag(props)
}
if (needRuntime) { if (needRuntime) {
runtimeDirectives.push(prop) runtimeDirectives.push(prop)
if (isSymbol(needRuntime)) {
directiveImportMap.set(prop, needRuntime)
}
} }
} else { } else {
// no built-in transform, this is a user custom directive. // no built-in transform, this is a user custom directive.
@ -362,7 +362,12 @@ function dedupeProperties(properties: Property[]): Property[] {
const name = prop.key.content const name = prop.key.content
const existing = knownProps[name] const existing = knownProps[name]
if (existing) { if (existing) {
if (name.startsWith('on') || name === 'style' || name === 'class') { if (
name === 'style' ||
name === 'class' ||
name.startsWith('on') ||
name.startsWith('vnode')
) {
mergeAsArray(existing, prop) mergeAsArray(existing, prop)
} }
// unexpected duplicate, should have emitted error during parse // unexpected duplicate, should have emitted error during parse
@ -389,12 +394,17 @@ function createDirectiveArgs(
dir: DirectiveNode, dir: DirectiveNode,
context: TransformContext context: TransformContext
): ArrayExpression { ): ArrayExpression {
// inject statement for resolving directive const dirArgs: ArrayExpression['elements'] = []
context.helper(RESOLVE_DIRECTIVE) const runtime = directiveImportMap.get(dir)
context.directives.add(dir.name) if (runtime) {
const dirArgs: ArrayExpression['elements'] = [ context.helper(runtime)
toValidAssetId(dir.name, `directive`) dirArgs.push(context.helperString(runtime))
] } else {
// inject statement for resolving directive
context.helper(RESOLVE_DIRECTIVE)
context.directives.add(dir.name)
dirArgs.push(toValidAssetId(dir.name, `directive`))
}
const { loc } = dir const { loc } = dir
if (dir.exp) dirArgs.push(dir.exp) if (dir.exp) dirArgs.push(dir.exp)
if (dir.arg) dirArgs.push(dir.arg) if (dir.arg) dirArgs.push(dir.arg)

View File

@ -28,10 +28,9 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
} }
} }
return { return {
props: createObjectProperty( props: [
arg!, createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
exp || createSimpleExpression('', true, loc) ],
),
needRuntime: false needRuntime: false
} }
} }

View File

@ -38,7 +38,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
]) ])
: createSimpleExpression('onUpdate:modelValue', true) : createSimpleExpression('onUpdate:modelValue', true)
return createTransformProps([ const props = [
createObjectProperty(propName, dir.exp!), createObjectProperty(propName, dir.exp!),
createObjectProperty( createObjectProperty(
eventName, eventName,
@ -48,7 +48,13 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
` = $event)` ` = $event)`
]) ])
) )
]) ]
if (dir.modifiers.length) {
// TODO add modelModifiers prop
}
return createTransformProps(props)
} }
function createTransformProps(props: Property[] = []) { function createTransformProps(props: Property[] = []) {

View File

@ -69,10 +69,12 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
} }
return { return {
props: createObjectProperty( props: [
eventName, createObjectProperty(
dir.exp || createSimpleExpression(`() => {}`, false, loc) eventName,
), dir.exp || createSimpleExpression(`() => {}`, false, loc)
)
],
needRuntime: false needRuntime: false
} }
} }

View File

@ -6,10 +6,12 @@ import {
export const transformOnce: DirectiveTransform = dir => { export const transformOnce: DirectiveTransform = dir => {
return { return {
props: createObjectProperty( props: [
createSimpleExpression(`$once`, true, dir.loc), createObjectProperty(
createSimpleExpression('true', false) createSimpleExpression(`$once`, true, dir.loc),
), createSimpleExpression('true', false)
)
],
needRuntime: false needRuntime: false
} }
} }

View File

@ -24,12 +24,16 @@ export const enum DOMErrorCodes {
X_V_HTML_NO_EXPRESSION = ErrorCodes.__EXTEND_POINT__, X_V_HTML_NO_EXPRESSION = ErrorCodes.__EXTEND_POINT__,
X_V_HTML_WITH_CHILDREN, X_V_HTML_WITH_CHILDREN,
X_V_TEXT_NO_EXPRESSION, X_V_TEXT_NO_EXPRESSION,
X_V_TEXT_WITH_CHILDREN X_V_TEXT_WITH_CHILDREN,
X_V_MODEL_ON_INVALID_ELEMENT,
X_V_MODEL_ARG_ON_ELEMENT
} }
export const DOMErrorMessages: { [code: number]: string } = { export const DOMErrorMessages: { [code: number]: string } = {
[DOMErrorCodes.X_V_HTML_NO_EXPRESSION]: `v-html is missing expression.`, [DOMErrorCodes.X_V_HTML_NO_EXPRESSION]: `v-html is missing expression.`,
[DOMErrorCodes.X_V_HTML_WITH_CHILDREN]: `v-html will override element children.`, [DOMErrorCodes.X_V_HTML_WITH_CHILDREN]: `v-html will override element children.`,
[DOMErrorCodes.X_V_TEXT_NO_EXPRESSION]: `v-text is missing expression.`, [DOMErrorCodes.X_V_TEXT_NO_EXPRESSION]: `v-text is missing expression.`,
[DOMErrorCodes.X_V_TEXT_WITH_CHILDREN]: `v-text will override element children.` [DOMErrorCodes.X_V_TEXT_WITH_CHILDREN]: `v-text will override element children.`,
[DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
[DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT]: `v-model argument is not supported on plain elements.`
} }

View File

@ -5,6 +5,7 @@ import { transformStyle } from './transforms/transformStyle'
import { transformCloak } from './transforms/vCloak' import { transformCloak } from './transforms/vCloak'
import { transformVHtml } from './transforms/vHtml' import { transformVHtml } from './transforms/vHtml'
import { transformVText } from './transforms/vText' import { transformVText } from './transforms/vText'
import { transformModel } from './transforms/vModel'
export function compile( export function compile(
template: string, template: string,
@ -18,6 +19,7 @@ export function compile(
cloak: transformCloak, cloak: transformCloak,
html: transformVHtml, html: transformVHtml,
text: transformVText, text: transformVText,
model: transformModel, // override compiler-core
...(options.directiveTransforms || {}) ...(options.directiveTransforms || {})
} }
}) })

View File

@ -0,0 +1,15 @@
import { registerRuntimeHelpers } from '@vue/compiler-core'
export const V_MODEL_RADIO = Symbol(__DEV__ ? `vModelRadio` : ``)
export const V_MODEL_CHECKBOX = Symbol(__DEV__ ? `vModelCheckbox` : ``)
export const V_MODEL_TEXT = Symbol(__DEV__ ? `vModelText` : ``)
export const V_MODEL_SELECT = Symbol(__DEV__ ? `vModelSelect` : ``)
export const V_MODEL_DYNAMIC = Symbol(__DEV__ ? `vModelDynamic` : ``)
registerRuntimeHelpers({
[V_MODEL_RADIO]: `vModelRadio`,
[V_MODEL_CHECKBOX]: `vModelCheckbox`,
[V_MODEL_TEXT]: `vModelText`,
[V_MODEL_SELECT]: `vModelSelect`,
[V_MODEL_DYNAMIC]: `vModelDynamic`
})

View File

@ -19,10 +19,12 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
node.children.length = 0 node.children.length = 0
} }
return { return {
props: createObjectProperty( props: [
createSimpleExpression(`innerHTML`, true, loc), createObjectProperty(
exp || createSimpleExpression('', true) createSimpleExpression(`innerHTML`, true, loc),
), exp || createSimpleExpression('', true)
)
],
needRuntime: false needRuntime: false
} }
} }

View File

@ -1 +1,66 @@
// TODO import {
transformModel as baseTransform,
DirectiveTransform,
ElementTypes,
findProp,
NodeTypes
} from '@vue/compiler-core'
import { createDOMCompilerError, DOMErrorCodes } from '../errors'
import {
V_MODEL_CHECKBOX,
V_MODEL_RADIO,
V_MODEL_SELECT,
V_MODEL_TEXT,
V_MODEL_DYNAMIC
} from '../runtimeHelpers'
export const transformModel: DirectiveTransform = (dir, node, context) => {
const res = baseTransform(dir, node, context)
const { tag, tagType } = node
if (tagType === ElementTypes.ELEMENT) {
if (dir.arg) {
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
dir.arg.loc
)
)
}
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
let directiveToUse = V_MODEL_TEXT
if (tag === 'input') {
const type = findProp(node, `type`)
if (type) {
if (type.type === NodeTypes.DIRECTIVE) {
// :type="foo"
directiveToUse = V_MODEL_DYNAMIC
} else if (type.value) {
switch (type.value.content) {
case 'radio':
directiveToUse = V_MODEL_RADIO
break
case 'checkbox':
directiveToUse = V_MODEL_CHECKBOX
break
}
}
}
} else if (tag === 'select') {
directiveToUse = V_MODEL_SELECT
}
// inject runtime directive
// by returning the helper symbol via needRuntime
// the import will replaced a resovleDirective call.
res.needRuntime = context.helper(directiveToUse)
} else {
context.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
dir.loc
)
)
}
}
return res
}

View File

@ -19,10 +19,12 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
node.children.length = 0 node.children.length = 0
} }
return { return {
props: createObjectProperty( props: [
createSimpleExpression(`textContent`, true, loc), createObjectProperty(
exp || createSimpleExpression('', true) createSimpleExpression(`textContent`, true, loc),
), exp || createSimpleExpression('', true)
)
],
needRuntime: false needRuntime: false
} }
} }

View File

@ -29,6 +29,7 @@ export { getCurrentInstance } from './component'
// For custom renderers // For custom renderers
export { createRenderer } from './createRenderer' export { createRenderer } from './createRenderer'
export { warn } from './warning'
export { export {
handleError, handleError,
callWithErrorHandling, callWithErrorHandling,

View File

@ -0,0 +1,40 @@
import { Directive } from '@vue/runtime-core'
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
export const vModelText: Directive = {
beforeMount(el, binding) {
el.value = binding.value
},
mounted(el, binding, vnode) {},
beforeUpdate(el, binding, vnode, prevVNode) {},
updated(el, binding, vnode) {}
}
export const vModelRadio: Directive = {
beforeMount(el, binding, vnode) {},
mounted(el, binding, vnode) {},
beforeUpdate(el, binding, vnode, prevVNode) {},
updated(el, binding, vnode) {}
}
export const vModelCheckbox: Directive = {
beforeMount(el, binding, vnode) {},
mounted(el, binding, vnode) {},
beforeUpdate(el, binding, vnode, prevVNode) {},
updated(el, binding, vnode) {}
}
export const vModelSelect: Directive = {
beforeMount(el, binding, vnode) {},
mounted(el, binding, vnode) {},
beforeUpdate(el, binding, vnode, prevVNode) {},
updated(el, binding, vnode) {}
}
export const vModelDynamic: Directive = {
beforeMount(el, binding, vnode) {},
mounted(el, binding, vnode) {},
beforeUpdate(el, binding, vnode, prevVNode) {},
updated(el, binding, vnode) {}
}

View File

@ -9,10 +9,20 @@ const { render, createApp } = createRenderer<Node, Element>({
export { render, createApp } export { render, createApp }
// DOM-only runtime helpers
export {
vModelText,
vModelCheckbox,
vModelRadio,
vModelSelect,
vModelDynamic
} from './directives/vModel'
// re-export everything from core // re-export everything from core
// h, Component, reactivity API, nextTick, flags & types // h, Component, reactivity API, nextTick, flags & types
export * from '@vue/runtime-core' export * from '@vue/runtime-core'
// Type augmentations
export interface ComponentPublicInstance { export interface ComponentPublicInstance {
$el: Element $el: Element
} }

View File

@ -29,6 +29,10 @@ export function patchProp(
case 'style': case 'style':
patchStyle(el, prevValue, nextValue) patchStyle(el, prevValue, nextValue)
break break
case 'modelValue':
case 'onUpdate:modelValue':
// Do nothing. This is handled by v-model directives.
break
default: default:
if (isOn(key)) { if (isOn(key)) {
patchEvent( patchEvent(