codegen.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /* @flow */
  2. import { genHandlers } from './events'
  3. import { ref } from './directives/ref'
  4. import { baseWarn } from './helpers'
  5. import { noop } from 'shared/util'
  6. const baseDirectives = {
  7. ref,
  8. cloak: noop
  9. }
  10. // configurable state
  11. let warn
  12. let platformDirectives
  13. let isPlatformReservedTag
  14. let staticRenderFns
  15. let currentOptions
  16. export function generate (
  17. ast: ASTElement | void,
  18. options: CompilerOptions
  19. ): {
  20. render: string,
  21. staticRenderFns: Array<string>
  22. } {
  23. // save previous staticRenderFns so generate calls can be nested
  24. const prevStaticRenderFns: Array<string> = staticRenderFns
  25. const currentStaticRenderFns: Array<string> = staticRenderFns = []
  26. currentOptions = options
  27. warn = options.warn || baseWarn
  28. platformDirectives = options.directives || {}
  29. isPlatformReservedTag = options.isReservedTag || (() => false)
  30. const code = ast ? genElement(ast) : '__h__("div")'
  31. staticRenderFns = prevStaticRenderFns
  32. return {
  33. render: `with (this) { return ${code}}`,
  34. staticRenderFns: currentStaticRenderFns
  35. }
  36. }
  37. function genElement (el: ASTElement): string {
  38. if (el.for) {
  39. return genFor(el)
  40. } else if (el.if) {
  41. return genIf(el)
  42. } else if (el.tag === 'template' && !el.slotTarget) {
  43. return genChildren(el)
  44. } else if (el.tag === 'render') {
  45. return genRender(el)
  46. } else if (el.tag === 'slot') {
  47. return genSlot(el)
  48. } else if (el.component) {
  49. return genComponent(el)
  50. } else {
  51. // if the element is potentially a component,
  52. // wrap its children as a thunk.
  53. const children = el.inlineTemplate
  54. ? 'undefined'
  55. : genChildren(el, !isPlatformReservedTag(el.tag) /* asThunk */)
  56. const namespace = el.ns ? `,'${el.ns}'` : ''
  57. const code = `__h__('${el.tag}', ${genData(el)}, ${children}${namespace})`
  58. if (el.staticRoot) {
  59. // hoist static sub-trees out
  60. staticRenderFns.push(`with(this){return ${code}}`)
  61. return `_staticTrees[${staticRenderFns.length - 1}]`
  62. } else {
  63. return code
  64. }
  65. }
  66. }
  67. function genIf (el: ASTElement): string {
  68. const exp = el.if
  69. el.if = null // avoid recursion
  70. return `(${exp}) ? ${genElement(el)} : ${genElse(el)}`
  71. }
  72. function genElse (el: ASTElement): string {
  73. return el.elseBlock
  74. ? genElement(el.elseBlock)
  75. : 'null'
  76. }
  77. function genFor (el: ASTElement): string {
  78. const exp = el.for
  79. const alias = el.alias
  80. const iterator = el.iterator
  81. el.for = null // avoid recursion
  82. return `(${exp})&&__renderList__((${exp}), ` +
  83. `function(${alias},$index,${iterator || '$key'}){` +
  84. `return ${genElement(el)}` +
  85. '})'
  86. }
  87. function genData (el: ASTElement): string {
  88. if (el.plain) {
  89. return 'undefined'
  90. }
  91. let data = '{'
  92. // directives first.
  93. // directives may mutate the el's other properties before they are generated.
  94. const dirs = genDirectives(el)
  95. if (dirs) data += dirs + ','
  96. // pre
  97. if (el.pre) {
  98. data += 'pre:true,'
  99. }
  100. // key
  101. if (el.key) {
  102. data += `key:${el.key},`
  103. }
  104. // slot target
  105. if (el.slotTarget) {
  106. data += `slot:${el.slotTarget},`
  107. }
  108. // class
  109. if (el.staticClass) {
  110. data += `staticClass:${el.staticClass},`
  111. }
  112. if (el.classBinding) {
  113. data += `class:${el.classBinding},`
  114. }
  115. // style
  116. if (el.styleBinding) {
  117. data += `style:${el.styleBinding},`
  118. }
  119. // transition
  120. if (el.transition) {
  121. data += `transition:{definition:(${el.transition}),appear:${el.transitionOnAppear}},`
  122. }
  123. // v-show, used to avoid transition being applied
  124. // since v-show takes it over
  125. if (el.attrsMap['v-show']) {
  126. data += 'show:true,'
  127. }
  128. // props
  129. if (el.props) {
  130. data += `props:{${genProps(el.props)}},`
  131. }
  132. // attributes
  133. if (el.attrs) {
  134. data += `attrs:{${genProps(el.attrs)}},`
  135. }
  136. // static attributes
  137. if (el.staticAttrs) {
  138. data += `staticAttrs:{${genProps(el.staticAttrs)}},`
  139. }
  140. // hooks
  141. if (el.hooks) {
  142. data += `hook:{${genHooks(el.hooks)}},`
  143. }
  144. // event handlers
  145. if (el.events) {
  146. data += `${genHandlers(el.events)},`
  147. }
  148. // inline-template
  149. if (el.inlineTemplate) {
  150. if (process.env.NODE_ENV !== 'production' && (
  151. el.children.length > 1 || !el.children[0].tag
  152. )) {
  153. warn('Inline-template components must have exactly one child element.')
  154. }
  155. const ast: ASTElement = el.children[0]
  156. const inlineRenderFns = generate(ast, currentOptions)
  157. data += `inlineTemplate:{render:function(){${
  158. inlineRenderFns.render
  159. }},staticRenderFns:[${
  160. inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',')
  161. }]}`
  162. }
  163. return data.replace(/,$/, '') + '}'
  164. }
  165. function genDirectives (el: ASTElement): string | void {
  166. const dirs = el.directives
  167. if (!dirs) return
  168. let res = 'directives:['
  169. let hasRuntime = false
  170. let i, l, dir, needRuntime
  171. for (i = 0, l = dirs.length; i < l; i++) {
  172. dir = dirs[i]
  173. needRuntime = true
  174. const gen = platformDirectives[dir.name] || baseDirectives[dir.name]
  175. if (gen) {
  176. // compile-time directive that manipulates AST.
  177. // returns true if it also needs a runtime counterpart.
  178. needRuntime = !!gen(el, dir)
  179. }
  180. if (needRuntime) {
  181. hasRuntime = true
  182. res += `{name:"${dir.name}"${
  183. dir.value ? `,value:(${dir.value})` : ''
  184. }${
  185. dir.arg ? `,arg:"${dir.arg}"` : ''
  186. }${
  187. dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
  188. }},`
  189. }
  190. }
  191. if (hasRuntime) {
  192. return res.slice(0, -1) + ']'
  193. }
  194. }
  195. function genChildren (el: ASTElement, asThunk?: boolean): string {
  196. if (!el.children.length) {
  197. return 'undefined'
  198. }
  199. const code = '[' + el.children.map(genNode).join(',') + ']'
  200. return asThunk
  201. ? `function(){return ${code}}`
  202. : code
  203. }
  204. function genNode (node) {
  205. if (node.tag) {
  206. return genElement(node)
  207. } else {
  208. return genText(node)
  209. }
  210. }
  211. function genText (text: ASTText): string {
  212. return text.expression
  213. ? `(${text.expression})`
  214. : JSON.stringify(text.text)
  215. }
  216. function genRender (el: ASTElement): string {
  217. return `${el.renderMethod}(${el.renderArgs || 'null'},${genChildren(el)})`
  218. }
  219. function genSlot (el: ASTElement): string {
  220. const name = el.slotName || '"default"'
  221. return `($slots[${name}] || ${genChildren(el)})`
  222. }
  223. function genComponent (el: ASTElement): string {
  224. return `__h__(${el.component}, ${genData(el)}, ${genChildren(el, true)})`
  225. }
  226. function genProps (props: Array<{ name: string, value: string }>): string {
  227. let res = ''
  228. for (let i = 0; i < props.length; i++) {
  229. const prop = props[i]
  230. res += `"${prop.name}":${prop.value},`
  231. }
  232. return res.slice(0, -1)
  233. }
  234. function genHooks (hooks: { [key: string]: Array<string> }): string {
  235. let res = ''
  236. for (const key in hooks) {
  237. res += `"${key}":function(n1,n2){${hooks[key].join(';')}},`
  238. }
  239. return res.slice(0, -1)
  240. }