render-context.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /* @flow */
  2. import { isUndef } from 'shared/util'
  3. type RenderState = {
  4. type: 'Element';
  5. rendered: number;
  6. total: number;
  7. children: Array<VNode>;
  8. endTag: string;
  9. } | {
  10. type: 'Fragment';
  11. rendered: number;
  12. total: number;
  13. children: Array<VNode>;
  14. } | {
  15. type: 'Component';
  16. prevActive: Component;
  17. } | {
  18. type: 'ComponentWithCache';
  19. buffer: Array<string>;
  20. bufferIndex: number;
  21. componentBuffer: Array<Set<Class<Component>>>;
  22. key: string;
  23. };
  24. export class RenderContext {
  25. userContext: ?Object;
  26. activeInstance: Component;
  27. renderStates: Array<RenderState>;
  28. write: (text: string, next: Function) => void;
  29. renderNode: (node: VNode, isRoot: boolean, context: RenderContext) => void;
  30. next: () => void;
  31. done: (err: ?Error) => void;
  32. modules: Array<(node: VNode) => ?string>;
  33. directives: Object;
  34. isUnaryTag: (tag: string) => boolean;
  35. cache: any;
  36. get: ?(key: string, cb: Function) => void;
  37. has: ?(key: string, cb: Function) => void;
  38. constructor (options: Object) {
  39. this.userContext = options.userContext
  40. this.activeInstance = options.activeInstance
  41. this.renderStates = []
  42. this.write = options.write
  43. this.done = options.done
  44. this.renderNode = options.renderNode
  45. this.isUnaryTag = options.isUnaryTag
  46. this.modules = options.modules
  47. this.directives = options.directives
  48. const cache = options.cache
  49. if (cache && (!cache.get || !cache.set)) {
  50. throw new Error('renderer cache must implement at least get & set.')
  51. }
  52. this.cache = cache
  53. this.get = cache && normalizeAsync(cache, 'get')
  54. this.has = cache && normalizeAsync(cache, 'has')
  55. this.next = this.next.bind(this)
  56. }
  57. next () {
  58. const lastState = this.renderStates[this.renderStates.length - 1]
  59. if (isUndef(lastState)) {
  60. return this.done()
  61. }
  62. /* eslint-disable no-case-declarations */
  63. switch (lastState.type) {
  64. case 'Element':
  65. case 'Fragment':
  66. const { children, total } = lastState
  67. const rendered = lastState.rendered++
  68. if (rendered < total) {
  69. this.renderNode(children[rendered], false, this)
  70. } else {
  71. this.renderStates.pop()
  72. if (lastState.type === 'Element') {
  73. this.write(lastState.endTag, this.next)
  74. } else {
  75. this.next()
  76. }
  77. }
  78. break
  79. case 'Component':
  80. this.renderStates.pop()
  81. this.activeInstance = lastState.prevActive
  82. this.next()
  83. break
  84. case 'ComponentWithCache':
  85. this.renderStates.pop()
  86. const { buffer, bufferIndex, componentBuffer, key } = lastState
  87. const result = {
  88. html: buffer[bufferIndex],
  89. components: componentBuffer[bufferIndex]
  90. }
  91. this.cache.set(key, result)
  92. if (bufferIndex === 0) {
  93. // this is a top-level cached component,
  94. // exit caching mode.
  95. this.write.caching = false
  96. } else {
  97. // parent component is also being cached,
  98. // merge self into parent's result
  99. buffer[bufferIndex - 1] += result.html
  100. const prev = componentBuffer[bufferIndex - 1]
  101. result.components.forEach(c => prev.add(c))
  102. }
  103. buffer.length = bufferIndex
  104. componentBuffer.length = bufferIndex
  105. this.next()
  106. break
  107. }
  108. }
  109. }
  110. function normalizeAsync (cache, method) {
  111. const fn = cache[method]
  112. if (isUndef(fn)) {
  113. return
  114. } else if (fn.length > 1) {
  115. return (key, cb) => fn.call(cache, key, cb)
  116. } else {
  117. return (key, cb) => cb(fn.call(cache, key))
  118. }
  119. }