1 |
|
2 |
|
3 | import { isUndef } from 'shared/util'
|
4 |
|
5 | type 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 |
|
27 | export 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 |
|
70 | while (true) {
|
71 | const lastState = this.renderStates[this.renderStates.length - 1]
|
72 | if (isUndef(lastState)) {
|
73 | return this.done()
|
74 | }
|
75 |
|
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 |
|
104 |
|
105 | this.write.caching = false
|
106 | } else {
|
107 |
|
108 |
|
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 |
|
121 | function 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 | }
|