• 服务端渲染原理及实现入门


    一、 CSR vs SSR

    不同于传统拉取JS进行解析渲染的CSR(JS负责进行页面渲染),SSR实现了服务器端直接返回Html代码让浏览器进行渲染。
    由此,我们就很容易理解以下代码实现了一个页面SSR:
    // server.js

    var express = require('express')
    var app = express()
    app.get('/', (req, res) => {
     res.send(
     `
       <html>
         <head>
           <title>hello</title>
         </head>
         <body>
           <h1>hello, this is SSR content.</h1>
           <p>now, let's begin to learn SSR.</p>
         </body>
       </html>
     `
     )
    })
    app.listen(3001, () => {
     console.log('listen:3001')
    })
    

    SSR优点:缩短首屏加载时间,便于SEO。

    二、Vue项目的SSR实现

    刚刚我们仅仅做到了让服务器返回一段html字符串,那么,要如何实现Vue项目的服务端渲染呢?
    由于整个项目的复杂性,我们可能一时无从下手,但没关系,我们可以先从实现一个Vue组件的服务端渲染开始,在此之前,我们先从Vue实例的SSR开始吧!

    1. 实现一个Vue实例的SSR

    首先实现一个Vue实例,这个倒是简单,那么问题来了:“我们要如何将它转换成html代码返回给浏览器呢?
    大家都学过vue,当然知道Vue的标签是基于虚拟DOM(JS对象)的,在客户端渲染中也是采用一定方法将虚拟DOM渲染为真实DOM的,那么服务端的渲染流程也是通过虚拟DOM的编译来完成的,编译虚拟DOM的方法是renderToString。在Vue中,vue-server-renderer 提供一个名为 createBundleRenderer 的 API,这个API用于创建一个 render,并且自带renderToString方法。
    官网实现代码如下:
    // server.js

    const Vue = require('vue')
    const server = require('express')()
    // 创建一个 renderer
    const renderer = require('vue-server-renderer').createRenderer()
    server.get('*', (req, res) => {
      // 第 1 步:创建一个 Vue 实例
      const app = new Vue({
        data: {
          url: req.url
        },
        template: `<div>访问的 URL 是: {{ url }}</div>`
      })
      // 第 2 步:将 Vue 实例渲染为 HTML
      renderer.renderToString(app, (err, html) => {
        if (err) {
          res.status(500).end('Internal Server Error')
          return
        }
        res.end(`
          <!DOCTYPE html>
          <html lang="en">
            <head>
              // <meta charset="utf-8"> // 加上这一行就不会出现乱码了
              <title>Hello</title>
            </head>
            <body>${html}</body>
          </html>
        `)
      })
    })
    server.listen(8080)
    

    启动express服务,再浏览器上打开对应端口,页面就能显示出你在Home组件中编写的内容了。但不出意外的话,大家看到页面渲染出来的是一段乱码,这是因为官网提供的示例代码返回的html字符串里没有带 ,加上它就OK了,在上面代码中直接取消这一行注释, 至此,我们初步实现了一个Vue实例的服务端渲染。
    TODO:(modify)
    当你在渲染 Vue 应用程序时,renderer 只从应用程序生成 HTML 标记 (markup)。在这个示例中,我们必须用一个额外的 HTML 页面包裹容器,来包裹生成的 HTML 标记。
    为了简化这些,你可以直接在创建 renderer 时提供一个页面模板。多数时候,我们会将页面模板放在特有的文件中,例如 index.template.html:

    <!-- index.template.html -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) -->
        <title>{{ title }}</title>
        <!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
        {{{ metas }}}
      </head>
      <body>
        <!--vue-ssr-outlet-->
      </body>
    </html>
    

    注意 注释 -- 这里将是应用程序 HTML 标记注入的地方。
    然后,我们可以读取和传输文件到 Vue renderer 中:
    // server.js

    const renderer = require('vue-server-renderer').createRenderer({
      template: require('fs').readFileSync('./index.template.html', 'utf-8')
    })
    renderer.renderToString(app, (err, html) => {
      console.log(html) // html 将是注入应用程序内容的完整页面
    })
    

    我们可以通过传入一个"渲染上下文对象",作为 renderToString 函数的第二个参数,来提供模板插值数据,也可以与 Vue 应用程序实例共享 context 对象,允许模板插值中的组件动态地注册数据。
    完整代码示例:
    // server.js

    const Vue = require('vue');
    const server = require('express')();
    const template = require('fs').readFileSync('./index.template.html', 'utf-8');
    const renderer = require('vue-server-renderer').createRenderer({
      template,
    });
    const context = {
        title: 'vue ssr',
        metas: `
            <meta name="keyword" content="vue,ssr">
            <meta name="description" content="vue srr demo">
        `,
    };
    server.get('*', (req, res) => {
      const app = new Vue({
        data: {
          url: req.url
        },
        template: `<div>访问的 URL 是: {{ url }}</div>`,
      });
      renderer.renderToString(app, context, (err, html) => {
        console.log(html);
        if (err) {
          res.status(500).end('Internal Server Error')
          return;
        }
        res.end(html);
      });
    })
    server.listen(8080);
    

    TODO:(个人理解)
    以下是个人对SSR的理解:服务端渲染实际是一套代码的两次应用,所谓的一套代码就是拿出server.js外面去的vm实例,上面之所以简单是因为我们在server内部创建的vm实例,一旦将vm拿出去,在server.js外部引入,那么涉及的就麻烦了。
    这里分两条线说,一个是在server.js外面创建一个app.js;结果是无法引入到server中,而这个也不是关注的重点;
    另一条线是使用vue-loader创建一个vm实例,然后引入到server中,整个vue渲染就在解决这个问题,解决引入的问题,解决引入之后与前端混合的问题。下面贴上简单案例的实现代码。
    因为不能直接应用.vue文件以及外部的js文件,所以需要借助webpack,借助webpack将vue实例,转译为node可用代码,以及对前端代码进行转译。
    以vue init webpack-simple vuessr0 为基础的vue-ssr案例
    官网中有提到:每个请求应该都是全新的、独立的应用程序实例,以便不会有交叉请求造成的状态污染。
    意思就是:每次服务端渲染都要渲染一个新的app,不能再用上一次渲染过的app对象去进行下一次渲染,这是由于app已经包含上一次渲染过的状态会影响我们渲染内容,所以每次都要创建新的app,即为每个请求创建一个新的根Vue实例。因此,我们不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例:
    // app.js

    const Vue = require('vue')
    module.exports= function createApp (context) {
      return new Vue({
        data: {
          title: context.title, 
          url: context.url
        },
        template: `
          <div>
            <h2>{{ title }}</h2>
            <div>访问的 URL 是:{{ url }}</div>
          </div>
        `
      })
    }
    

    服务器代码只需要更改下vue实例的生成方式就可以了:
    // server.js

    const createApp = require('./app')
    const server = require('express')()
    // 创建一个 renderer
    const renderer = require('vue-server-renderer').createRenderer({
      template: require('fs').readFileSync('./index.template.html', 'utf-8')
    })
    server.get('*', (req, res) => {
      // 创建一个"渲染上下文对象"
      const context = {
        title: 'vue ssr',
        metas: `
          <meta name="keyword" content="vue,ssr">
          <meta name="description" content="vue srr demo">
        `,
        url: req.url
      }
      const app = createApp(context)
      // 第 2 步:将 Vue 实例渲染为 HTML
      renderer.renderToString(app, context, (err, html) => {
        if (err) {
          res.status(500).end('Internal Server Error')
          return
        }
        console.log(html)
        res.end(html)
      })
    })
    server.listen(8080)
    

    2. 实现一个Vue组件的SSR

    既然涉及到组件,那我们就必须加入路由了,官方建议使用 vue-router。同时,需要webpack来构建项目。
    首先创建router.js,实现给每个请求创建一个新的 router 实例,我们在router.js中导出一个 createRouter 函数,然后更新app.js。
    // router.js

    import Vue from 'vue'
    import Router from 'vue-router'
    Vue.use(Router)
    export function createRouter () {
      return new Router({
        mode: 'history',
        routes: [
          // ...
        ]
      })
    }
    

    // app.js

    const Vue = require('vue')
    module.exports= function createApp (context) {
      return new Vue({
        data: {
          title: context.title, 
          url: context.url
        },
        template: `
          <div>
            <h2>{{ title }}</h2>
            <div>访问的 URL 是:{{ url }}</div>
          </div>
        `
      })
    }
    import App from './App.vue'
    import { createRouter } from './router'
    export function createApp () {
      // 创建 router 实例
      const router = createRouter()
      const app = new Vue({
        // 注入 router 到根 Vue 实例
        router,
        render: h => h(App)
      })
      // 返回 app 和 router
      return { app, router }
    }
    

    首先实现一个Vue组件 src/views/home/index.vue,那么接下来我们仍旧是通过renderToString方法编译Vue组件来实现服务端渲染。

    const Vue = require('vue');
    const server = require('express')();
    // 引入Vue组件
    import Home from './containers/Home';
    const template = require('fs').readFileSync('./index.template.html', 'utf-8');
    const renderer = require('vue-server-renderer').createRenderer({
      template,
    });
    const context = {
        title: 'vue ssr',
        metas: `
            <meta name="keyword" content="vue,ssr">
            <meta name="description" content="vue srr demo">
        `,
    };
    server.get('*', (req, res) => {
      // 替换为Vue组件
      const app = new Vue({
        data: {
          url: req.url
        },
        template: `<div>访问的 URL 是: {{ url }}</div>`,
      });
      renderer.renderToString(app, context, (err, html) => {
        console.log(html);
        if (err) {
          res.status(500).end('Internal Server Error')
          return;
        }
        res.end(html);
      });
    })
    server.listen(8080);
    

    3. 初识同构

    首先我们来了解下什么是重构?--->
    前提:VUE框架用于构建客户端应用,在浏览器中输出Vue组件,生成并操作DOM。将同一个组件渲染为服务器端的HTML字符串发送到浏览器,然后将这些静态标记“激活”为客户端上可交互的应用程序。
    服务器渲染的Vue.js应用程序为"同构"或"通用",应为应用程序的大部分代码都可以在服务端和客户端运行。
    通俗的讲,就是一套Vue代码在服务器上运行一遍,到达浏览器又运行一遍。服务端渲染完成页面结构,浏览器端渲染完成事件绑定,这个完成事件绑定的过程就是静态标记“激活”为客户端上可交互应用程序的过程。
    那么如何进行浏览器端的事件绑定呢?
    首先我们要明白:
    只要开启express的静态文件服务,前端的script就能拿到控制浏览器的JS代码啦!
    4. node作中间层及请求代码优化
    作用:解决前后端协作问题
    场景:
    在不用中间层的前后端分离开发模式下,前端直接请求后端接口获得返回的数据,但这个返回数据的数据格式也许并非是前端需要的,但出于性能原因或其他因素无法更改接口,就需要前端来做一些数据处理操作,这无疑会产生前端性能损耗,尤其当前端处理数据量很大的时候,甚至会影响用户体验。于是引入node中间层,用于替代前端做数据处理操作,中间层的工作流:前端发送请求--->请求node层的接口--->node对于相应的前端请求做转发,用node去请求真正的后端接口获取数据--->获取后再由node层做对应的数据计算等处理操作--->返回给前端。

  • 相关阅读:
    课堂练习-找水王绪
    输入法评估
    课堂练习-找水王
    课堂练习—电梯调度
    第一阶段冲刺总结
    《你的灯亮着吗》阅读笔记Ⅱ
    《你的灯亮着吗》阅读笔记Ⅰ
    软件工程——寻找水桶
    软件工程——寻找水王
    软件工程——典型用户
  • 原文地址:https://www.cnblogs.com/lynn-z/p/14427229.html
Copyright © 2020-2023  润新知