UNPKG

3.32 kBJavaScriptView Raw
1/* @flow */
2
3import { isUndef } from 'shared/util'
4
5type RenderState = {
6 type: 'Element';
7 rendered: number;
8 total: number;
9 endTag: string;
10 children: Array<VNode>;
11} | {
12 type: 'Component';
13 prevActive: Component;
14} | {
15 type: 'ComponentWithCache';
16 buffer: Array<string>;
17 bufferIndex: number;
18 componentBuffer: Array<Set<Class<Component>>>;
19 key: string;
20};
21
22export class RenderContext {
23 userContext: ?Object;
24 activeInstance: Component;
25 renderStates: Array<RenderState>;
26 write: (text: string, next: Function) => void;
27 renderNode: (node: VNode, isRoot: boolean, context: RenderContext) => void;
28 next: () => void;
29 done: () => void;
30
31 modules: Array<() => ?string>;
32 directives: Object;
33 isUnaryTag: (tag: string) => boolean;
34
35 cache: any;
36 get: ?(key: string, cb: Function) => void;
37 has: ?(key: string, cb: Function) => void;
38
39 constructor (options: Object) {
40 this.userContext = options.userContext
41 this.activeInstance = options.activeInstance
42 this.renderStates = []
43
44 this.write = options.write
45 this.done = options.done
46 this.renderNode = options.renderNode
47
48 this.isUnaryTag = options.isUnaryTag
49 this.modules = options.modules
50 this.directives = options.directives
51
52 const cache = options.cache
53 if (cache && (!cache.get || !cache.set)) {
54 throw new Error('renderer cache must implement at least get & set.')
55 }
56 this.cache = cache
57 this.get = cache && normalizeAsync(cache, 'get')
58 this.has = cache && normalizeAsync(cache, 'has')
59
60 this.next = this.next.bind(this)
61 }
62
63 next () {
64 const lastState = this.renderStates[this.renderStates.length - 1]
65 if (isUndef(lastState)) {
66 return this.done()
67 }
68 switch (lastState.type) {
69 case 'Element':
70 const { children, total } = lastState
71 const rendered = lastState.rendered++
72 if (rendered < total) {
73 this.renderNode(children[rendered], false, this)
74 } else {
75 this.renderStates.pop()
76 this.write(lastState.endTag, this.next)
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
111function normalizeAsync (cache, method) {
112 const fn = cache[method]
113 if (isUndef(fn)) {
114 return
115 } else if (fn.length > 1) {
116 return (key, cb) => fn.call(cache, key, cb)
117 } else {
118 return (key, cb) => cb(fn.call(cache, key))
119 }
120}