From d376439167c97b9e3d70663e5c219372248567b2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 10 Oct 2019 18:02:51 -0400 Subject: [PATCH] wip(compiler-dom): v-model runtime --- .../transforms/transformElement.spec.ts | 2 +- packages/compiler-core/src/ast.ts | 29 ++++---- packages/compiler-core/src/codegen.ts | 12 ++-- packages/compiler-core/src/index.ts | 6 ++ packages/compiler-core/src/runtimeHelpers.ts | 30 +++------ packages/compiler-core/src/transform.ts | 11 ++- .../src/transforms/transformElement.ts | 46 ++++++++----- .../compiler-core/src/transforms/vBind.ts | 7 +- .../compiler-core/src/transforms/vModel.ts | 10 ++- packages/compiler-core/src/transforms/vOn.ts | 10 +-- .../compiler-core/src/transforms/vOnce.ts | 10 +-- packages/compiler-dom/src/errors.ts | 8 ++- packages/compiler-dom/src/index.ts | 2 + packages/compiler-dom/src/runtimeHelpers.ts | 15 +++++ packages/compiler-dom/src/transforms/vHtml.ts | 10 +-- .../compiler-dom/src/transforms/vModel.ts | 67 ++++++++++++++++++- packages/compiler-dom/src/transforms/vText.ts | 10 +-- packages/runtime-core/src/index.ts | 1 + packages/runtime-dom/src/directives/vModel.ts | 40 +++++++++++ packages/runtime-dom/src/index.ts | 10 +++ packages/runtime-dom/src/patchProp.ts | 4 ++ 21 files changed, 245 insertions(+), 95 deletions(-) create mode 100644 packages/compiler-dom/src/runtimeHelpers.ts create mode 100644 packages/runtime-dom/src/directives/vModel.ts diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 092cb56d3..072d34950 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -271,7 +271,7 @@ describe('compiler: element transform', () => { foo(dir) { _dir = dir return { - props: createObjectProperty(dir.arg!, dir.exp!), + props: [createObjectProperty(dir.arg!, dir.exp!)], needRuntime: false } } diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 9da05bb89..c1a94dc69 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -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, diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index a7b5f7181..28c280344 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -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 { 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 diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 9e8343e68..ab5655d93 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -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' diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts index 6cd63d04c..f37511938 100644 --- a/packages/compiler-core/src/runtimeHelpers.ts +++ b/packages/compiler-core/src/runtimeHelpers.ts @@ -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] + }) +} diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 6970eb1ce..ee462effd 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -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 { root: RootNode - helpers: Set + helpers: Set components: Set directives: Set hoists: JSChildNode[] @@ -84,8 +83,8 @@ export interface TransformContext extends Required { parent: ParentNode | null childIndex: number currentNode: RootNode | TemplateChildNode | null - helper(name: T): T - helperString(name: RuntimeHelper): string + helper(name: T): T + helperString(name: symbol): string replaceNode(node: TemplateChildNode): void removeNode(node?: TemplateChildNode): void onNodeRemoved: () => void diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 762ed0d5d..08c4eb502 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -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() + // 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) diff --git a/packages/compiler-core/src/transforms/vBind.ts b/packages/compiler-core/src/transforms/vBind.ts index 065889f07..5f17714c3 100644 --- a/packages/compiler-core/src/transforms/vBind.ts +++ b/packages/compiler-core/src/transforms/vBind.ts @@ -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 } } diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts index 812bab7f6..86857ce37 100644 --- a/packages/compiler-core/src/transforms/vModel.ts +++ b/packages/compiler-core/src/transforms/vModel.ts @@ -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[] = []) { diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts index d4a21ba98..3e8c74c25 100644 --- a/packages/compiler-core/src/transforms/vOn.ts +++ b/packages/compiler-core/src/transforms/vOn.ts @@ -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 } } diff --git a/packages/compiler-core/src/transforms/vOnce.ts b/packages/compiler-core/src/transforms/vOnce.ts index b83e04057..170a24c3c 100644 --- a/packages/compiler-core/src/transforms/vOnce.ts +++ b/packages/compiler-core/src/transforms/vOnce.ts @@ -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 } } diff --git a/packages/compiler-dom/src/errors.ts b/packages/compiler-dom/src/errors.ts index edab3fb70..efe55715b 100644 --- a/packages/compiler-dom/src/errors.ts +++ b/packages/compiler-dom/src/errors.ts @@ -24,12 +24,16 @@ export const enum DOMErrorCodes { X_V_HTML_NO_EXPRESSION = ErrorCodes.__EXTEND_POINT__, X_V_HTML_WITH_CHILDREN, 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 } = { [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_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 ,