2
0

index.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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 onceCount
  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. const prevOnceCount = onceCount
  24. onceCount = 0
  25. currentOptions = options
  26. warn = options.warn || baseWarn
  27. transforms = pluckModuleFunction(options.modules, 'transformCode')
  28. dataGenFns = pluckModuleFunction(options.modules, 'genData')
  29. platformDirectives = options.directives || {}
  30. const code = ast ? genElement(ast) : '_h("div")'
  31. staticRenderFns = prevStaticRenderFns
  32. onceCount = prevOnceCount
  33. return {
  34. render: `with(this){return ${code}}`,
  35. staticRenderFns: currentStaticRenderFns
  36. }
  37. }
  38. function genElement (el: ASTElement): string {
  39. if (el.staticRoot && !el.staticProcessed) {
  40. return genStatic(el)
  41. } else if (el.once && !el.onceProcessed) {
  42. return genOnce(el)
  43. } else if (el.for && !el.forProcessed) {
  44. return genFor(el)
  45. } else if (el.if && !el.ifProcessed) {
  46. return genIf(el)
  47. } else if (el.tag === 'template' && !el.slotTarget) {
  48. return genChildren(el) || 'void 0'
  49. } else if (el.tag === 'slot') {
  50. return genSlot(el)
  51. } else {
  52. // component or element
  53. let code
  54. if (el.component) {
  55. code = genComponent(el.component, el)
  56. } else {
  57. const data = el.plain ? undefined : genData(el)
  58. const children = el.inlineTemplate ? null : genChildren(el)
  59. code = `_h('${el.tag}'${
  60. data ? `,${data}` : '' // data
  61. }${
  62. children ? `,${children}` : '' // children
  63. })`
  64. }
  65. // module transforms
  66. for (let i = 0; i < transforms.length; i++) {
  67. code = transforms[i](el, code)
  68. }
  69. return code
  70. }
  71. }
  72. // hoist static sub-trees out
  73. function genStatic (el: ASTElement): string {
  74. el.staticProcessed = true
  75. staticRenderFns.push(`with(this){return ${genElement(el)}}`)
  76. return `_m(${staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})`
  77. }
  78. // v-once
  79. function genOnce (el: ASTElement): string {
  80. el.onceProcessed = true
  81. if (el.if && !el.ifProcessed) {
  82. return genIf(el)
  83. } else if (el.staticInFor) {
  84. let key = ''
  85. let parent = el.parent
  86. while (parent) {
  87. if (parent.for) {
  88. key = parent.key
  89. break
  90. }
  91. parent = parent.parent
  92. }
  93. if (!key) {
  94. process.env.NODE_ENV !== 'production' && warn(
  95. `v-once can only be used inside v-for that is keyed. `
  96. )
  97. return genElement(el)
  98. }
  99. return `_o(${genElement(el)},${onceCount++}${key ? `,${key}` : ``})`
  100. } else {
  101. return genStatic(el)
  102. }
  103. }
  104. // v-if with v-once shuold generate code like (a)?_m(0):_m(1)
  105. function genIf (el: any): string {
  106. const exp = el.if
  107. el.ifProcessed = true // avoid recursion
  108. return `(${exp})?${el.once ? genOnce(el) : genElement(el)}:${genElse(el)}`
  109. }
  110. function genElse (el: ASTElement): string {
  111. return el.elseBlock
  112. ? genElement(el.elseBlock)
  113. : '_e()'
  114. }
  115. function genFor (el: any): string {
  116. const exp = el.for
  117. const alias = el.alias
  118. const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
  119. const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
  120. el.forProcessed = true // avoid recursion
  121. return `_l((${exp}),` +
  122. `function(${alias}${iterator1}${iterator2}){` +
  123. `return ${genElement(el)}` +
  124. '})'
  125. }
  126. function genData (el: ASTElement): string {
  127. let data = '{'
  128. // directives first.
  129. // directives may mutate the el's other properties before they are generated.
  130. const dirs = genDirectives(el)
  131. if (dirs) data += dirs + ','
  132. // key
  133. if (el.key) {
  134. data += `key:${el.key},`
  135. }
  136. // ref
  137. if (el.ref) {
  138. data += `ref:${el.ref},`
  139. }
  140. if (el.refInFor) {
  141. data += `refInFor:true,`
  142. }
  143. // record original tag name for components using "is" attribute
  144. if (el.component) {
  145. data += `tag:"${el.tag}",`
  146. }
  147. // slot target
  148. if (el.slotTarget) {
  149. data += `slot:${el.slotTarget},`
  150. }
  151. // module data generation functions
  152. for (let i = 0; i < dataGenFns.length; i++) {
  153. data += dataGenFns[i](el)
  154. }
  155. // attributes
  156. if (el.attrs) {
  157. data += `attrs:{${genProps(el.attrs)}},`
  158. }
  159. // DOM props
  160. if (el.props) {
  161. data += `domProps:{${genProps(el.props)}},`
  162. }
  163. // event handlers
  164. if (el.events) {
  165. data += `${genHandlers(el.events)},`
  166. }
  167. if (el.nativeEvents) {
  168. data += `${genHandlers(el.nativeEvents, true)},`
  169. }
  170. // inline-template
  171. if (el.inlineTemplate) {
  172. data += `${genInlineTemplate(el)},`
  173. }
  174. // scoped slots
  175. if (el.scopedSlots) {
  176. data += `${genScopedSlots(el.scopedSlots)},`
  177. }
  178. data = data.replace(/,$/, '') + '}'
  179. // v-bind data wrap
  180. if (el.wrapData) {
  181. data = el.wrapData(data)
  182. }
  183. return data
  184. }
  185. function genDirectives (el: ASTElement): string | void {
  186. const dirs = el.directives
  187. if (!dirs) return
  188. let res = 'directives:['
  189. let hasRuntime = false
  190. let i, l, dir, needRuntime
  191. for (i = 0, l = dirs.length; i < l; i++) {
  192. dir = dirs[i]
  193. needRuntime = true
  194. const gen = platformDirectives[dir.name] || baseDirectives[dir.name]
  195. if (gen) {
  196. // compile-time directive that manipulates AST.
  197. // returns true if it also needs a runtime counterpart.
  198. needRuntime = !!gen(el, dir, warn)
  199. }
  200. if (needRuntime) {
  201. hasRuntime = true
  202. res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
  203. dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
  204. }${
  205. dir.arg ? `,arg:"${dir.arg}"` : ''
  206. }${
  207. dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
  208. }},`
  209. }
  210. }
  211. if (hasRuntime) {
  212. return res.slice(0, -1) + ']'
  213. }
  214. }
  215. function genInlineTemplate (el) {
  216. const ast = el.children[0]
  217. if (process.env.NODE_ENV !== 'production' && (
  218. el.children.length > 1 || ast.type !== 1
  219. )) {
  220. warn('Inline-template components must have exactly one child element.')
  221. }
  222. if (ast.type === 1) {
  223. const inlineRenderFns = generate(ast, currentOptions)
  224. return `inlineTemplate:{render:function(){${
  225. inlineRenderFns.render
  226. }},staticRenderFns:[${
  227. inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',')
  228. }]}`
  229. } else {
  230. return ''
  231. }
  232. }
  233. function genScopedSlots (slots) {
  234. return `scopedSlots:{${
  235. Object.keys(slots).map(key => genScopedSlot(key, slots[key])).join(',')
  236. }}`
  237. }
  238. function genScopedSlot (key: string, el: ASTElement) {
  239. return `${key}:function(${String(el.attrsMap.scope)}){` +
  240. `return ${el.tag === 'template'
  241. ? genChildren(el) || 'void 0'
  242. : genElement(el)
  243. }}`
  244. }
  245. function genChildren (el: ASTElement): string | void {
  246. if (el.children.length) {
  247. return '[' + el.children.map(genNode).join(',') + ']'
  248. }
  249. }
  250. function genNode (node: ASTNode) {
  251. if (node.type === 1) {
  252. return genElement(node)
  253. } else {
  254. return genText(node)
  255. }
  256. }
  257. function genText (text: ASTText | ASTExpression): string {
  258. return text.type === 2
  259. ? text.expression // no need for () because already wrapped in _s()
  260. : JSON.stringify(text.text)
  261. }
  262. function genSlot (el: ASTElement): string {
  263. const slotName = el.slotName || '"default"'
  264. const children = genChildren(el)
  265. return `_t(${slotName}${
  266. children ? `,${children}` : ''
  267. })`
  268. }
  269. // componentName is el.component, take it as argument to shun flow's pessimistic refinement
  270. function genComponent (componentName, el): string {
  271. const children = el.inlineTemplate ? null : genChildren(el)
  272. return `_h(${componentName},${genData(el)}${
  273. children ? `,${children}` : ''
  274. })`
  275. }
  276. function genProps (props: Array<{ name: string, value: string }>): string {
  277. let res = ''
  278. for (let i = 0; i < props.length; i++) {
  279. const prop = props[i]
  280. res += `"${prop.name}":${prop.value},`
  281. }
  282. return res.slice(0, -1)
  283. }