| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- /* @flow */
- import { cached } from 'shared/util'
- import { encodeHTML } from 'entities'
- import { createComponentInstanceForVnode } from 'core/vdom/create-component'
- const encodeHTMLCached = cached(encodeHTML)
- const normalizeAsync = (cache, method) => {
- const fn = cache[method]
- if (!fn) {
- return
- } else if (fn.length > 1) {
- return (key, cb) => fn.call(cache, key, cb)
- } else {
- return (key, cb) => cb(fn.call(cache, key))
- }
- }
- export function createRenderFunction (
- modules: Array<Function>,
- directives: Object,
- isUnaryTag: Function,
- cache: any
- ) {
- if (cache && (!cache.get || !cache.set)) {
- throw new Error('renderer cache must implement at least get & set.')
- }
- const get = cache && normalizeAsync(cache, 'get')
- const has = cache && normalizeAsync(cache, 'has')
- function renderNode (
- node: VNode,
- write: Function,
- next: Function,
- isRoot: boolean
- ) {
- if (node.componentOptions) {
- // check cache hit
- const Ctor = node.componentOptions.Ctor
- const getKey = Ctor.options.serverCacheKey
- if (getKey && cache) {
- const key = Ctor.cid + '::' + getKey(node.componentOptions.propsData)
- if (has) {
- has(key, hit => {
- if (hit) {
- get(key, res => write(res, next))
- } else {
- renderComponentWithCache(node, write, next, isRoot, cache, key)
- }
- })
- } else {
- get(key, res => {
- if (res) {
- write(res, next)
- } else {
- renderComponentWithCache(node, write, next, isRoot, cache, key)
- }
- })
- }
- } else {
- if (getKey) {
- console.error(
- `[vue-server-renderer] Component ${
- Ctor.options.name || '(anonymous)'
- } implemented serverCacheKey, ` +
- 'but no cache was provided to the renderer.'
- )
- }
- renderComponent(node, write, next, isRoot)
- }
- } else {
- if (node.tag) {
- renderElement(node, write, next, isRoot)
- } else {
- write(node.raw ? node.text : encodeHTMLCached(node.text), next)
- }
- }
- }
- function renderComponent (node, write, next, isRoot) {
- const child = createComponentInstanceForVnode(node)._render()
- child.parent = node
- renderNode(child, write, next, isRoot)
- }
- function renderComponentWithCache (node, write, next, isRoot, cache, key) {
- write.caching = true
- const buffer = write.cacheBuffer
- const bufferIndex = buffer.push('') - 1
- renderComponent(node, write, () => {
- const result = buffer[bufferIndex]
- cache.set(key, result)
- if (bufferIndex === 0) {
- // this is a top-level cached component,
- // exit caching mode.
- write.caching = false
- } else {
- // parent component is also being cached,
- // merge self into parent's result
- buffer[bufferIndex - 1] += result
- }
- buffer.length = bufferIndex
- next()
- }, isRoot)
- }
- function renderElement (el, write, next, isRoot) {
- if (isRoot) {
- if (!el.data) el.data = {}
- if (!el.data.attrs) el.data.attrs = {}
- el.data.attrs['server-rendered'] = 'true'
- }
- const startTag = renderStartingTag(el)
- const endTag = `</${el.tag}>`
- if (isUnaryTag(el.tag)) {
- write(startTag, next)
- } else if (!el.children || !el.children.length) {
- write(startTag + endTag, next)
- } else {
- const children: Array<VNode> = el.children || []
- write(startTag, () => {
- const total = children.length
- let rendered = 0
- function renderChild (child: VNode) {
- renderNode(child, write, () => {
- rendered++
- if (rendered < total) {
- renderChild(children[rendered])
- } else {
- write(endTag, next)
- }
- }, false)
- }
- renderChild(children[0])
- })
- }
- }
- function renderStartingTag (node: VNode) {
- let markup = `<${node.tag}`
- if (node.data) {
- // check directives
- const dirs = node.data.directives
- if (dirs) {
- for (let i = 0; i < dirs.length; i++) {
- const dirRenderer = directives[dirs[i].name]
- if (dirRenderer) {
- // directives mutate the node's data
- // which then gets rendered by modules
- dirRenderer(node, dirs[i])
- }
- }
- }
- // apply other modules
- for (let i = 0; i < modules.length; i++) {
- const res = modules[i](node)
- if (res) {
- markup += res
- }
- }
- }
- // attach scoped CSS ID
- let scopeId
- if (node.host && (scopeId = node.host.$options._scopeId)) {
- markup += ` ${scopeId}`
- }
- while (node) {
- if ((scopeId = node.context.$options._scopeId)) {
- markup += ` ${scopeId}`
- }
- node = node.parent
- }
- return markup + '>'
- }
- return function render (
- component: Component,
- write: (text: string, next: Function) => void,
- done: Function
- ) {
- renderNode(component._render(), write, done, true)
- }
- }
|