wip(compiler-dom): v-model runtime
This commit is contained in:
@@ -2,7 +2,6 @@ import { isString } from '@vue/shared'
|
||||
import { ForParseResult } from './transforms/vFor'
|
||||
import {
|
||||
CREATE_VNODE,
|
||||
RuntimeHelper,
|
||||
APPLY_DIRECTIVES,
|
||||
RENDER_SLOT,
|
||||
CREATE_SLOTS,
|
||||
@@ -88,7 +87,7 @@ export type TemplateChildNode =
|
||||
export interface RootNode extends Node {
|
||||
type: NodeTypes.ROOT
|
||||
children: TemplateChildNode[]
|
||||
helpers: RuntimeHelper[]
|
||||
helpers: symbol[]
|
||||
components: string[]
|
||||
directives: string[]
|
||||
hoists: JSChildNode[]
|
||||
@@ -184,7 +183,7 @@ export interface CompoundExpressionNode extends Node {
|
||||
| InterpolationNode
|
||||
| TextNode
|
||||
| string
|
||||
| RuntimeHelper)[]
|
||||
| symbol)[]
|
||||
// an expression parsed as the params of a function will track
|
||||
// the identifiers declared inside the function body.
|
||||
identifiers?: string[]
|
||||
@@ -226,10 +225,10 @@ export type JSChildNode =
|
||||
|
||||
export interface CallExpression extends Node {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION
|
||||
callee: string | RuntimeHelper
|
||||
callee: string | symbol
|
||||
arguments: (
|
||||
| string
|
||||
| RuntimeHelper
|
||||
| symbol
|
||||
| JSChildNode
|
||||
| TemplateChildNode
|
||||
| TemplateChildNode[])[]
|
||||
@@ -276,17 +275,17 @@ export interface ConditionalExpression extends Node {
|
||||
export interface PlainElementCodegenNode extends CallExpression {
|
||||
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
|
||||
arguments: // tag, props, children, patchFlag, dynamicProps
|
||||
| [string | RuntimeHelper]
|
||||
| [string | RuntimeHelper, PropsExpression]
|
||||
| [string | RuntimeHelper, 'null' | PropsExpression, TemplateChildNode[]]
|
||||
| [string | symbol]
|
||||
| [string | symbol, PropsExpression]
|
||||
| [string | symbol, 'null' | PropsExpression, TemplateChildNode[]]
|
||||
| [
|
||||
string | RuntimeHelper,
|
||||
string | symbol,
|
||||
'null' | PropsExpression,
|
||||
'null' | TemplateChildNode[],
|
||||
string
|
||||
]
|
||||
| [
|
||||
string | RuntimeHelper,
|
||||
string | symbol,
|
||||
'null' | PropsExpression,
|
||||
'null' | TemplateChildNode[],
|
||||
string,
|
||||
@@ -302,17 +301,17 @@ export type ElementCodegenNode =
|
||||
export interface PlainComponentCodegenNode extends CallExpression {
|
||||
callee: typeof CREATE_VNODE | typeof CREATE_BLOCK
|
||||
arguments: // Comp, props, slots, patchFlag, dynamicProps
|
||||
| [string | RuntimeHelper]
|
||||
| [string | RuntimeHelper, PropsExpression]
|
||||
| [string | RuntimeHelper, 'null' | PropsExpression, SlotsExpression]
|
||||
| [string | symbol]
|
||||
| [string | symbol, PropsExpression]
|
||||
| [string | symbol, 'null' | PropsExpression, SlotsExpression]
|
||||
| [
|
||||
string | RuntimeHelper,
|
||||
string | symbol,
|
||||
'null' | PropsExpression,
|
||||
'null' | SlotsExpression,
|
||||
string
|
||||
]
|
||||
| [
|
||||
string | RuntimeHelper,
|
||||
string | symbol,
|
||||
'null' | PropsExpression,
|
||||
'null' | SlotsExpression,
|
||||
string,
|
||||
|
||||
@@ -33,8 +33,7 @@ import {
|
||||
COMMENT,
|
||||
helperNameMap,
|
||||
RESOLVE_COMPONENT,
|
||||
RESOLVE_DIRECTIVE,
|
||||
RuntimeHelper
|
||||
RESOLVE_DIRECTIVE
|
||||
} from './runtimeHelpers'
|
||||
|
||||
type CodegenNode = TemplateChildNode | JSChildNode
|
||||
@@ -74,7 +73,7 @@ export interface CodegenContext extends Required<CodegenOptions> {
|
||||
offset: number
|
||||
indentLevel: number
|
||||
map?: SourceMapGenerator
|
||||
helper(key: RuntimeHelper): string
|
||||
helper(key: symbol): string
|
||||
push(code: string, node?: CodegenNode, openOnly?: boolean): void
|
||||
resetMapping(loc: SourceLocation): void
|
||||
indent(): void
|
||||
@@ -338,7 +337,7 @@ function genNodeListAsArray(
|
||||
}
|
||||
|
||||
function genNodeList(
|
||||
nodes: (string | RuntimeHelper | CodegenNode | TemplateChildNode[])[],
|
||||
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
|
||||
context: CodegenContext,
|
||||
multilines: boolean = false
|
||||
) {
|
||||
@@ -363,10 +362,7 @@ function genNodeList(
|
||||
}
|
||||
}
|
||||
|
||||
function genNode(
|
||||
node: CodegenNode | RuntimeHelper | string,
|
||||
context: CodegenContext
|
||||
) {
|
||||
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
|
||||
if (isString(node)) {
|
||||
context.push(node)
|
||||
return
|
||||
|
||||
@@ -98,3 +98,9 @@ export {
|
||||
createCompilerError
|
||||
} from './errors'
|
||||
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 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
|
||||
// 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`,
|
||||
[PORTAL]: `Portal`,
|
||||
[COMMENT]: `Comment`,
|
||||
@@ -62,3 +42,9 @@ export const helperNameMap = {
|
||||
[TO_HANDLERS]: `toHandlers`,
|
||||
[CAMELIZE]: `camelize`
|
||||
}
|
||||
|
||||
export function registerRuntimeHelpers(helpers: any) {
|
||||
Object.getOwnPropertySymbols(helpers).forEach(s => {
|
||||
helperNameMap[s] = helpers[s]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
COMMENT,
|
||||
CREATE_VNODE,
|
||||
FRAGMENT,
|
||||
RuntimeHelper,
|
||||
helperNameMap,
|
||||
APPLY_DIRECTIVES,
|
||||
CREATE_BLOCK
|
||||
@@ -48,8 +47,8 @@ export type DirectiveTransform = (
|
||||
node: ElementNode,
|
||||
context: TransformContext
|
||||
) => {
|
||||
props: Property | Property[]
|
||||
needRuntime: boolean
|
||||
props: Property[]
|
||||
needRuntime: boolean | symbol
|
||||
}
|
||||
|
||||
// A structural directive transform is a technically a NodeTransform;
|
||||
@@ -70,7 +69,7 @@ export interface TransformOptions {
|
||||
|
||||
export interface TransformContext extends Required<TransformOptions> {
|
||||
root: RootNode
|
||||
helpers: Set<RuntimeHelper>
|
||||
helpers: Set<symbol>
|
||||
components: Set<string>
|
||||
directives: Set<string>
|
||||
hoists: JSChildNode[]
|
||||
@@ -84,8 +83,8 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
parent: ParentNode | null
|
||||
childIndex: number
|
||||
currentNode: RootNode | TemplateChildNode | null
|
||||
helper<T extends RuntimeHelper>(name: T): T
|
||||
helperString(name: RuntimeHelper): string
|
||||
helper<T extends symbol>(name: T): T
|
||||
helperString(name: symbol): string
|
||||
replaceNode(node: TemplateChildNode): void
|
||||
removeNode(node?: TemplateChildNode): void
|
||||
onNodeRemoved: () => void
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
createObjectExpression,
|
||||
Property
|
||||
} from '../ast'
|
||||
import { isArray, PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import {
|
||||
CREATE_VNODE,
|
||||
@@ -28,6 +28,10 @@ import {
|
||||
import { getInnerRange, isVSlot, toValidAssetId } from '../utils'
|
||||
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
|
||||
export const transformElement: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
@@ -137,9 +141,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
||||
[
|
||||
vnode,
|
||||
createArrayExpression(
|
||||
runtimeDirectives.map(dir => {
|
||||
return createDirectiveArgs(dir, context)
|
||||
}),
|
||||
runtimeDirectives.map(dir => createDirectiveArgs(dir, context)),
|
||||
loc
|
||||
)
|
||||
],
|
||||
@@ -274,15 +276,13 @@ export function buildProps(
|
||||
if (directiveTransform) {
|
||||
// has built-in directive transform.
|
||||
const { props, needRuntime } = directiveTransform(prop, node, context)
|
||||
if (isArray(props)) {
|
||||
properties.push(...props)
|
||||
properties.forEach(analyzePatchFlag)
|
||||
} else {
|
||||
properties.push(props)
|
||||
analyzePatchFlag(props)
|
||||
}
|
||||
props.forEach(analyzePatchFlag)
|
||||
properties.push(...props)
|
||||
if (needRuntime) {
|
||||
runtimeDirectives.push(prop)
|
||||
if (isSymbol(needRuntime)) {
|
||||
directiveImportMap.set(prop, needRuntime)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 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 existing = knownProps[name]
|
||||
if (existing) {
|
||||
if (name.startsWith('on') || name === 'style' || name === 'class') {
|
||||
if (
|
||||
name === 'style' ||
|
||||
name === 'class' ||
|
||||
name.startsWith('on') ||
|
||||
name.startsWith('vnode')
|
||||
) {
|
||||
mergeAsArray(existing, prop)
|
||||
}
|
||||
// unexpected duplicate, should have emitted error during parse
|
||||
@@ -389,12 +394,17 @@ function createDirectiveArgs(
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext
|
||||
): ArrayExpression {
|
||||
// inject statement for resolving directive
|
||||
context.helper(RESOLVE_DIRECTIVE)
|
||||
context.directives.add(dir.name)
|
||||
const dirArgs: ArrayExpression['elements'] = [
|
||||
toValidAssetId(dir.name, `directive`)
|
||||
]
|
||||
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
|
||||
context.helper(RESOLVE_DIRECTIVE)
|
||||
context.directives.add(dir.name)
|
||||
dirArgs.push(toValidAssetId(dir.name, `directive`))
|
||||
}
|
||||
const { loc } = dir
|
||||
if (dir.exp) dirArgs.push(dir.exp)
|
||||
if (dir.arg) dirArgs.push(dir.arg)
|
||||
|
||||
@@ -28,10 +28,9 @@ export const transformBind: DirectiveTransform = (dir, node, context) => {
|
||||
}
|
||||
}
|
||||
return {
|
||||
props: createObjectProperty(
|
||||
arg!,
|
||||
exp || createSimpleExpression('', true, loc)
|
||||
),
|
||||
props: [
|
||||
createObjectProperty(arg!, exp || createSimpleExpression('', true, loc))
|
||||
],
|
||||
needRuntime: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
])
|
||||
: createSimpleExpression('onUpdate:modelValue', true)
|
||||
|
||||
return createTransformProps([
|
||||
const props = [
|
||||
createObjectProperty(propName, dir.exp!),
|
||||
createObjectProperty(
|
||||
eventName,
|
||||
@@ -48,7 +48,13 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
` = $event)`
|
||||
])
|
||||
)
|
||||
])
|
||||
]
|
||||
|
||||
if (dir.modifiers.length) {
|
||||
// TODO add modelModifiers prop
|
||||
}
|
||||
|
||||
return createTransformProps(props)
|
||||
}
|
||||
|
||||
function createTransformProps(props: Property[] = []) {
|
||||
|
||||
@@ -69,10 +69,12 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||
}
|
||||
|
||||
return {
|
||||
props: createObjectProperty(
|
||||
eventName,
|
||||
dir.exp || createSimpleExpression(`() => {}`, false, loc)
|
||||
),
|
||||
props: [
|
||||
createObjectProperty(
|
||||
eventName,
|
||||
dir.exp || createSimpleExpression(`() => {}`, false, loc)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import {
|
||||
|
||||
export const transformOnce: DirectiveTransform = dir => {
|
||||
return {
|
||||
props: createObjectProperty(
|
||||
createSimpleExpression(`$once`, true, dir.loc),
|
||||
createSimpleExpression('true', false)
|
||||
),
|
||||
props: [
|
||||
createObjectProperty(
|
||||
createSimpleExpression(`$once`, true, dir.loc),
|
||||
createSimpleExpression('true', false)
|
||||
)
|
||||
],
|
||||
needRuntime: false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user