render-context.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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. case 'Fragment':
  60. const { children, total } = lastState
  61. const rendered = lastState.rendered++
  62. if (rendered < total) {
  63. this.renderNode(children[rendered], false, this)
  64. } else {
  65. this.renderStates.pop()
  66. if (lastState.endTag) {
  67. this.write(lastState.endTag, this.next)
  68. } else {
  69. this.next()
  70. }
  71. }
  72. break
  73. case 'Component':
  74. this.renderStates.pop()
  75. this.activeInstance = lastState.prevActive
  76. this.next()
  77. break
  78. case 'ComponentWithCache':
  79. this.renderStates.pop()
  80. const { buffer, bufferIndex, componentBuffer, key } = lastState
  81. const result = {
  82. html: buffer[bufferIndex],
  83. components: componentBuffer[bufferIndex]
  84. }
  85. this.cache.set(key, result)
  86. if (bufferIndex === 0) {
  87. // this is a top-level cached component,
  88. // exit caching mode.
  89. this.write.caching = false
  90. } else {
  91. // parent component is also being cached,
  92. // merge self into parent's result
  93. buffer[bufferIndex - 1] += result.html
  94. const prev = componentBuffer[bufferIndex - 1]
  95. result.components.forEach(c => prev.add(c))
  96. }
  97. buffer.length = bufferIndex
  98. componentBuffer.length = bufferIndex
  99. this.next()
  100. break
  101. }
  102. }
  103. }
  104. function normalizeAsync (cache, method) {
  105. const fn = cache[method]
  106. if (isUndef(fn)) {
  107. return
  108. } else if (fn.length > 1) {
  109. return (key, cb) => fn.call(cache, key, cb)
  110. } else {
  111. return (key, cb) => cb(fn.call(cache, key))
  112. }
  113. }