codegen.js 7.5 KB

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