render-context.js 3.3 KB

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