wip(compiler-dom): v-model runtime
This commit is contained in:
parent
145559e170
commit
d376439167
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
const dirArgs: ArrayExpression['elements'] = []
|
||||||
|
const runtime = directiveImportMap.get(dir)
|
||||||
|
if (runtime) {
|
||||||
|
context.helper(runtime)
|
||||||
|
dirArgs.push(context.helperString(runtime))
|
||||||
|
} else {
|
||||||
// inject statement for resolving directive
|
// inject statement for resolving directive
|
||||||
context.helper(RESOLVE_DIRECTIVE)
|
context.helper(RESOLVE_DIRECTIVE)
|
||||||
context.directives.add(dir.name)
|
context.directives.add(dir.name)
|
||||||
const dirArgs: ArrayExpression['elements'] = [
|
dirArgs.push(toValidAssetId(dir.name, `directive`))
|
||||||
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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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[] = []) {
|
||||||
|
@ -69,10 +69,12 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: createObjectProperty(
|
props: [
|
||||||
|
createObjectProperty(
|
||||||
eventName,
|
eventName,
|
||||||
dir.exp || createSimpleExpression(`() => {}`, false, loc)
|
dir.exp || createSimpleExpression(`() => {}`, false, loc)
|
||||||
),
|
)
|
||||||
|
],
|
||||||
needRuntime: false
|
needRuntime: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ import {
|
|||||||
|
|
||||||
export const transformOnce: DirectiveTransform = dir => {
|
export const transformOnce: DirectiveTransform = dir => {
|
||||||
return {
|
return {
|
||||||
props: createObjectProperty(
|
props: [
|
||||||
|
createObjectProperty(
|
||||||
createSimpleExpression(`$once`, true, dir.loc),
|
createSimpleExpression(`$once`, true, dir.loc),
|
||||||
createSimpleExpression('true', false)
|
createSimpleExpression('true', false)
|
||||||
),
|
)
|
||||||
|
],
|
||||||
needRuntime: false
|
needRuntime: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.`
|
||||||
}
|
}
|
||||||
|
@ -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 || {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
15
packages/compiler-dom/src/runtimeHelpers.ts
Normal file
15
packages/compiler-dom/src/runtimeHelpers.ts
Normal 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`
|
||||||
|
})
|
@ -19,10 +19,12 @@ export const transformVHtml: DirectiveTransform = (dir, node, context) => {
|
|||||||
node.children.length = 0
|
node.children.length = 0
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
props: createObjectProperty(
|
props: [
|
||||||
|
createObjectProperty(
|
||||||
createSimpleExpression(`innerHTML`, true, loc),
|
createSimpleExpression(`innerHTML`, true, loc),
|
||||||
exp || createSimpleExpression('', true)
|
exp || createSimpleExpression('', true)
|
||||||
),
|
)
|
||||||
|
],
|
||||||
needRuntime: false
|
needRuntime: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -19,10 +19,12 @@ export const transformVText: DirectiveTransform = (dir, node, context) => {
|
|||||||
node.children.length = 0
|
node.children.length = 0
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
props: createObjectProperty(
|
props: [
|
||||||
|
createObjectProperty(
|
||||||
createSimpleExpression(`textContent`, true, loc),
|
createSimpleExpression(`textContent`, true, loc),
|
||||||
exp || createSimpleExpression('', true)
|
exp || createSimpleExpression('', true)
|
||||||
),
|
)
|
||||||
|
],
|
||||||
needRuntime: false
|
needRuntime: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
40
packages/runtime-dom/src/directives/vModel.ts
Normal file
40
packages/runtime-dom/src/directives/vModel.ts
Normal 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) {}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user