index.js 6.6 KB

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