瀏覽代碼

improve codegen for smaller generated code size

Evan You 10 年之前
父節點
當前提交
6fe268874c

+ 1 - 0
.flowconfig

@@ -16,4 +16,5 @@ module.name_mapper='^core/\(.*\)$' -> '<PROJECT_ROOT>/src/core/\1'
 module.name_mapper='^shared/\(.*\)$' -> '<PROJECT_ROOT>/src/shared/\1'
 module.name_mapper='^web/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/web/\1'
 module.name_mapper='^server/\(.*\)$' -> '<PROJECT_ROOT>/src/server/\1'
+module.name_mapper='^entries/\(.*\)$' -> '<PROJECT_ROOT>/src/entries/\1'
 module.name_mapper='^entities$' -> '<PROJECT_ROOT>/src/compiler/parser/entity-decoder'

+ 2 - 1
build/alias.js

@@ -6,5 +6,6 @@ module.exports = {
   core: path.resolve(__dirname, '../src/core'),
   shared: path.resolve(__dirname, '../src/shared'),
   web: path.resolve(__dirname, '../src/platforms/web'),
-  server: path.resolve(__dirname, '../src/server')
+  server: path.resolve(__dirname, '../src/server'),
+  entries: path.resolve(__dirname, '../src/entries')
 }

+ 16 - 8
flow/component.js

