codegen.js 7.3 KB

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