服务端渲染(Vue中的服务器端渲染)

Vue中的服务器端渲染

本节以Vue的服务器端渲染为例,首先介绍基本流程,然后通过一个代码实例探讨其中的若干细节,并从中带领读者体会服务器端渲染是如何解决首屏性能问题的。

一、Vue 的SSR基本流程

​ 如图所示,描述了Vue服务器端渲染的整体流程,左边的是通用业务代码,可以看出无论是服务器端还是浏览器客户端,二者使用的是同套代码。 由于Vue组件生命周期在服务器端和在客户端上不一致,因此需要针对服务器端渲染编写相应的组件代码。

​ 比如Vue组件在进行服务器端渲染的时候,不存在真实DOM节点渲染的情况,所以并不存在mounted这个生命周期函数,那么原本在客户端编写的组件,就需要将mounted中的业务逻辑迁移到组件的其他位置上。

Vue服务器端渲染

​ 接着往右看,业务源代码从 app.js 处分出了两个构建入口,webpack 会根据不同的入口配置,分别生成用于服务器端谊染所需的Sever Bundle 和客户端准染所需的Client Bundle其中Server Bundle会在所定义的包渲染器中,被编译生成可以在浏览器端直接进行渲染的HTML文件。

​ 这里还存在一个小问题: 由于这份服务器端渲染所得的HTML文件,也是由Vue组件和相应的数据生成的,其包含的数据到了客户端之后,还是需要通过浏览器端的Vue框架进行管理的。

​ 那么客户端的Vue框架如何知道服务器渲染出的页面中,哪些数据与客户端代码中的相应组件存在关联呢?所以在浏览器的客户端部分就需要存在一个混入处理的阶段,将二者有效关联起来。这样在客户端中发生相应数据的改变后,服务器端渲染生成的页面也能够有响应式的联动变化。

二、Vue 的SSR项目实例

​ 为了更清楚地介绍Vue服务器端溶染的处理过程,下面借助 GitHub上的Demo项目源代码进行说明,其目录结构如下所示:

bulid/
|-- webpack.base.config.js
|-- webpack.client.config.js
|-- webpack.server.config.js
|-- vue-loader.config.js
|-- setup-dev-server.js
dist/
public/
src/
|-- components/
| |-- movieComment.vue
| |-- moviesTag.vue
| |-- searchTag.vue
|-- router/
| |-- index.js
|-- store/
| |-- moving/
| |-- index.js
|-- style/
| |-- base.css
|-- views/
| |-- userView.vue
| |-- moviesDetail.vue
|-- App.vue
|-- app.js
|-- entry-client.js
|-- entry-server.js
|-- index.template.html
server.js

​ build下存放与项目构建相关的配置文件,public 中存放着项目中用到的一些静态资源文件,dist存放着工程构建打包的输出文件,src目录下为项目的主要源代码文件,可以看出这是一个基于Vue的典型前端项目。

​ 其中包含了组建目录components、路由设置router、基于Vuex状态管理的store、页面视图views及相应的入口文件。接下来将对该Vue项目的服务器端渲染过程进行简要介绍。

1.服务器端渲染所返回的HTML文件