@@ -70,28 +70,36 @@ declare interface Component {
   // rendering
   _render: () => VNode;
   __patch__: (a: Element | VNode | void, b: VNode) => Element;
-  __r__: (
+  // renderElementWithChildren
+  _h: (
     vnode?: VNode,
     children?: VNodeChildren
   ) => VNode | void;
-  __s__: (
+  // renderElement
+  _e: (
     tag?: string | Component | Object,
     data?: Object,
     namespace?: string
   ) => VNode | void;
-  __t__: (
+  // renderText
+  _t: (
     str?: string
   ) => string;
-  __m__: (
+  // renderStaticTree
+  _m: (
     index?: number
   ) => Object | void;
-  __toString__: (value: any) => string;
-  __resolveFilter__: (id: string) => Function;
-  __renderList__: (
+  // toString
+  _s: (value: any) => string;
+  // resolveFilter
+  _f: (id: string) => Function;
+  // renderList
+  _l: (
     val: any,
     render: Function
   ) => ?Array<VNode>;
-  __registerRef__: (
+  // registerRef
+  _r: (
     key: string,
     ref: Component | Element,
     vFor: boolean,

+ 38 - 21
src/compiler/codegen.js

@@ -33,10 +33,10 @@ export function generate (
   platformModules = options.modules || []
   platformDirectives = options.directives || {}
   isPlatformReservedTag = options.isReservedTag || (() => false)
-  const code = ast ? genElement(ast) : '__r__(__s__("div"))'
+  const code = ast ? genElement(ast) : '_h(_e("div"))'
   staticRenderFns = prevStaticRenderFns
   return {
-    render: `with (this) { return ${code}}`,
+    render: `with(this){return ${code}}`,
     staticRenderFns: currentStaticRenderFns
   }
 }
@@ -47,7 +47,7 @@ function genElement (el: ASTElement): string {
   } else if (el.if) {
     return genIf(el)
   } else if (el.tag === 'template' && !el.slotTarget) {
-    return genChildren(el)
+    return genChildren(el) || 'void 0'
   } else if (el.tag === 'render') {
     return genRender(el)
   } else if (el.tag === 'slot') {
@@ -55,17 +55,23 @@ function genElement (el: ASTElement): string {
   } else if (el.component) {
     return genComponent(el)
   } else {
+    const data = genData(el)
     // if the element is potentially a component,
     // wrap its children as a thunk.
-    const children = el.inlineTemplate
-      ? 'undefined'
-      : genChildren(el, !isPlatformReservedTag(el.tag) /* asThunk */)
-    const namespace = el.ns ? `,'${el.ns}'` : ''
-    const code = `__r__(__s__('${el.tag}', ${genData(el)}${namespace}), ${children})`
+    const children = !el.inlineTemplate
+      ? genChildren(el, !el.ns && !isPlatformReservedTag(el.tag) /* asThunk */)
+      : null
+    const code = `_h(_e('${el.tag}'${
+      data ? `,${data}` : el.ns ? ',void 0' : '' // data
+    }${
+      el.ns ? `,'${el.ns}'` : '' // namespace
+    })${
+      children ? `,${children}` : '' // children
+    })`
     if (el.staticRoot) {
       // hoist static sub-trees out
       staticRenderFns.push(`with(this){return ${code}}`)
-      return `__m__(${staticRenderFns.length - 1})`
+      return `_m(${staticRenderFns.length - 1})`
     } else {
       return code
     }
@@ -75,13 +81,13 @@ function genElement (el: ASTElement): string {
 function genIf (el: ASTElement): string {
   const exp = el.if
   el.if = null // avoid recursion
-  return `(${exp}) ? ${genElement(el)} : ${genElse(el)}`
+  return `(${exp})?${genElement(el)}:${genElse(el)}`
 }
 
 function genElse (el: ASTElement): string {
   return el.elseBlock
     ? genElement(el.elseBlock)
-    : 'null'
+    : 'void 0'
 }
 
 function genFor (el: ASTElement): string {
@@ -89,15 +95,15 @@ function genFor (el: ASTElement): string {
   const alias = el.alias
   const iterator = el.iterator
   el.for = null // avoid recursion
-  return `(${exp})&&__renderList__((${exp}), ` +
+  return `(${exp})&&_l((${exp}),` +
     `function(${alias},$index,${iterator || '$key'}){` +
       `return ${genElement(el)}` +
     '})'
 }
 
-function genData (el: ASTElement): string {
+function genData (el: ASTElement): string | void {
   if (el.plain) {
-    return 'undefined'
+    return
   }
 
   let data = '{'
@@ -199,9 +205,9 @@ function genDirectives (el: ASTElement): string | void {
   }
 }
 
-function genChildren (el: ASTElement, asThunk?: boolean): string {
+function genChildren (el: ASTElement, asThunk?: boolean): string | void {
   if (!el.children.length) {
-    return 'undefined'
+    return
   }
   const code = '[' + el.children.map(genNode).join(',') + ']'
   return asThunk
@@ -220,20 +226,31 @@ function genNode (node: ASTNode) {
 function genText (text: ASTText | ASTExpression): string {
   return text.type === 2
     ? `(${text.expression})`
-    : '__t__(' + JSON.stringify(text.text) + ')'
+    : '_t(' + JSON.stringify(text.text) + ')'
 }
 
 function genRender (el: ASTElement): string {
-  return `${el.renderMethod}(${el.renderArgs || 'null'},${genChildren(el)})`
+  const children = genChildren(el)
+  return `${el.renderMethod}(${
+    el.renderArgs || ''
+  }${
+    children ? (el.renderArgs ? ',' : '') + children : ''
+  })`
 }
 
 function genSlot (el: ASTElement): string {
-  const name = el.slotName || '"default"'
-  return `($slots[${name}] || ${genChildren(el)})`
+  const slot = `$slots[${el.slotName || '"default"'}]`
+  const children = genChildren(el)
+  return children
+    ? `(${slot}||${children})`
+    : slot
 }
 
 function genComponent (el: ASTElement): string {
-  return `__r__(__s__(${el.component}, ${genData(el)}), ${genChildren(el, true)})`
+  const children = genChildren(el, true)
+  return `_h(_e(${el.component},${genData(el)})${
+    children ? `,${children}` : ''
+  })`
 }
 
 function genProps (props: Array<{ name: string, value: string }>): string {

+ 4 - 4
src/compiler/directives/ref.js

@@ -12,8 +12,8 @@ export function ref (el: ASTElement, dir: ASTDirective) {
     }
     parent = parent.parent
   }
-  // __registerRef__(name, ref, vFor?, remove?)
-  const code = `__registerRef__("${dir.arg}", n1.child || n1.elm, ${isFor ? 'true' : 'false'}`
-  addHook(el, 'insert', `${code}), false`)
-  addHook(el, 'destroy', `${code}, true)`)
+  // registerRef: _r(name, ref, vFor?, remove?)
+  const code = `_r("${dir.arg}",n1.child||n1.elm,${isFor ? 'true' : 'false'}`
+  addHook(el, 'insert', `${code},false`)
+  addHook(el, 'destroy', `${code},true)`)
 }

+ 3 - 2
src/compiler/parser/filter-parser.js

@@ -68,10 +68,11 @@ export function parseFilters (exp: string): string {
 function wrapFilter (exp: string, filter: string): string {
   const i = filter.indexOf('(')
   if (i < 0) {
-    return `__resolveFilter__("${filter}")(${exp})`
+    // _f: resolveFilter
+    return `_f("${filter}")(${exp})`
   } else {
     const name = filter.slice(0, i)
     const args = filter.slice(i + 1)
-    return `__resolveFilter__("${name}")(${exp},${args}`
+    return `_f("${name}")(${exp},${args}`
   }
 }

+ 3 - 0
src/compiler/parser/index.js

@@ -198,6 +198,9 @@ function processRawAttrs (el) {
         value: JSON.stringify(el.attrsList[i].value)
       }
     }
+  } else if (!el.pre) {
+    // non root node in pre blocks with no attributes
+    el.plain = true
   }
 }
 

+ 1 - 1
src/compiler/parser/text-parser.js

@@ -31,7 +31,7 @@ export function parseText (
     }
     // tag token
     const exp = parseFilters(match[1].trim())
-    tokens.push(`__toString__(${exp})`)
+    tokens.push(`_s(${exp})`)
     lastIndex = index + match[0].length
   }
   if (lastIndex < text.length) {

+ 9 - 9
src/core/instance/render.js

@@ -30,7 +30,7 @@ export function initRender (vm: Component) {
     children?: VNodeChildren,
     namespace?: string
   ) {
-    return this.__r__(this.__s__(tag, data, namespace), children)
+    return this._h(this._e(tag, data, namespace), children)
   }, vm)
   if (vm.$options.el) {
     vm.$mount(vm.$options.el)
@@ -66,22 +66,22 @@ export function renderMixin (Vue: Class<Component>) {
   }
 
   // shorthands used in render functions
-  Vue.prototype.__r__ = renderElementWithChildren
-  Vue.prototype.__s__ = renderElement
-  Vue.prototype.__t__ = renderText
-  Vue.prototype.__m__ = renderStatic
+  Vue.prototype._h = renderElementWithChildren
+  Vue.prototype._e = renderElement
+  Vue.prototype._t = renderText
+  Vue.prototype._m = renderStatic
 
   // toString for mustaches
-  Vue.prototype.__toString__ = renderString
+  Vue.prototype._s = renderString
 
   // filter resolution helper
   const identity = _ => _
-  Vue.prototype.__resolveFilter__ = function (id) {
+  Vue.prototype._f = function (id) {
     return resolveAsset(this.$options, 'filters', id, true) || identity
   }
 
   // render v-for
-  Vue.prototype.__renderList__ = function (
+  Vue.prototype._l = function (
     val: any,
     render: () => VNode
   ): ?Array<VNode> {
@@ -108,7 +108,7 @@ export function renderMixin (Vue: Class<Component>) {
   }
 
   // register ref
-  Vue.prototype.__registerRef__ = function (
+  Vue.prototype._r = function (
     key: string,
     ref: Vue | Element,
     vFor: boolean,

+ 2 - 8
src/entries/web-compiler.js

@@ -1,6 +1,6 @@
 /* @flow */
 
-import { extend } from 'shared/util'
+import { extend, genStaticKeys } from 'shared/util'
 import { warn } from 'core/util/debug'
 import { compile as baseCompile } from 'compiler/index'
 import modules from 'web/compiler/modules/index'
@@ -33,7 +33,7 @@ type CompiledFunctions = {
 const cache1: { [key: string]: CompiledFunctions } = Object.create(null)
 const cache2: { [key: string]: CompiledFunctions } = Object.create(null)
 
-const baseOptions: CompilerOptions = {
+export const baseOptions: CompilerOptions = {
   expectHTML: true,
   preserveWhitespace: true,
   modules,
@@ -73,9 +73,3 @@ export function compileToFunctions (
   }
   return (cache[template] = res)
 }
-
-function genStaticKeys (modules: Array<ModuleOptions>): string {
-  return modules.reduce((keys, m) => {
-    return keys.concat(m.staticKeys || [])
-  }, []).join(',')
-}

+ 1 - 1
src/platforms/web/compiler/directives/html.js

@@ -4,6 +4,6 @@ import { addProp } from 'compiler/helpers'
 
 export default function html (el: ASTElement, dir: ASTDirective) {
   if (dir.value) {
-    addProp(el, 'innerHTML', `__toString__(${dir.value})`)
+    addProp(el, 'innerHTML', `_s(${dir.value})`)
   }
 }

+ 1 - 1
src/platforms/web/compiler/directives/text.js

@@ -4,6 +4,6 @@ import { addProp } from 'compiler/helpers'
 
 export default function text (el: ASTElement, dir: ASTDirective) {
   if (dir.value) {
-    addProp(el, 'textContent', `__toString__(${dir.value})`)
+    addProp(el, 'textContent', `_s(${dir.value})`)
   }
 }

+ 9 - 0
src/shared/util.js

@@ -170,3 +170,12 @@ export function toObject (arr: Array<any>): Object {
  * Perform no operation.
  */
 export function noop () {}
+
+/**
+ * Generate a static keys string from compiler modules.
+ */
+export function genStaticKeys (modules: Array<ModuleOptions>): string {
+  return modules.reduce((keys, m) => {
+    return keys.concat(m.staticKeys || [])
+  }, []).join(',')
+}

+ 140 - 87
test/unit/modules/compiler/codegen.spec.js

@@ -1,143 +1,141 @@
 import { parse } from 'compiler/parser/index'
 import { optimize } from 'compiler/optimizer'
 import { generate } from 'compiler/codegen'
-import directives from 'web/compiler/directives/index'
-import { isReservedTag, isUnaryTag, mustUseProp, getTagNamespace } from 'web/util/index'
 import { isObject } from 'shared/util'
+import directives from 'web/compiler/directives/index'
+import { isReservedTag } from 'web/util/index'
+import { baseOptions } from 'entries/web-compiler'
 
-/* eslint-disable quotes */
-describe('codegen', () => {
-  const baseOptions = {
-    expectHTML: true,
-    preserveWhitespace: true,
-    directives,
-    isReservedTag,
-    isUnaryTag,
-    mustUseProp,
-    getTagNamespace
-  }
-
-  function assertCodegen (template, generatedCode, ...args) {
-    let staticRenderFnCodes = []
-    let generateOptions = baseOptions
-    let proc = null
-    let len = args.length
-    while (len--) {
-      const arg = args[len]
-      if (Array.isArray(arg)) {
-        staticRenderFnCodes = arg
-      } else if (isObject(arg)) {
-        generateOptions = arg
-      } else if (typeof arg === 'function') {
-        proc = arg
-      }
+function assertCodegen (template, generatedCode, ...args) {
+  let staticRenderFnCodes = []
+  let generateOptions = baseOptions
+  let proc = null
+  let len = args.length
+  while (len--) {
+    const arg = args[len]
+    if (Array.isArray(arg)) {
+      staticRenderFnCodes = arg
+    } else if (isObject(arg)) {
+      generateOptions = arg
+    } else if (typeof arg === 'function') {
+      proc = arg
     }
-
-    const ast = parse(template, baseOptions)
-    optimize(ast, baseOptions)
-    proc && proc(ast)
-    const res = generate(ast, generateOptions)
-    expect(res.render).toBe(generatedCode)
-    expect(res.staticRenderFns).toEqual(staticRenderFnCodes)
   }
+  const ast = parse(template, baseOptions)
+  optimize(ast, baseOptions)
+  proc && proc(ast)
+  const res = generate(ast, generateOptions)
+  expect(res.render).toBe(generatedCode)
+  expect(res.staticRenderFns).toEqual(staticRenderFnCodes)
+}
 
+/* eslint-disable quotes */
+describe('codegen', () => {
   it('generate directive', () => {
     assertCodegen(
       '<p v-custom1:arg1.modifire="value1" v-custom2><p>',
-      `with (this) { return __r__(__s__('p', {directives:[{name:"custom1",value:(value1),arg:"arg1",modifiers:{"modifire":true}},{name:"custom2",arg:"arg1"}]}), undefined)}`
+      `with(this){return _h(_e('p',{directives:[{name:"custom1",value:(value1),arg:"arg1",modifiers:{"modifire":true}},{name:"custom2",arg:"arg1"}]}))}`
     )
   })
 
   it('generate v-pre', () => {
     assertCodegen(
       '<div v-pre><p>hello world</p></div>',
-      'with (this) { return __m__(0)}',
-      [`with(this){return __r__(__s__('div', {pre:true}), [__r__(__s__('p', {}), [__t__("hello world")])])}`]
+      'with(this){return _m(0)}',
+      [`with(this){return _h(_e('div',{pre:true}),[_h(_e('p'),[_t("hello world")])])}`]
     )
   })
 
   it('generate v-for directive', () => {
     assertCodegen(
-      '<li v-for="item in items" track-by="uid"></li>',
-      `with (this) { return (items)&&__renderList__((items), function(item,$index,$key){return __r__(__s__('li', {key:uid}), undefined)})}`
+      '<li v-for="item in items" track-by="item.uid"></li>',
+      `with(this){return (items)&&_l((items),function(item,$index,$key){return _h(_e('li',{key:item.uid}))})}`
     )
   })
 
   it('generate v-if directive', () => {
     assertCodegen(
       '<p v-if="show">hello</p>',
-      `with (this) { return (show) ? __r__(__s__('p', undefined), [__t__("hello")]) : null}`
+      `with(this){return (show)?_h(_e('p'),[_t("hello")]):void 0}`
     )
   })
 
   it('generate v-else directive', () => {
     assertCodegen(
       '<div><p v-if="show">hello</p><p v-else>world</p></div>',
-      `with (this) { return __r__(__s__('div', undefined), [(show) ? __r__(__s__('p', undefined), [__t__("hello")]) : __r__(__s__('p', undefined), [__t__("world")])])}`
+      `with(this){return _h(_e('div'),[(show)?_h(_e('p'),[_t("hello")]):_h(_e('p'),[_t("world")])])}`
     )
   })
 
   it('generate v-ref directive', () => {
     assertCodegen(
       '<p v-ref:component1></p>',
-      `with (this) { return __r__(__s__('p', {hook:{"insert":function(n1,n2){__registerRef__("component1", n1.child || n1.elm, false), false},"destroy":function(n1,n2){__registerRef__("component1", n1.child || n1.elm, false, true)}}}), undefined)}`
+      `with(this){return _h(_e('p',{hook:{"insert":function(n1,n2){_r("component1",n1.child||n1.elm,false,false},"destroy":function(n1,n2){_r("component1",n1.child||n1.elm,false,true)}}}))}`
     )
   })
 
   it('generate v-ref directive on v-for', () => {
     assertCodegen(
       '<ul><li v-for="item in items" v-ref:component1></li></ul>',
-      `with (this) { return __r__(__s__('ul', undefined), [(items)&&__renderList__((items), function(item,$index,$key){return __r__(__s__('li', {hook:{"insert":function(n1,n2){__registerRef__("component1", n1.child || n1.elm, true), false},"destroy":function(n1,n2){__registerRef__("component1", n1.child || n1.elm, true, true)}}}), undefined)})])}`
+      `with(this){return _h(_e('ul'),[(items)&&_l((items),function(item,$index,$key){return _h(_e('li',{hook:{"insert":function(n1,n2){_r("component1",n1.child||n1.elm,true,false},"destroy":function(n1,n2){_r("component1",n1.child||n1.elm,true,true)}}}))})])}`
     )
   })
 
   it('generate template tag', () => {
     assertCodegen(
       '<template><p>hello world</p></template>',
-      `with (this) { return [__r__(__s__('p', undefined), [__t__("hello world")])]}`
+      `with(this){return [_h(_e('p'),[_t("hello world")])]}`
     )
   })
 
   it('generate svg tag', () => {
     assertCodegen(
       '<svg><text>hello world</text></svg>',
-      `with (this) { return __r__(__s__('svg', undefined,'svg'), function(){return [__r__(__s__('text', undefined,'svg'), function(){return [__t__("hello world")]})]})}`
+      `with(this){return _h(_e('svg',void 0,'svg'),[_h(_e('text',void 0,'svg'),[_t("hello world")])])}`
     )
   })
 
   it('generate render tag', () => {
     assertCodegen(
       '<render :method="onRender" :args="params"></render>',
-      `with (this) { return onRender(params,undefined)}`
+      `with(this){return onRender(params)}`
     )
   })
 
-  it('generate render tag that have childs', () => {
+  it('generate render tag that have children', () => {
     assertCodegen(
       '<render :method="onRender"><p>hello</p></render>',
-      `with (this) { return onRender(null,[__m__(0)])}`,
-      [`with(this){return __r__(__s__('p', undefined), [__t__("hello")])}`]
+      `with(this){return onRender([_m(0)])}`,
+      [`with(this){return _h(_e('p'),[_t("hello")])}`]
     )
   })
 
   it('generate single slot', () => {
     assertCodegen(
       '<slot></slot>',
-      `with (this) { return ($slots["default"] || undefined)}`
+      `with(this){return $slots["default"]}`
     )
   })
 
   it('generate named slot', () => {
     assertCodegen(
       '<slot name="one"></slot>',
-      `with (this) { return ($slots["one"] || undefined)}`
+      `with(this){return $slots["one"]}`
+    )
+  })
+
+  it('generate slot fallback content', () => {
+    assertCodegen(
+      '<slot><div>hi</div></slot>',
+      `with(this){return ($slots["default"]||[_m(0)])}`,
+      [`with(this){return _h(_e('div'),[_t("hi")])}`]
     )
   })
 
   it('generate slot target', () => {
     assertCodegen(
       '<p slot="one">hello world</p>',
-      `with (this) { return __r__(__s__('p', {slot:"one"}), [__t__("hello world")])}`
+      `with(this){return _h(_e('p',{slot:"one"}),[_t("hello world")])}`
     )
   })
 
@@ -145,137 +143,192 @@ describe('codegen', () => {
     // static
     assertCodegen(
       '<p class="class1">hello world</p>',
-      'with (this) { return __m__(0)}',
-      [`with(this){return __r__(__s__('p', {staticAttrs:{"class":"class1"}}), [__t__("hello world")])}`]
+      'with(this){return _m(0)}',
+      [`with(this){return _h(_e('p',{staticClass:"class1"}),[_t("hello world")])}`]
     )
     // dynamic
     assertCodegen(
       '<p :class="class1">hello world</p>',
-      `with (this) { return __r__(__s__('p', {attrs:{"class":class1}}), [__t__("hello world")])}`
+      `with(this){return _h(_e('p',{class:class1}),[_t("hello world")])}`
     )
   })
 
   it('generate style binding', () => {
     assertCodegen(
       '<p :style="error">hello world</p>',
-      `with (this) { return __r__(__s__('p', {attrs:{"style":error}}), [__t__("hello world")])}`
+      `with(this){return _h(_e('p',{style:error}),[_t("hello world")])}`
     )
   })
 
   it('generate transition', () => {
     assertCodegen(
       '<p transition="expand">hello world</p>',
-      'with (this) { return __m__(0)}',
-      [`with(this){return __r__(__s__('p', {staticAttrs:{"transition":"expand"}}), [__t__("hello world")])}`]
+      `with(this){return _h(_e('p',{transition:{definition:("expand"),appear:false}}),[_t("hello world")])}`
+    )
+  })
+
+  it('generate dynamic transition with transition on appear', () => {
+    assertCodegen(
+      '<p :transition="expand" transition-on-appear>hello world</p>',
+      `with(this){return _h(_e('p',{transition:{definition:(expand),appear:true}}),[_t("hello world")])}`
     )
   })
 
   it('generate v-show directive', () => {
     assertCodegen(
       '<p v-show="shown">hello world</p>',
-      `with (this) { return __r__(__s__('p', {directives:[{name:"show",value:(shown)}],show:true}), [__t__("hello world")])}`
+      `with(this){return _h(_e('p',{directives:[{name:"show",value:(shown)}],show:true}),[_t("hello world")])}`
     )
   })
 
   it('generate props with v-bind directive', () => {
     assertCodegen(
       '<p :value="msg">',
-      `with (this) { return __r__(__s__('p', {props:{"value":msg}}), undefined)}`
+      `with(this){return _h(_e('p',{props:{"value":msg}}))}`
     )
   })
 
   it('generate attrs with v-bind directive', () => {
     assertCodegen(
       '<input :name="field1">',
-      `with (this) { return __r__(__s__('input', {attrs:{"name":field1}}), undefined)}`
+      `with(this){return _h(_e('input',{attrs:{"name":field1}}))}`
     )
   })
 
   it('generate staticAttrs', () => {
     assertCodegen(
       '<input name="field1">',
-      `with (this) { return __m__(0)}`,
-      [`with(this){return __r__(__s__('input', {staticAttrs:{"name":"field1"}}), undefined)}`]
+      `with(this){return _m(0)}`,
+      [`with(this){return _h(_e('input',{staticAttrs:{"name":"field1"}}))}`]
     )
   })
 
   it('generate events with v-on directive', () => {
     assertCodegen(
       '<input @input="onInput">',
-      `with (this) { return __r__(__s__('input', {on:{"input":onInput}}), undefined)}`
+      `with(this){return _h(_e('input',{on:{"input":onInput}}))}`
     )
-    // keycode
+  })
+
+  it('generate events with keycode', () => {
     assertCodegen(
       '<input @input.enter="onInput">',
-      `with (this) { return __r__(__s__('input', {on:{"input":function($event){if($event.keyCode!==13)return;onInput($event)}}}), undefined)}`
+      `with(this){return _h(_e('input',{on:{"input":function($event){if($event.keyCode!==13)return;onInput($event)}}}))}`
     )
-    // multiple keycode
+    // multiple keycodes (delete)
     assertCodegen(
       '<input @input.delete="onInput">',
-      `with (this) { return __r__(__s__('input', {on:{"input":function($event){if($event.keyCode!==8&&$event.keyCode!==46)return;onInput($event)}}}), undefined)}`
+      `with(this){return _h(_e('input',{on:{"input":function($event){if($event.keyCode!==8&&$event.keyCode!==46)return;onInput($event)}}}))}`
+    )
+  })
+
+  it('generate events with modifiers', () => {
+    assertCodegen(
+      '<input @input.stop="onInput">',
+      `with(this){return _h(_e('input',{on:{"input":function($event){$event.stopPropagation();onInput($event)}}}))}`
     )
-    // inline statement
+    assertCodegen(
+      '<input @input.prevent="onInput">',
+      `with(this){return _h(_e('input',{on:{"input":function($event){$event.preventDefault();onInput($event)}}}))}`
+    )
+    assertCodegen(
+      '<input @input.self="onInput">',
+      `with(this){return _h(_e('input',{on:{"input":function($event){if($event.target !== $event.currentTarget)return;onInput($event)}}}))}`
+    )
+  })
+
+  it('generate events with multiple modifers', () => {
+    assertCodegen(
+      '<input @input.stop.prevent.self="onInput">',
+      `with(this){return _h(_e('input',{on:{"input":function($event){$event.stopPropagation();$event.preventDefault();if($event.target !== $event.currentTarget)return;onInput($event)}}}))}`
+    )
+  })
+
+  it('generate events with capture modifier', () => {
+    assertCodegen(
+      '<input @input.capture="onInput">',
+      `with(this){return _h(_e('input',{on:{"!input":function($event){onInput($event)}}}))}`
+    )
+  })
+
+  it('generate events with inline statement', () => {
     assertCodegen(
       '<input @input="curent++">',
-      `with (this) { return __r__(__s__('input', {on:{"input":function($event){curent++}}}), undefined)}`
+      `with(this){return _h(_e('input',{on:{"input":function($event){curent++}}}))}`
     )
-    // unhandled
+  })
+
+  it('generate unhandled events', () => {
     assertCodegen(
       '<input @input="curent++">',
-      `with (this) { return __r__(__s__('input', {on:{"input":function(){}}}), undefined)}`,
+      `with(this){return _h(_e('input',{on:{"input":function(){}}}))}`,
       ast => {
         ast.events.input = undefined
       }
     )
-    // multiple handlers
+  })
+
+  it('generate multiple event handlers', () => {
     assertCodegen(
       '<input @input="curent++" @input="onInput">',
-      `with (this) { return __r__(__s__('input', {on:{"input":[function($event){curent++},onInput]}}), undefined)}`
+      `with(this){return _h(_e('input',{on:{"input":[function($event){curent++},onInput]}}))}`
     )
   })
 
   it('generate component', () => {
     assertCodegen(
-      '<my-component name="mycomponent1" :msg="msg" @notify="onNotify"></my-component>',
-      `with (this) { return __r__(__s__('my-component', {attrs:{"msg":msg},staticAttrs:{"name":"mycomponent1"},on:{"notify":onNotify}}), undefined)}`
+      '<my-component name="mycomponent1" :msg="msg" @notify="onNotify"><div>hi</div></my-component>',
+      `with(this){return _h(_e('my-component',{attrs:{"msg":msg},staticAttrs:{"name":"mycomponent1"},on:{"notify":onNotify}}),function(){return [_m(0)]})}`,
+      [`with(this){return _h(_e('div'),[_t("hi")])}`]
+    )
+  })
+
+  it('generate is attribute', () => {
+    assertCodegen(
+      '<div is="component1"></div>',
+      `with(this){return _h(_e("component1",{}))}`
     )
-    // have "is" attribute
     assertCodegen(
-      '<my-component is="component1"></my-component>',
-      `with (this) { return __r__(__s__("component1", {}), undefined)}`
+      '<div :is="component1"></div>',
+      `with(this){return _h(_e(component1,{}))}`
     )
+  })
+
+  it('generate component with inline-template', () => {
     // have "inline-template'"
     assertCodegen(
       '<my-component inline-template><p>hello world</p></my-component>',
-      `with (this) { return __r__(__s__('my-component', {inlineTemplate:{render:function(){with (this) { return __m__(0)}},staticRenderFns:[function(){with(this){return __r__(__s__('p', undefined), [__t__("hello world")])}}]}}), undefined)}`
+      `with(this){return _h(_e('my-component',{inlineTemplate:{render:function(){with(this){return _m(0)}},staticRenderFns:[function(){with(this){return _h(_e('p'),[_t("hello world")])}}]}}))}`
     )
-    // "have inline-template attrs, and not have child
+    // "have inline-template attrs, but not having extactly one child element
     assertCodegen(
       '<my-component inline-template><hr><hr></my-component>',
-      `with (this) { return __r__(__s__('my-component', {inlineTemplate:{render:function(){with (this) { return __m__(0)}},staticRenderFns:[function(){with(this){return __r__(__s__('hr', undefined), undefined)}}]}}), undefined)}`
+      `with(this){return _h(_e('my-component',{inlineTemplate:{render:function(){with(this){return _m(0)}},staticRenderFns:[function(){with(this){return _h(_e('hr'))}}]}}))}`
     )
     expect('Inline-template components must have exactly one child element.').toHaveBeenWarned()
   })
 
   it('not specified ast type', () => {
     const res = generate(null, baseOptions)
-    expect(res.render).toBe(`with (this) { return __r__(__s__("div"))}`)
+    expect(res.render).toBe(`with(this){return _h(_e("div"))}`)
     expect(res.staticRenderFns).toEqual([])
   })
 
   it('not specified directives option', () => {
     assertCodegen(
       '<p v-if="show">hello world</p>',
-      `with (this) { return (show) ? __r__(__s__('p', undefined), [__t__("hello world")]) : null}`,
+      `with(this){return (show)?_h(_e('p'),[_t("hello world")]):void 0}`,
       { isReservedTag }
     )
   })
 
   it('not specified isReservedTag option', () => {
+    // this causes all tags to be treated as components,
+    // thus all children are wrapped in thunks.
     assertCodegen(
       '<div><p>hello world</p></div>',
-      `with (this) { return __m__(0)}`,
-      [`with(this){return __r__(__s__('div', undefined), function(){return [__r__(__s__('p', undefined), function(){return [__t__("hello world")]})]})}`],
+      `with(this){return _m(0)}`,
+      [`with(this){return _h(_e('div'),function(){return [_h(_e('p'),function(){return [_t("hello world")]})]})}`],
       { directives }
     )
   })

+ 2 - 2
test/unit/modules/compiler/parser.spec.js

@@ -27,7 +27,7 @@ describe('parser', () => {
     const ast = parse('<h1>{{msg}}</h1>', baseOptions)
     expect(ast.tag).toBe('h1')
     expect(ast.plain).toBe(true)
-    expect(ast.children[0].expression).toBe('__toString__(msg)')
+    expect(ast.children[0].expression).toBe('_s(msg)')
   })
 
   it('child elements', () => {
@@ -298,7 +298,7 @@ describe('parser', () => {
 
   it('custom delimiter', () => {
     const ast = parse('<p>{msg}</p>', extend({ delimiters: ['{', '}'] }, baseOptions))
-    expect(ast.children[0].expression).toBe('__toString__(msg)')
+    expect(ast.children[0].expression).toBe('_s(msg)')
   })
 
   it('not specified getTagNamespace option', () => {