美文网首页
构建同构渲染

构建同构渲染

作者: 翔子丶 | 来源:发表于2021-03-28 18:21 被阅读0次

接上篇使用 Vue SSR 渲染一个 Vue 实例

构建流程
  • 客户端动态交互功能

    服务端渲染只是把 vue 实例处理为纯静态的 html 片段返回给客户端,对于 vue 实例当中需要客户端动态交互的功能本身没有提供

    ...
    const app = new Vue({
        template: `
          <div id="app">
            <h1>{{ message }}</h1>
            <h2>客户端动态交互</h2>
            <div>
              <input v-model="message">
            </div>
            <div>
              <button @click="onClick">点击测试</button>
            </div>
          </div>
        `,
        data: {
          message: '拉钩'
        },
        methods: {
          onClick () {
            console.log('hello world')
          }
        }
    })
    ...
    

    返回到客户端的页面是没有交互功能的

image-20210325083058071.png
  • 基本思路
image-20210325083156083.png

源代码 --> webpack 打包 --> Node Server 服务

此时,我们应用中只有 Server entry 服务端入口,只能处理服务端渲染,想要实现服务端渲染内容拥有动态交互能力,还需要 Client entry 客户端入口,用于处理客户端渲染,接管服务端渲染内容,激活成一个动态页面

Server entry 通过 webpack 最终打包成 Server Buner,主要用于做服务端渲染(SSR)

Client entry 通过 webpack 最终打包成 Client Buner,发送给浏览器,用于接管服务端渲染好的静态页面,对他进行激活成一个动态的客户端应用

源码结构

根据这幅图实现同构应用,既要实现服务端渲染,也要实现客户端渲染能够处理客户端动态交互

一个基本项目可能像是这样:

src
├── components
│   ├── Foo.vue
│   ├── Bar.vue
│   └── Baz.vue
├── App.vue
├── app.js # 通用 entry(universal entry)
├── entry-client.js # 仅运行于浏览器
└── entry-server.js # 仅运行于服务器
  1. 使用 webpack 的源码结构重新设置代码结构

    创建 src 目录,添加 App.vue 和 app.js、entry-client.js、entry-server.js,具体请查看 webpack 的源码结构

  2. 通过 webpack 打包构建,真正完成同构应用

    安装依赖

    npm i vue vue-server-renderer express cross-env
    

    安装开发依赖

    npm i -D webpack webpack-cli webpack-merge webpack-node-externals @babel/core @babel/plugin-transform-runtime @babel/preset-env babel-loader css-loader url-loader file-loader rimraf vue-loader vue-template-compiler friendly-errors-webpack-plugin
    

    配置文件及打包命令

    • 初始化 webpack 打包配置文件,具体见github

      build
      ├── webpack.base.config.js # 公共配置
      ├── webpack.client.config.js # 客户端打包配置文件
      └── webpack.server.config.js # 服务端打包配置文件
      
    • 在 npm scripts 中配置打包命令

      "scripts": {
      "build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js",
      "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js",
      "build": "rimraf dist && npm run build:client && npm run build:server"
      },
      
启动服务
  1. 通过yarn build打包生成客户端和服务端文件

  2. 具体实现参考Bundle Renderer 指引

  3. 替换 server.js 中 createRender 为 createBundleRenderer,接收 serverBundle、template 和 clientManifest 为参数,启动服务

    ...
    const serverBundle = require('./dist/vue-ssr-server-bundle.json')
    const template = fs.readFileSync('./index.template.html', 'utf-8')
    const clientManifest = require('./dist/vue-ssr-client-manifest.json')
    
    renderer = require('vue-server-renderer').createBundleRenderer(serverBundle, {
        template,
        clientManifest,
    })
    ...
    

    此时在浏览器端获取不到 dist 下的 js 文件,是因为没有在服务器当中有 dist 中的资源传递给客户端,挂载中间件 server.use('/dist')

    image-20210326082507311.png
  1. 挂载处理静态资源中间件

    const express = require('express')
    // 得到express实例
    const server = express()
    // 挂载处理静态资源中间件
    //dist应该和客户端打包的出口的publicPath保持一致 使用express.static处理静态资源
    // 当请求到/dist开头的资源时,使用express.static尝试在./dist目录下查找并返回
    server.use('/dist', express.static('./dist'))
    

    此时重新刷新浏览器,js 文件正常加载,客户端交互功能正常

解析渲染流程

服务端渲染如何输出

  • 从路由着手,当客户端请求后匹配路由,调用 renderer 渲染器的 renderer.renderToString 方法

  • renderToString 将 vue 实例渲染为字符串发送给客户端,但是 renderToString 中并没有 vue 实例,vue 实例是从哪来的呢?

  • renderer 是通过 createBundleRenderer 创建,传入的 serverBundle 对应vue-ssr-server-bundle.json文件,是通过 server.entry.js 构建出来的结果文件

    {
      "entry": "server-bundle.js", // 入口
      "files": {...}, // 所有构建结果资源列表
      "maps": {...} //源代码 source map 信息
    }
    
  • renderer 在渲染时会加载 serverBundle 的入口,得到 entrry-server.js 中创建的 vue 实例

  • 对此 vue 实例进行渲染,并将渲染的结果注入到 template 模板当中,最终将 template 模板发送到客户端

客户端渲染如何接管并激活

  • 客户端需要将打包的 js 脚本注入到页面当中,是怎么做的呢?

  • 在 createBundleRenderer 中,配置的 clientManifest,对应 vue-ssr-client-mainfest.json 文件,是客户端打包资源的构建清单

    {
      "publicPath": "/dist/",
      "all": ["app.5c8c7bcfd286b41168f3.js", "app.5c8c7bcfd286b41168f3.js.map"],
      "initial": ["app.5c8c7bcfd286b41168f3.js"],
      "async": [],
      "modules": {}
    }
    /*
    publicPath:访问静态资源的根相对路径,与 webpack 配置中的 publicPath 一致
    all:打包后的所有静态资源文件路径
    initial:页面初始化时需要加载的文件,会在页面加载时配置到 preload 中
    async:页面跳转时需要加载的文件,会在页面加载时配置到 prefetch 中
    modules:项目的各个模块包含的文件的序号,对应 all 中文件的顺序;moduleIdentifier和
    和all数组中文件的映射关系(modules对象是我们查找文件引用的重要数据),实际作用是当客户端在实际运行的时候,加载的模块用到哪些资源,vue 会根据此信息去加载那些资源
    */
    
  • 当客户端加载完成后,客户端的 js 是如何工作的呢?

    详细请查看客户端激活

相关文章

网友评论

      本文标题:构建同构渲染

      本文链接:https://www.haomeiwen.com/subject/ikzphltx.html