]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): element transform
authorEvan You <yyx990803@gmail.com>
Sat, 21 Sep 2019 21:42:12 +0000 (17:42 -0400)
committerEvan You <yyx990803@gmail.com>
Sat, 21 Sep 2019 21:42:12 +0000 (17:42 -0400)
13 files changed:
packages/compiler-core/__tests__/directives/vFor.spec.ts
packages/compiler-core/__tests__/directives/vIf.spec.ts
packages/compiler-core/__tests__/transform.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/errors.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/parse.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/element.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-core/src/transforms/vPre.ts [deleted file]
packages/compiler-dom/src/index.ts

index b23537e373c6bc085b31f2ecc58dae27d4b7e811..64ed06e7e731a54ff2c6e9f1f978e8c5663a4d2c 100644 (file)
@@ -1,6 +1,6 @@
 import { parse } from '../../src/parse'
 import { transform } from '../../src/transform'
-import { transformFor } from '../../src/directives/vFor'
+import { transformFor } from '../../src/transforms/vFor'
 import { ForNode, NodeTypes } from '../../src/ast'
 import { ErrorCodes } from '../../src/errors'
 
@@ -9,7 +9,7 @@ describe('v-for', () => {
     test('number expression', () => {
       const node = parse('<span v-for="index in 5" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -25,7 +25,7 @@ describe('v-for', () => {
     test('value', () => {
       const node = parse('<span v-for="(item) in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -41,7 +41,7 @@ describe('v-for', () => {
     test('object de-structured value', () => {
       const node = parse('<span v-for="({ id, value }) in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -57,7 +57,7 @@ describe('v-for', () => {
     test('array de-structured value', () => {
       const node = parse('<span v-for="([ id, value ]) in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -73,7 +73,7 @@ describe('v-for', () => {
     test('value and key', () => {
       const node = parse('<span v-for="(item, key) in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -89,7 +89,7 @@ describe('v-for', () => {
     test('value, key and index', () => {
       const node = parse('<span v-for="(value, key, index) in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -107,7 +107,7 @@ describe('v-for', () => {
     test('skipped key', () => {
       const node = parse('<span v-for="(value,,index) in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -124,7 +124,7 @@ describe('v-for', () => {
     test('skipped value and key', () => {
       const node = parse('<span v-for="(,,index) in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -141,7 +141,7 @@ describe('v-for', () => {
     test('unbracketed value', () => {
       const node = parse('<span v-for="item in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -157,7 +157,7 @@ describe('v-for', () => {
     test('unbracketed value and key', () => {
       const node = parse('<span v-for="item, key in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -174,7 +174,7 @@ describe('v-for', () => {
     test('unbracketed value, key and index', () => {
       const node = parse('<span v-for="value, key, index in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -192,7 +192,7 @@ describe('v-for', () => {
     test('unbracketed skipped key', () => {
       const node = parse('<span v-for="value, , index in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -209,7 +209,7 @@ describe('v-for', () => {
     test('unbracketed skipped value and key', () => {
       const node = parse('<span v-for=", , index in items" />')
 
-      transform(node, { transforms: [transformFor] })
+      transform(node, { nodeTransforms: [transformFor] })
 
       expect(node.children.length).toBe(1)
 
@@ -226,7 +226,7 @@ describe('v-for', () => {
     test('missing expression', () => {
       const node = parse('<span v-for />')
       const onError = jest.fn()
-      transform(node, { transforms: [transformFor], onError })
+      transform(node, { nodeTransforms: [transformFor], onError })
 
       expect(onError).toHaveBeenCalledTimes(1)
       expect(onError).toHaveBeenCalledWith(
@@ -239,7 +239,7 @@ describe('v-for', () => {
     test('empty expression', () => {
       const node = parse('<span v-for="" />')
       const onError = jest.fn()
-      transform(node, { transforms: [transformFor], onError })
+      transform(node, { nodeTransforms: [transformFor], onError })
 
       expect(onError).toHaveBeenCalledTimes(1)
       expect(onError).toHaveBeenCalledWith(
@@ -252,7 +252,7 @@ describe('v-for', () => {
     test('invalid expression', () => {
       const node = parse('<span v-for="items" />')
       const onError = jest.fn()
-      transform(node, { transforms: [transformFor], onError })
+      transform(node, { nodeTransforms: [transformFor], onError })
 
       expect(onError).toHaveBeenCalledTimes(1)
       expect(onError).toHaveBeenCalledWith(
@@ -265,7 +265,7 @@ describe('v-for', () => {
     test('missing source', () => {
       const node = parse('<span v-for="item in" />')
       const onError = jest.fn()
-      transform(node, { transforms: [transformFor], onError })
+      transform(node, { nodeTransforms: [transformFor], onError })
 
       expect(onError).toHaveBeenCalledTimes(1)
       expect(onError).toHaveBeenCalledWith(
@@ -278,7 +278,7 @@ describe('v-for', () => {
     test('missing value', () => {
       const node = parse('<span v-for="in items" />')
       const onError = jest.fn()
-      transform(node, { transforms: [transformFor], onError })
+      transform(node, { nodeTransforms: [transformFor], onError })
 
       expect(onError).toHaveBeenCalledTimes(1)
       expect(onError).toHaveBeenCalledWith(
@@ -293,7 +293,7 @@ describe('v-for', () => {
         const source = '<span v-for="item in items" />'
         const node = parse(source)
 
-        transform(node, { transforms: [transformFor] })
+        transform(node, { nodeTransforms: [transformFor] })
 
         expect(node.children.length).toBe(1)
 
@@ -328,7 +328,7 @@ describe('v-for', () => {
         const source = '<span v-for="( item ) in items" />'
         const node = parse(source)
 
-        transform(node, { transforms: [transformFor] })
+        transform(node, { nodeTransforms: [transformFor] })
 
         expect(node.children.length).toBe(1)
 
@@ -363,7 +363,7 @@ describe('v-for', () => {
         const source = '<span v-for="(  { id, key })in items" />'
         const node = parse(source)
 
-        transform(node, { transforms: [transformFor] })
+        transform(node, { nodeTransforms: [transformFor] })
 
         expect(node.children.length).toBe(1)
 
@@ -398,7 +398,7 @@ describe('v-for', () => {
         const source = '<span v-for="( item, key, index ) in items" />'
         const node = parse(source)
 
-        transform(node, { transforms: [transformFor] })
+        transform(node, { nodeTransforms: [transformFor] })
 
         expect(node.children.length).toBe(1)
 
@@ -455,7 +455,7 @@ describe('v-for', () => {
         const source = '<span v-for="( item,, index ) in items" />'
         const node = parse(source)
 
-        transform(node, { transforms: [transformFor] })
+        transform(node, { nodeTransforms: [transformFor] })
 
         expect(node.children.length).toBe(1)
 
index 5039010148d8b0f0d3fb04d478fb82b1b8cffee5..86809b9bdbf33cff9174beba1db07fc607947168 100644 (file)
@@ -1,6 +1,6 @@
 import { parse } from '../../src/parse'
 import { transform } from '../../src/transform'
-import { transformIf } from '../../src/directives/vIf'
+import { transformIf } from '../../src/transforms/vIf'
 import {
   IfNode,
   NodeTypes,
@@ -15,7 +15,7 @@ describe('compiler: v-if', () => {
     test('basic v-if', () => {
       const ast = parse(`<div v-if="ok"/>`)
       transform(ast, {
-        transforms: [transformIf]
+        nodeTransforms: [transformIf]
       })
       const node = ast.children[0] as IfNode
       expect(node.type).toBe(NodeTypes.IF)
@@ -29,7 +29,7 @@ describe('compiler: v-if', () => {
     test('template v-if', () => {
       const ast = parse(`<template v-if="ok"><div/>hello<p/></template>`)
       transform(ast, {
-        transforms: [transformIf]
+        nodeTransforms: [transformIf]
       })
       const node = ast.children[0] as IfNode
       expect(node.type).toBe(NodeTypes.IF)
@@ -47,7 +47,7 @@ describe('compiler: v-if', () => {
     test('v-if + v-else', () => {
       const ast = parse(`<div v-if="ok"/><p v-else/>`)
       transform(ast, {
-        transforms: [transformIf]
+        nodeTransforms: [transformIf]
       })
       // should fold branches
       expect(ast.children.length).toBe(1)
@@ -72,7 +72,7 @@ describe('compiler: v-if', () => {
     test('v-if + v-else-if', () => {
       const ast = parse(`<div v-if="ok"/><p v-else-if="orNot"/>`)
       transform(ast, {
-        transforms: [transformIf]
+        nodeTransforms: [transformIf]
       })
       // should fold branches
       expect(ast.children.length).toBe(1)
@@ -99,7 +99,7 @@ describe('compiler: v-if', () => {
         `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
       )
       transform(ast, {
-        transforms: [transformIf]
+        nodeTransforms: [transformIf]
       })
       // should fold branches
       expect(ast.children.length).toBe(1)
@@ -136,7 +136,7 @@ describe('compiler: v-if', () => {
         <template v-else>fine</template>
       `)
       transform(ast, {
-        transforms: [transformIf]
+        nodeTransforms: [transformIf]
       })
       // should fold branches
       expect(ast.children.length).toBe(1)
@@ -172,7 +172,7 @@ describe('compiler: v-if', () => {
       const ast = parse(`<div v-else/>`)
       const spy = jest.fn()
       transform(ast, {
-        transforms: [transformIf],
+        nodeTransforms: [transformIf],
         onError: spy
       })
       expect(spy.mock.calls[0]).toMatchObject([
@@ -185,7 +185,7 @@ describe('compiler: v-if', () => {
       const ast2 = parse(`<div/><div v-else/>`)
       const spy2 = jest.fn()
       transform(ast2, {
-        transforms: [transformIf],
+        nodeTransforms: [transformIf],
         onError: spy2
       })
       expect(spy2.mock.calls[0]).toMatchObject([
@@ -198,7 +198,7 @@ describe('compiler: v-if', () => {
       const ast3 = parse(`<div/>foo<div v-else/>`)
       const spy3 = jest.fn()
       transform(ast3, {
-        transforms: [transformIf],
+        nodeTransforms: [transformIf],
         onError: spy3
       })
       expect(spy3.mock.calls[0]).toMatchObject([
@@ -213,7 +213,7 @@ describe('compiler: v-if', () => {
       const ast = parse(`<div v-else-if="foo"/>`)
       const spy = jest.fn()
       transform(ast, {
-        transforms: [transformIf],
+        nodeTransforms: [transformIf],
         onError: spy
       })
       expect(spy.mock.calls[0]).toMatchObject([
@@ -226,7 +226,7 @@ describe('compiler: v-if', () => {
       const ast2 = parse(`<div/><div v-else-if="foo"/>`)
       const spy2 = jest.fn()
       transform(ast2, {
-        transforms: [transformIf],
+        nodeTransforms: [transformIf],
         onError: spy2
       })
       expect(spy2.mock.calls[0]).toMatchObject([
@@ -239,7 +239,7 @@ describe('compiler: v-if', () => {
       const ast3 = parse(`<div/>foo<div v-else-if="foo"/>`)
       const spy3 = jest.fn()
       transform(ast3, {
-        transforms: [transformIf],
+        nodeTransforms: [transformIf],
         onError: spy3
       })
       expect(spy3.mock.calls[0]).toMatchObject([
index e6d8ef058248fd609e1b93b6012063f094203728..c8aa3a97beb4133c76f9fc5dffca646b551286ec 100644 (file)
@@ -1,5 +1,5 @@
 import { parse } from '../src/parse'
-import { transform, Transform } from '../src/transform'
+import { transform, NodeTransform } from '../src/transform'
 import { ElementNode, NodeTypes } from '../src/ast'
 import { ErrorCodes, createCompilerError } from '../src/errors'
 
@@ -10,12 +10,12 @@ describe('compiler: transform', () => {
     // manually store call arguments because context is mutable and shared
     // across calls
     const calls: any[] = []
-    const plugin: Transform = (node, context) => {
+    const plugin: NodeTransform = (node, context) => {
       calls.push([node, Object.assign({}, context)])
     }
 
     transform(ast, {
-      transforms: [plugin]
+      nodeTransforms: [plugin]
     })
 
     const div = ast.children[0] as ElementNode
@@ -48,7 +48,7 @@ describe('compiler: transform', () => {
 
   test('context.replaceNode', () => {
     const ast = parse(`<div/><span/>`)
-    const plugin: Transform = (node, context) => {
+    const plugin: NodeTransform = (node, context) => {
       if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
         // change the node to <p>
         context.replaceNode(
@@ -67,7 +67,7 @@ describe('compiler: transform', () => {
     }
     const spy = jest.fn(plugin)
     transform(ast, {
-      transforms: [spy]
+      nodeTransforms: [spy]
     })
 
     expect(ast.children.length).toBe(2)
@@ -85,14 +85,14 @@ describe('compiler: transform', () => {
     const c1 = ast.children[0]
     const c2 = ast.children[2]
 
-    const plugin: Transform = (node, context) => {
+    const plugin: NodeTransform = (node, context) => {
       if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
         context.removeNode()
       }
     }
     const spy = jest.fn(plugin)
     transform(ast, {
-      transforms: [spy]
+      nodeTransforms: [spy]
     })
 
     expect(ast.children.length).toBe(2)
@@ -111,7 +111,7 @@ describe('compiler: transform', () => {
     const c1 = ast.children[0]
     const c2 = ast.children[2]
 
-    const plugin: Transform = (node, context) => {
+    const plugin: NodeTransform = (node, context) => {
       if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
         context.removeNode()
         // remove previous sibling
@@ -120,7 +120,7 @@ describe('compiler: transform', () => {
     }
     const spy = jest.fn(plugin)
     transform(ast, {
-      transforms: [spy]
+      nodeTransforms: [spy]
     })
 
     expect(ast.children.length).toBe(1)
@@ -138,7 +138,7 @@ describe('compiler: transform', () => {
     const c1 = ast.children[0]
     const d1 = ast.children[1]
 
-    const plugin: Transform = (node, context) => {
+    const plugin: NodeTransform = (node, context) => {
       if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
         context.removeNode()
         // remove next sibling
@@ -147,7 +147,7 @@ describe('compiler: transform', () => {
     }
     const spy = jest.fn(plugin)
     transform(ast, {
-      transforms: [spy]
+      nodeTransforms: [spy]
     })
 
     expect(ast.children.length).toBe(1)
@@ -163,14 +163,14 @@ describe('compiler: transform', () => {
   test('onError option', () => {
     const ast = parse(`<div/>`)
     const loc = ast.children[0].loc.start
-    const plugin: Transform = (node, context) => {
-      context.emitError(
+    const plugin: NodeTransform = (node, context) => {
+      context.onError(
         createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc.start)
       )
     }
     const spy = jest.fn()
     transform(ast, {
-      transforms: [plugin],
+      nodeTransforms: [plugin],
       onError: spy
     })
     expect(spy.mock.calls[0]).toMatchObject([
index 7c3b7a35e7a15f54b924addfc8d9841ad6abf337..d97b976ac65ad084abdb0eb1bc6f1b2c47fec18e 100644 (file)
@@ -73,8 +73,7 @@ export interface ElementNode extends Node {
   tag: string
   tagType: ElementTypes
   isSelfClosing: boolean
-  attrs: AttributeNode[]
-  directives: DirectiveNode[]
+  props: Array<AttributeNode | DirectiveNode>
   children: ChildNode[]
   codegenNode: CallExpression | undefined
 }
@@ -161,3 +160,64 @@ export interface ArrayExpression extends Node {
   type: NodeTypes.ARRAY_EXPRESSION
   elements: Array<CodegenNode>
 }
+
+export function createArrayExpression(
+  elements: ArrayExpression['elements'],
+  loc: SourceLocation
+): ArrayExpression {
+  return {
+    type: NodeTypes.ARRAY_EXPRESSION,
+    loc,
+    elements
+  }
+}
+
+export function createObjectExpression(
+  properties: Property[],
+  loc: SourceLocation
+): ObjectExpression {
+  return {
+    type: NodeTypes.OBJECT_EXPRESSION,
+    loc,
+    properties
+  }
+}
+
+export function createObjectProperty(
+  key: ExpressionNode,
+  value: ExpressionNode,
+  loc: SourceLocation
+): Property {
+  return {
+    type: NodeTypes.PROPERTY,
+    loc,
+    key,
+    value
+  }
+}
+
+export function createExpression(
+  content: string,
+  isStatic: boolean,
+  loc: SourceLocation
+): ExpressionNode {
+  return {
+    type: NodeTypes.EXPRESSION,
+    loc,
+    content,
+    isStatic
+  }
+}
+
+export function createCallExpression(
+  callee: string,
+  args: CallExpression['arguments'],
+  loc: SourceLocation
+): CallExpression {
+  return {
+    type: NodeTypes.CALL_EXPRESSION,
+    loc,
+    callee,
+    arguments: args
+  }
+}
index 5467715fdd6570f4cbae0dea7371a0b2e3edb520..6f6959b6b592282e95fbf89f6bbdd103d3675fb2 100644 (file)
@@ -65,7 +65,8 @@ export const enum ErrorCodes {
   X_ELSE_IF_NO_ADJACENT_IF,
   X_ELSE_NO_ADJACENT_IF,
   X_FOR_NO_EXPRESSION,
-  X_FOR_MALFORMED_EXPRESSION
+  X_FOR_MALFORMED_EXPRESSION,
+  X_V_BIND_NO_EXPRESSION
 }
 
 export const errorMessages: { [code: number]: string } = {
index 3950c5cbfe6ad009ae074ae25019a9f2206fdae2..62fc7317c02909ba578808371e6a7d2bdd629505 100644 (file)
@@ -14,10 +14,14 @@ export function compile(
 
   transform(ast, {
     ...options,
-    transforms: [
+    nodeTransforms: [
       // TODO include built-in core transforms
-      ...(options.transforms || []) // user transforms
-    ]
+      ...(options.nodeTransforms || []) // user transforms
+    ],
+    directiveTransforms: {
+      // TODO include built-in directive transforms
+      ...(options.directiveTransforms || {}) // user transforms
+    }
   })
 
   return generate(ast, options)
@@ -27,11 +31,11 @@ export function compile(
 export { parse, ParserOptions, TextModes } from './parse'
 export {
   transform,
-  createDirectiveTransform,
+  createStructuralDirectiveTransform,
   TransformOptions,
   TransformContext,
-  Transform,
-  DirectiveTransform
+  NodeTransform as Transform,
+  StructuralDirectiveTransform
 } from './transform'
 export {
   generate,
@@ -41,3 +45,6 @@ export {
 } from './codegen'
 export { ErrorCodes, CompilerError, createCompilerError } from './errors'
 export * from './ast'
+
+// debug
+export { prepareElementForCodegen } from './transforms/element'
index 0a9521ad5bb2898ef747145ddd58a2e0c111d7cc..73aa22f238ade247ddceeec53d341ec77a5ff744 100644 (file)
@@ -376,8 +376,7 @@ function parseTag(
   const start = getCursor(context)
   const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
   const tag = match[1]
-  const attrs = []
-  const directives = []
+  const props = []
   const ns = context.options.getNamespace(tag, parent)
 
   advanceBy(context, match[0].length)
@@ -402,11 +401,7 @@ function parseTag(
 
     const attr = parseAttribute(context, attributeNames)
     if (type === TagType.Start) {
-      if (attr.type === NodeTypes.DIRECTIVE) {
-        directives.push(attr)
-      } else {
-        attrs.push(attr)
-      }
+      props.push(attr)
     }
 
     if (/^[^\t\r\n\f />]/.test(context.source)) {
@@ -438,11 +433,11 @@ function parseTag(
     ns,
     tag,
     tagType,
-    attrs,
-    directives,
+    props,
     isSelfClosing,
     children: [],
-    loc: getSelection(context, start)
+    loc: getSelection(context, start),
+    codegenNode: undefined // to be created during transform phase
   }
 }
 
index e200e1270e10ef2dc1c5bbe4747b9a3443f2419d..4e3fda5ec5fdd147430356b7b41881356bba50c6 100644 (file)
@@ -4,27 +4,45 @@ import {
   ParentNode,
   ChildNode,
   ElementNode,
-  DirectiveNode
+  DirectiveNode,
+  Property
 } from './ast'
 import { isString } from '@vue/shared'
 import { CompilerError, defaultOnError } from './errors'
 
-export type Transform = (node: ChildNode, context: TransformContext) => void
+// There are two types of transforms:
+//
+// - NodeTransform:
+//   Transforms that operate directly on a ChildNode. NodeTransforms may mutate,
+//   replace or remove the node being processed.
+export type NodeTransform = (node: ChildNode, context: TransformContext) => void
 
+// - DirectiveTransform:
+//   Transforms that handles a single directive attribute on an element.
+//   It translates the raw directive into actual props for the VNode.
 export type DirectiveTransform = (
+  dir: DirectiveNode,
+  context: TransformContext
+) => {
+  props: Property | Property[]
+  needRuntime: boolean
+}
+
+// A structural directive transform is a techically a NodeTransform;
+// Only v-if and v-for fall into this category.
+export type StructuralDirectiveTransform = (
   node: ElementNode,
   dir: DirectiveNode,
   context: TransformContext
-) => false | void
+) => void
 
 export interface TransformOptions {
-  transforms?: Transform[]
+  nodeTransforms?: NodeTransform[]
+  directiveTransforms?: { [name: string]: DirectiveTransform }
   onError?: (error: CompilerError) => void
 }
 
-export interface TransformContext {
-  transforms: Transform[]
-  emitError: (error: CompilerError) => void
+export interface TransformContext extends Required<TransformOptions> {
   parent: ParentNode
   ancestors: ParentNode[]
   childIndex: number
@@ -44,8 +62,9 @@ function createTransformContext(
   options: TransformOptions
 ): TransformContext {
   const context: TransformContext = {
-    transforms: options.transforms || [],
-    emitError: options.onError || defaultOnError,
+    nodeTransforms: options.nodeTransforms || [],
+    directiveTransforms: options.directiveTransforms || {},
+    onError: options.onError || defaultOnError,
     parent: root,
     ancestors: [],
     childIndex: 0,
@@ -109,9 +128,9 @@ function traverseNode(
   ancestors: ParentNode[]
 ) {
   // apply transform plugins
-  const { transforms } = context
-  for (let i = 0; i < transforms.length; i++) {
-    const plugin = transforms[i]
+  const { nodeTransforms } = context
+  for (let i = 0; i < nodeTransforms.length; i++) {
+    const plugin = nodeTransforms[i]
     plugin(node, context)
     if (!context.currentNode) {
       return
@@ -135,34 +154,27 @@ function traverseNode(
   }
 }
 
-const identity = <T>(_: T): T => _
-
-export function createDirectiveTransform(
+export function createStructuralDirectiveTransform(
   name: string | RegExp,
-  fn: DirectiveTransform
-): Transform {
+  fn: StructuralDirectiveTransform
+): NodeTransform {
   const matches = isString(name)
     ? (n: string) => n === name
     : (n: string) => name.test(n)
 
   return (node, context) => {
     if (node.type === NodeTypes.ELEMENT) {
-      const dirs = node.directives
-      let didRemove = false
-      for (let i = 0; i < dirs.length; i++) {
-        if (matches(dirs[i].name)) {
-          const res = fn(node, dirs[i], context)
-          // Directives are removed after transformation by default. A transform
-          // returning false means the directive should not be removed.
-          if (res !== false) {
-            ;(dirs as any)[i] = undefined
-            didRemove = true
-          }
+      const { props } = node
+      for (let i = 0; i < props.length; i++) {
+        const prop = props[i]
+        if (prop.type === NodeTypes.DIRECTIVE && matches(prop.name)) {
+          fn(node, prop, context)
+          // structural directives are removed after being processed
+          // to avoid infinite recursion
+          props.splice(i, 1)
+          i--
         }
       }
-      if (didRemove) {
-        node.directives = dirs.filter(identity)
-      }
     }
   }
 }
index 263af75fed2bb0fad192fc5674328bbb1f1e8625..780b7729f39f02601bc6fd493af5495789c14e9c 100644 (file)
@@ -1,29 +1,43 @@
-import { Transform, TransformContext } from '../transform'
+import { NodeTransform, TransformContext } from '../transform'
 import {
   NodeTypes,
   ElementTypes,
   CallExpression,
   ObjectExpression,
-  ElementNode
+  ElementNode,
+  DirectiveNode,
+  ExpressionNode,
+  ArrayExpression,
+  createCallExpression,
+  createArrayExpression,
+  createObjectProperty,
+  createExpression,
+  createObjectExpression
 } from '../ast'
+import { isArray } from '@vue/shared'
+import { createCompilerError, ErrorCodes } from '../errors'
 
 // generate a JavaScript AST for this element's codegen
-export const prepareElementForCodegen: Transform = (node, context) => {
+export const prepareElementForCodegen: NodeTransform = (node, context) => {
   if (node.type === NodeTypes.ELEMENT) {
     if (
       node.tagType === ElementTypes.ELEMENT ||
       node.tagType === ElementTypes.COMPONENT
     ) {
       const isComponent = node.tagType === ElementTypes.ELEMENT
-      const hasProps = node.attrs.length > 0 || node.directives.length > 0
+      const hasProps = node.props.length > 0
       const hasChildren = node.children.length > 0
+      let runtimeDirectives: DirectiveNode[] | undefined
 
       const args: CallExpression['arguments'] = [
+        // TODO inject resolveComponent dep to root
         isComponent ? node.tag : `"${node.tag}"`
       ]
       // props
       if (hasProps) {
-        args.push(buildProps(node))
+        const { props, directives } = buildProps(node, context)
+        args.push(props)
+        runtimeDirectives = directives
       }
       // children
       if (hasChildren) {
@@ -34,53 +48,155 @@ export const prepareElementForCodegen: Transform = (node, context) => {
         args.push(isComponent ? buildSlots(node, context) : node.children)
       }
 
-      node.codegenNode = {
-        type: NodeTypes.CALL_EXPRESSION,
-        loc: node.loc,
-        callee: `h`,
-        arguments: args
+      const { loc } = node
+      const vnode = createCallExpression(`h`, args, loc)
+
+      if (runtimeDirectives) {
+        node.codegenNode = createCallExpression(
+          `applyDirectives`,
+          [
+            vnode,
+            createArrayExpression(
+              runtimeDirectives.map(dir => {
+                return createDirectiveArgs(dir, context)
+              }),
+              loc
+            )
+          ],
+          loc
+        )
+      } else {
+        node.codegenNode = vnode
       }
+    } else if (node.tagType === ElementTypes.SLOT) {
+      // <slot [name="xxx"]/>
+      // TODO
+    } else if (node.tagType === ElementTypes.TEMPLATE) {
+      // do nothing
     }
   }
 }
 
-function buildProps({ loc, attrs }: ElementNode): ObjectExpression {
-  return {
-    type: NodeTypes.OBJECT_EXPRESSION,
-    loc,
-    // At this stage we will only process static attrs. Directive bindings will
-    // be handled by their respective transforms which adds/modifies the props.
-    properties: attrs.map(({ name, value, loc }) => {
-      return {
-        type: NodeTypes.PROPERTY,
-        loc,
-        key: {
-          type: NodeTypes.EXPRESSION,
-          loc,
-          content: name,
-          isStatic: true
-        },
-        value: {
-          type: NodeTypes.EXPRESSION,
-          loc: value ? value.loc : loc,
-          content: value ? value.content : '',
-          isStatic: true
+function buildProps(
+  { loc, props }: ElementNode,
+  context: TransformContext
+): {
+  props: ObjectExpression | CallExpression
+  directives: DirectiveNode[]
+} {
+  let properties: ObjectExpression['properties'] = []
+  const mergeArgs: Array<ObjectExpression | ExpressionNode> = []
+  const runtimeDirectives: DirectiveNode[] = []
+
+  for (let i = 0; i < props.length; i++) {
+    // static attribute
+    const prop = props[i]
+    if (prop.type === NodeTypes.ATTRIBUTE) {
+      const { loc, name, value } = prop
+      properties.push(
+        createObjectProperty(
+          createExpression(name, true, loc),
+          createExpression(
+            value ? value.content : '',
+            true,
+            value ? value.loc : loc
+          ),
+          loc
+        )
+      )
+    } else {
+      // directives
+      // special case for v-bind with no argument
+      if (prop.name === 'bind' && !prop.arg) {
+        if (prop.exp) {
+          if (properties.length) {
+            mergeArgs.push(createObjectExpression(properties, loc))
+            properties = []
+          }
+          mergeArgs.push(prop.exp)
+        } else {
+          context.onError(
+            createCompilerError(
+              ErrorCodes.X_V_BIND_NO_EXPRESSION,
+              prop.loc.start
+            )
+          )
+        }
+        continue
+      }
+
+      const directiveTransform = context.directiveTransforms[prop.name]
+      if (directiveTransform) {
+        const { props, needRuntime } = directiveTransform(prop, context)
+        if (isArray(props)) {
+          properties.push(...props)
+        } else {
+          properties.push(props)
+        }
+        if (needRuntime) {
+          runtimeDirectives.push(prop)
         }
+      } else {
+        // no built-in transform, this is a user custom directive.
+        runtimeDirectives.push(prop)
       }
-    })
+    }
+  }
+
+  let ret: ObjectExpression | CallExpression
+
+  // has v-bind="object", wrap with mergeProps
+  if (mergeArgs.length) {
+    if (properties.length) {
+      mergeArgs.push(createObjectExpression(properties, loc))
+    }
+    if (mergeArgs.length > 1) {
+      ret = createCallExpression(`mergeProps`, mergeArgs, loc)
+    } else {
+      // single v-bind with nothing else - no need for a mergeProps call
+      ret = createObjectExpression(properties, loc)
+    }
+  } else {
+    ret = createObjectExpression(properties, loc)
+  }
+
+  return {
+    props: ret,
+    directives: runtimeDirectives
   }
 }
 
+function createDirectiveArgs(
+  dir: DirectiveNode,
+  context: TransformContext
+): ArrayExpression {
+  // TODO inject resolveDirective dep to root
+  const dirArgs: ArrayExpression['elements'] = [dir.name]
+  const { loc } = dir
+  if (dir.exp) dirArgs.push(dir.exp)
+  if (dir.arg) dirArgs.push(dir.arg)
+  if (Object.keys(dir.modifiers).length) {
+    dirArgs.push(
+      createObjectExpression(
+        dir.modifiers.map(modifier =>
+          createObjectProperty(
+            createExpression(modifier, true, loc),
+            createExpression(`true`, false, loc),
+            loc
+          )
+        ),
+        loc
+      )
+    )
+  }
+  return createArrayExpression(dirArgs, dir.loc)
+}
+
 function buildSlots(
   { loc, children }: ElementNode,
   context: TransformContext
 ): ObjectExpression {
-  const slots: ObjectExpression = {
-    type: NodeTypes.OBJECT_EXPRESSION,
-    loc,
-    properties: []
-  }
-
+  const slots = createObjectExpression([], loc)
   // TODO
 
   return slots
index 668fbd124a5cf8fb6699c7eb56d63a06b3dcaf21..8b8e79e2d1b50adab09c974ceff8b5536a9d7921 100644 (file)
@@ -1,5 +1,5 @@
-import { createDirectiveTransform } from '../transform'
-import { NodeTypes, ExpressionNode } from '../ast'
+import { createStructuralDirectiveTransform } from '../transform'
+import { NodeTypes, ExpressionNode, createExpression } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { getInnerRange } from '../utils'
 
@@ -7,7 +7,7 @@ const forAliasRE = /([\s\S]*?)(?:(?<=\))|\s+)(?:in|of)\s+([\s\S]*)/
 const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
 const stripParensRE = /^\(|\)$/g
 
-export const transformFor = createDirectiveTransform(
+export const transformFor = createStructuralDirectiveTransform(
   'for',
   (node, dir, context) => {
     if (dir.exp) {
@@ -27,7 +27,7 @@ export const transformFor = createDirectiveTransform(
           children: [node]
         })
       } else {
-        context.emitError(
+        context.onError(
           createCompilerError(
             ErrorCodes.X_FOR_MALFORMED_EXPRESSION,
             dir.loc.start
@@ -35,7 +35,7 @@ export const transformFor = createDirectiveTransform(
         )
       }
     } else {
-      context.emitError(
+      context.onError(
         createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start)
       )
     }
@@ -118,11 +118,10 @@ function maybeCreateExpression(
   node: ExpressionNode
 ): ExpressionNode | undefined {
   if (alias) {
-    return {
-      type: NodeTypes.EXPRESSION,
-      loc: getInnerRange(node.loc, alias.offset, alias.content.length),
-      content: alias.content,
-      isStatic: false
-    }
+    return createExpression(
+      alias.content,
+      false,
+      getInnerRange(node.loc, alias.offset, alias.content.length)
+    )
   }
 }
index cbdf8be9583e74cf1055629909f7bb2094b35ab5..494823c9f71f43dbd3a5d33692f5c9e2bd631a39 100644 (file)
@@ -1,4 +1,4 @@
-import { createDirectiveTransform } from '../transform'
+import { createStructuralDirectiveTransform } from '../transform'
 import {
   NodeTypes,
   ElementTypes,
@@ -8,7 +8,7 @@ import {
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 
-export const transformIf = createDirectiveTransform(
+export const transformIf = createStructuralDirectiveTransform(
   /^(if|else|else-if)$/,
   (node, dir, context) => {
     if (dir.name === 'if') {
@@ -38,7 +38,7 @@ export const transformIf = createDirectiveTransform(
           }
           sibling.branches.push(branch)
         } else {
-          context.emitError(
+          context.onError(
             createCompilerError(
               dir.name === 'else'
                 ? ErrorCodes.X_ELSE_NO_ADJACENT_IF
diff --git a/packages/compiler-core/src/transforms/vPre.ts b/packages/compiler-core/src/transforms/vPre.ts
deleted file mode 100644 (file)
index 70b786d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-// TODO
index 7b636355ba5cb2d77d6d2be760ae6acf4d06b5b8..6c09e0bcf6730e30b234d31664d13acbada5ebdb 100644 (file)
@@ -6,6 +6,8 @@ import {
 import { parserOptionsMinimal } from './parserOptionsMinimal'
 import { parserOptionsStandard } from './parserOptionsStandard'
 
+export * from '@vue/compiler-core'
+
 export function compile(
   template: string,
   options: CompilerOptions = {}
@@ -13,11 +15,9 @@ export function compile(
   return baseCompile(template, {
     ...options,
     ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
-    transforms: [
-      // TODO include DOM-specific transforms
-      ...(options.transforms || []) // extra user transforms
-    ]
+    directiveTransforms: {
+      // TODO include DOM-specific directiveTransforms
+      ...(options.directiveTransforms || {})
+    }
   })
 }
-
-export * from '@vue/compiler-core'