/* @flow */
|
|
import { isUndef } from 'shared/util'
|
|
type RenderState = {
|
type: 'Element';
|
rendered: number;
|
total: number;
|
children: Array<VNode>;
|
endTag: string;
|
} | {
|
type: 'Fragment';
|
rendered: number;
|
total: number;
|
children: Array<VNode>;
|
} | {
|
type: 'Component';
|
prevActive: Component;
|
} | {
|
type: 'ComponentWithCache';
|
buffer: Array<string>;
|
bufferIndex: number;
|
componentBuffer: Array<Set<Class<Component>>>;
|
key: string;
|
};
|
|
export class RenderContext {
|
userContext: ?Object;
|
activeInstance: Component;
|
renderStates: Array<RenderState>;
|
write: (text: string, next: Function) => void;
|
renderNode: (node: VNode, isRoot: boolean, context: RenderContext) => void;
|
next: () => void;
|
done: (err: ?Error) => void;
|
|
modules: Array<(node: VNode) => ?string>;
|
directives: Object;
|
isUnaryTag: (tag: string) => boolean;
|
|
cache: any;
|
get: ?(key: string, cb: Function) => void;
|
has: ?(key: string, cb: Function) => void;
|
|
constructor (options: Object) {
|
this.userContext = options.userContext
|
this.activeInstance = options.activeInstance
|
this.renderStates = []
|
|
this.write = options.write
|
this.done = options.done
|
this.renderNode = options.renderNode
|
|
this.isUnaryTag = options.isUnaryTag
|
this.modules = options.modules
|
this.directives = options.directives
|
|
const cache = options.cache
|
if (cache && (!cache.get || !cache.set)) {
|
throw new Error('renderer cache must implement at least get & set.')
|
}
|
this.cache = cache
|
this.get = cache && normalizeAsync(cache, 'get')
|
this.has = cache && normalizeAsync(cache, 'has')
|
|
this.next = this.next.bind(this)
|
}
|
|
next () {
|
// eslint-disable-next-line
|
while (true) {
|
const lastState = this.renderStates[this.renderStates.length - 1]
|
if (isUndef(lastState)) {
|
return this.done()
|
}
|
/* eslint-disable no-case-declarations */
|
switch (lastState.type) {
|
case 'Element':
|
case 'Fragment':
|
const { children, total } = lastState
|
const rendered = lastState.rendered++
|
if (rendered < total) {
|
return this.renderNode(children[rendered], false, this)
|
} else {
|
this.renderStates.pop()
|
if (lastState.type === 'Element') {
|
return this.write(lastState.endTag, this.next)
|
}
|
}
|
break
|
case 'Component':
|
this.renderStates.pop()
|
this.activeInstance = lastState.prevActive
|
break
|
case 'ComponentWithCache':
|
this.renderStates.pop()
|
const { buffer, bufferIndex, componentBuffer, key } = lastState
|
const result = {
|
html: buffer[bufferIndex],
|
components: componentBuffer[bufferIndex]
|
}
|
this.cache.set(key, result)
|
if (bufferIndex === 0) {
|
// this is a top-level cached component,
|
// exit caching mode.
|
this.write.caching = false
|
} else {
|
// parent component is also being cached,
|
// merge self into parent's result
|
buffer[bufferIndex - 1] += result.html
|
const prev = componentBuffer[bufferIndex - 1]
|
result.components.forEach(c => prev.add(c))
|
}
|
buffer.length = bufferIndex
|
componentBuffer.length = bufferIndex
|
break
|
}
|
}
|
}
|
}
|
|
function normalizeAsync (cache, method) {
|
const fn = cache[method]
|
if (isUndef(fn)) {
|
return
|
} else if (fn.length > 1) {
|
return (key, cb) => fn.call(cache, key, cb)
|
} else {
|
return (key, cb) => cb(fn.call(cache, key))
|
}
|
}
|