vue-server-renderer

Vue SSR 依赖于vue-server-renderer这个包,而这个包的源码也在vue仓库的package/vue-server-renderer目录下。

// packages/vue-server-renderer/index.js
try {
  var vueVersion = require('vue').version
} catch (e) {}

var packageName = require('./package.json').name
var packageVersion = require('./package.json').version
if (vueVersion && vueVersion !== packageVersion) {
  throw new Error(
    '\n\nVue packages version mismatch:\n\n' +
    '- vue@' + vueVersion + '\n' +
    '- ' + packageName + '@' + packageVersion + '\n\n' +
    'This may cause things to work incorrectly. Make sure to use the same version for both.\n'
  )
}

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./build.prod.js')
} else {
  module.exports = require('./build.dev.js')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

package/vue-server-renderer目录下的文件基本上是vue在打包时动态产生的。

// scripts/config.js
// ...
const builds = {
  // ...
  // Web server renderer (CommonJS).
  'web-server-renderer-dev': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.dev.js'),
    format: 'cjs',
    env: 'development',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-prod': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.prod.js'),
    format: 'cjs',
    env: 'production',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-basic': {
    entry: resolve('web/entry-server-basic-renderer.js'),
    dest: resolve('packages/vue-server-renderer/basic.js'),
    format: 'umd',
    env: 'development',
    moduleName: 'renderVueComponentToString',
    plugins: [node(), cjs()]
  },
  'web-server-renderer-webpack-server-plugin': {
    entry: resolve('server/webpack-plugin/server.js'),
    dest: resolve('packages/vue-server-renderer/server-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-webpack-client-plugin': {
    entry: resolve('server/webpack-plugin/client.js'),
    dest: resolve('packages/vue-server-renderer/client-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  // ...
}
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

因此,vue-server-renderer包的入口文件实际上是web/entry-server-renderer.js文件。该文件导出createRenderer函数和createBundleRenderer函数,分别用于创建Rendereropen in new window实例和BundleRendereropen in new window实例,这两个实例上存在renderToString方法和renderToStream方法。

  • Renderer实例的renderToString方法: 将 Vue 实例渲染为字符串
  • Renderer实例的renderToStream方法: 将 Vue 实例渲染为一个 Node.js 可读流
  • BundleRenderer实例的renderToString方法: 将bundle渲染为字符串
  • BundleRenderer实例的renderToStream方法: 将bundle渲染为一个 Node.js 可读流
// src/platforms/web/entry-server-renderer.js
process.env.VUE_ENV = 'server'

import { extend } from 'shared/util'
import modules from './server/modules/index'
import baseDirectives from './server/directives/index'
import { isUnaryTag, canBeLeftOpenTag } from './compiler/util'

import { createRenderer as _createRenderer } from 'server/create-renderer'
import { createBundleRendererCreator } from 'server/bundle-renderer/create-bundle-renderer'

export function createRenderer (options?: Object = {}): {
  renderToString: Function,
  renderToStream: Function
} {
  return _createRenderer(extend(extend({}, options), {
    isUnaryTag,
    canBeLeftOpenTag,
    modules,
    // user can provide server-side implementations for custom directives
    // when creating the renderer.
    directives: extend(baseDirectives, options.directives)
  }))
}

export const createBundleRenderer = createBundleRendererCreator(createRenderer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

createRenderer

createRenderer函数是对_createRenderer函数的封装,传入的参数是开发者自定义的options对象和基本的options对象合并后的options对象。

_createRenderer函数的定义如下:

export function createRenderer ({
  modules = [],
  directives = {},
  isUnaryTag = (() => false),
  template,
  inject,
  cache,
  shouldPreload,
  shouldPrefetch,
  clientManifest,
  serializer
}: RenderOptions = {}): Renderer {

  // 创建 render 函数,该函数会将 Vue 实例渲染成字符串
  const render = createRenderFunction(modules, directives, isUnaryTag, cache)
  const templateRenderer = new TemplateRenderer({
    template,
    inject,
    shouldPreload,
    shouldPrefetch,
    clientManifest,
    serializer
  })

  return {
    renderToString (
      // 组件实例对象
      component: Component,
      // 用于模板插值
      context: any,
      // 回调函数,可以不传,会封装成 promise 形式
      cb: any
    ): ?Promise<string> {
      // 处理不传入 context 的情况
      if (typeof context === 'function') {
        cb = context
        context = {}
      }
      if (context) {
        // 往 context 上挂载 rendererResourceHints/rendererState/rendererScripts/rendererStyles/getPreloadFiles 等方法
        templateRenderer.bindRenderFns(context)
      }

      // 处理不传入 cb 的情况
      // 没有传 cb(形如 (err, html) => { ... } 的函数),则新创建一个 cb 并返回 promise;等到调用 cb 后,会触发 promise 的 resolve/reject
      // no callback, return Promise
      let promise
      if (!cb) {
        ({ promise, cb } = createPromiseCallback())
      }

      let result = ''
      // 该方法之后会挂在 context.write 上,并可通过 context.write.caching 确定是否要进行对写入的内容进行缓存
      const write = createWriteFunction(text => {
        result += text
        return false
      // 传入 cb 主要是用于内部错误的时候使用
      }, cb)
      try {
        // 调用 render 函数,将 Vue 实例渲染成字符串
        render(component, write, context, err => {
          if (err) {
            return cb(err)
          }
          if (context && context.rendered) {
            context.rendered(context)
          }
          if (template) {
            try {
              // 若是存在模板,则将组件的渲染结果字符串和模板结合一下再返回
              const res = templateRenderer.render(result, context)
              if (typeof res !== 'string') {
                // function template returning promise
                res
                  .then(html => cb(null, html))
                  .catch(cb)
              } else {
                cb(null, res)
              }
            } catch (e) {
              cb(e)
            }
          } else {
            cb(null, result)
          }
        })
      } catch (e) {
        cb(e)
      }

      // 始终返回 promise。
      // 针对传入 cb 的情况,这个 promise 是 undefined,开发者不需要关心这个返回值
      // 针对未传入 cb 的情况,经过 createPromiseCallback() 重新赋值 promise 和 cb 后,在调用 cb 后,会触发 promise 的 resolve/reject
      return promise
    },

    renderToStream (
      component: Component,
      context?: Object
    ): stream$Readable {
      if (context) {
        templateRenderer.bindRenderFns(context)
      }
      const renderStream = new RenderStream((write, done) => {
        render(component, write, context, done)
      })
      if (!template) {
        if (context && context.rendered) {
          const rendered = context.rendered
          renderStream.once('beforeEnd', () => {
            rendered(context)
          })
        }
        return renderStream
      } else if (typeof template === 'function') {
        throw new Error(`function template is only supported in renderToString.`)
      } else {
        const templateStream = templateRenderer.createStream(context)
        renderStream.on('error', err => {
          templateStream.emit('error', err)
        })
        renderStream.pipe(templateStream)
        if (context && context.rendered) {
          const rendered = context.rendered
          renderStream.once('beforeEnd', () => {
            rendered(context)
          })
        }
        return templateStream
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133