​ 服务器端渲染的目的是为浏览器返回一个可供直接进行绘制的HTML文件,从而减少首屏出现的时间,在该项目中文件 index.template.html 即为最终所要生成的服务器端渲染结果的模板文件,其内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>{{title}}</title>
    <meta charset="UTF-8" />
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui" />
    <link rel="shortcut icon" sizes="48x48" href="/public/logo-48.png">
    <meta name="theme-color" content="#f60">
    <link rel="manifest" href="/manifest.json">
    <% for (var chunk of chunk.files){
      for (var file of chunk.files){
        if (file.match(/\.(js|css)$/)){
          %>
    <link rel="<%= chunk.initial?'preload':'prefetch' %>" href="<%=htmlWebpackPlugin.files.publicPath + file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><%}}}%>
  </head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>

​ 在head标签中包含了title、 显示设置、样式文件及一些预加载和预获取的文件配置,而在body标签中则通过注释的方式(vue-ssr-oulet) 标定出了服务器端渲染DOM所要注入的节点位置。

2.输出HTML文件的编译过程

​ 明确了模板文件 index.template.html的作用后,接下来我们分析该模板文件如何处理并最终生成给浏览器直接渲染的HTML文件,这个过程必定是通过webpack构建完成的,可在配置文件中搜索模板文件的文件名,在webpack.client.config.js 中查到如下配置信息:

const config = merge(base,{
  plugins:[
    //...其他配置
    new HTML.Plugin({
      template:'src/index.template.html'
    })
  ]
})

​ 该配置插件HTMLPlugin的作用是编译入参中指定的模板文件,并在dist目录下生成最终所需的index.html文件。要追溯编译构建过程,可从启动项目的命令npm run dev开始查询服务器启动代码server.js,代码如下:

//定义服务器渲染结果字符串
let renderer
//若为生产环境
if (!isprod){
  //在生产环境下,使用Server bundle 和 index.template.html 模版生产渲染内容的字符串
  //通过 vue-ssr-webpack-plugin生产所需的 Server bundle
  const bundle = require('./dist/vue-ssr-bundle.json')
  //同步读取预编译好的HTML文件
  const template = fs.readFileSync(resolve('./dist/index.html'),'utf-8')
  renderer = createRenderer(bundle,template)
}else{
  //在开发环境下,调用setup-dev-server 启动一个开发服务器监控项目文件修改并进行热加载
  require('./build/setup-dev-server')(app,(bundle,template) => {
    renderer = createRenderer(bundle,template)
  })
}

​ 这里是服务器启动处理的一个中间环节,一方面开发环境下更具体的处理流程在 /build/setup-dev-server.js文件中进行,在其中会启动一个开发调试用的服务器:另一方面当文件修改发生后,会调用createRenderer方法生成服务器返回给浏览器的HTML文件中的内容字符串。

​ 在服务器启动环节中的主要操作分别根据 webpack.client.config.js 和 webpack.server.config.js的配置文件构建打包出Client Bundle和Server Bundle,其中处理Server Bundle的代码如下:

//引入服务器端代码构建配置
const serverConfig = require('./webpack.server.config')
//引入内存文件系统
const MFS = require('memory-fs')
const serverCompiler = webpack(serverConfig)
const mfs = new MFS()
serverCompiler.outputFileSystem = mfs
//监控文件修改时的处理
serverCompiler.watch({},(err,stats) => {
  if(err) throw err
  stats = stats.toJson()
  stats.errors.forEach(err => console.error(err))
  stats.warnings.forEach(err => console.warn(err))
  //若构建无误则输出 Bundle 文件
  const bundlePath = path.join(serverConfig.output.path, 'vue-ssr-budle.json')
  bundle = JSON.parse(mfs.readFileSync(bundlePath,'utf-8'))
  //若指定了模版文件,则 createRenderer 方法进行服务器渲染操作
  if(template){
    cd(bundle,template)
  }
})

3.服务器端渲染方法

createRenderer 方法代码如下:

function createRenderer(bundle,template){
  //将构建的服务器端bundle包与HTML模版文件一起渲染成最终HTML文件内容
  return require('vue-server-renderer').createBundleRenderer(bundle,{
    template,
    cache:require('lru-cache')({max:1000,maxAge:1000 * 60 *15})
  })
}

​ 这里引用了Vue 官方所提供的服务器端渲染工具包vue-sever-renderer.具体使用细节及配置说明可参考官方给出的文档,这里仅梳理流程和处理逻辑,通计createBundleRenderer方法可根据上一步构建生成的Server Bundle和模板配置选项共同生成一个BundleRenderer 实例,该实例包含两个成员方法renderToString 和 renderToStream,它们分别可以将服务器渲染的内容以字符串和可读数据流的形式输出,输出结果即为浏览器请求首屏页面后服务器端返回可供直接渲染的结果。

​ 可以看出该项目并非对所有页面都进行了服务器端渲染,它仅对首屏页面的项部进行了服务器端渲染,下半部分的资源列表采用的是客户端渲染,因此能够根据实际的业务情况去平衡需要客户端渲染与服务器端渲染是十分必要的。

​ 一方面服务器端渲染大部分解决的应当是首屏性能问题,对首屏涉及页面进行服务器端渲染更加符合逻辑和应用场景;另一 方面处理时还需对服务器端与客户端计算能力进行平衡,虽然我们需要合理利用服务器端计算能力,但也不能将客户端计算能力闲置下来。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2022-2023 alan_mf
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信