UNPKG

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