zoukankan      html  css  js  c++  java
  • vue-ssr 文档备注

    https://ssr.vuejs.org/zh/universal.html

    基本用法

      通过vue-server-renderer插件的createRenderer方法创建一个renderer,再调用这个renderer的renderToString(app),将一个带有template和data的vue实例渲染为字符串。

      渲染出来的字符串可以拼接到html字符串中再返回,也可以使用模板(createRenderer创建renderer的时候指定template,这个模板中使用 vue-ssr-outlet 来指定插入位置),在这个模板中使用{{}}可以渲染出转义后的字符串,{{{}}}可以渲染出没转义的字符串,以上两个插值可以通过renderToString传入的context来指定。

      以上的模板支持更高级的用法:

    1. 当时用vue单文件的时候自动注入关键css
    2. 当时用客户端清单的时候,自动注入资源link
    3. 当vuex状态数据在客户端进行混合的时候,能防范CSS

    代码规范

      同一份代码需要运行在客户端和服务器。

      对于每个请求服务器都需要创建一个新的app(避免出现跨请求的状态污染),渲染这个app之前需要再服务器端预先获取数据,也就意味着在开始渲染之前,app的状态就应该是确定的,所以,响应式的数据变化是没有必要的,默认被禁止,禁止数据转换为响应式对象。

      服务端渲染仅仅会执行beforeCreate和created这两个钩子。不要在这两个钩子中使用setInterval,而应该把这些带副作用的api放在beforeMount和mounted中。

      跨平台的代码不应该使用平台限定的api,如window和document。而应该使用通用api或者库(如axios,提供了服务端和客户端相同的api来访问网络)。如果要访问浏览器api,应该放到客户端的钩子函数中执行

    代码结构

      不在每次请求来的时候直接创建新的vue实例,而是导出一个工厂函数createApp来创建vue实例(同样的方式创建router,store和event bus)。也可以不通过工厂函数来生成实例,仅当使用bundle render而且配置了{runInNewContext:true},但这么做的话会消耗性能,因为每个请求都会创建一个新的vm context。

      使用webpack来打包客户端(client bundle用于发给浏览器来进行html混合)和服务器(server bundle被服务器使用,用于SSR)。

      router.js:返回一个工厂函数,内部创建并且配置了路由组件的映射,使用的组件是动态import的异步组件。

      app.js:在这里导出一个createApp工厂函数来返回vue实例和router,内部创建vue实例,获取router.js获取路由,然后将router注入到vue实例中,获取store.js,注入store。这个文件用于为其他地方提供vue实例。

      entry-client.js:前端逻辑,在浏览器运行,引入app.js,获取vue实例和router,当router.onReady之后才进行挂载。通过store.replaceState(window.__INITIAL_STATE__)将state序列化回来使用

      entry-server.js:在服务器运行,每次SSR都会调用这里的代码,进行数据预获取和获取实例。导出一个函数,该函数返回一个promise。内引入app.js,获取vue实例,根据函数参数context脚本化地设置路由router.push(url),当router.onReady之后,通过router获取到当前的组件(服务端渲染仅仅是渲染即将要显示的组件,其他异步组件还是要等到要显示的时候才在浏览器下载和渲染),调用当前组件的异步获取数据方法,获取到了之后,将store.state设置到context.state中(这里相当于把异步数据通过context传递到了外部的server.js),最后对vue实例进行resolve(app)。用webpack将这个文件打包为server-bundle.js

      server.js:处理get * 请求,然后获取req.url。引入server-bundle.js,给他传递一个context(里面包含了req.url),返回了一个promise,在promise中对get请求进行响应,把app renderToString出去(以上resolve(app)就把vue实例传递了过来)。当renderer(由createBundleRenderer创建)启用了template设置,则context传递给renderToString渲染时,context.state就会自动序列化到html中。

      在客户端必须要onReady之后再挂载,再服务器必须在onReady之后再进行vue实例的renderToString和响应。这么做的原因是:

     the router must resolve async route components ahead of time in order to properly invoke in-component hooks. 

    数据预获取

      在SSR之前需要把页面依赖的异步数据,预先获取出来存放进store中,然后把state序列化内联到html中,客户端直接把内联的数据state获取出来,然后挂载vue实例即可

      store.js:返回一个工厂函数createStore,函数返回一个新的store,store可以根据id,设置自己state.item。

      在组件中的computed对象通过this.$store.state.item获取store中的item,组件的定义对象中写一个方法,该方法获取store和router,根据路由参数dispatch一个action。这个方法对于组件来说是静态方法。

      服务端的数据预获取是通过把state序列化到html中。客户端的数据预获取先不看

    客户端的混合

      在客户端处把服务器发来的html进行转换为可由vue来管理的dom,仅仅执行挂载即可。当vue发现根元素上有data-server-rendered="true"这个值时,就会以混合的方式进行挂载转换。如果已有的html与客户端vue实例中的结构不一致,则混合失败(注意tbody)。在开发模式,混合失败会抛弃现有的html,全部在客户端重新生成;而对于产品模式,则保留当前的html,而不重新生成。

      在客户端进行混合后,data-server-rendered="true" 会从根元素上消失。如果混合失败的话,浏览器会报错:

    [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.

      服务器端把App这个组件(跟元素为#app)渲染到了插槽的位置上,到了浏览器端(App这个组件的dom已经存在了),需要把App这个组件重新挂载回#app上。template仅仅写个插槽即可,而不需要写一个空的div#app标签。 

    bundle Renderer

      服务器通过require使用server bundle,当修改了源码,则需要重启服务器,而且因为nodeJS不支持sourceMap,对server bundle的调试很不方便。

      可以通过vue-server-renderer提供的createBundleRenderer来解决这个问题(可以使用sourceMap、hmr、关键css注入,注入用户清单等)。使用方式如下:通过webpack插件,生成server-bundle.json,然后将这个json传递给createBundleRenderer。

      回顾之前的renderer获取方式:通过 require('vue-server-renderer').createRenderer()来获取的。使用都是在server.js中使用

    构建配置

      createBundleRenderer需要知道server-bundle以及客户端的清单文件信息,这样生成出来的renderer,才能往生成出来的html注入preload标签,以及客户端要执行的脚本标签。

    对于runInNewContext这个设置:

    1. true(默认),对于每个请求,server bundle都会运行一次,而且每次运行的环境不同,并且与server线程的环境是隔离的(global对象不同)
    2. false,仅在第一次渲染的时候,执行server bundle,对于其他的请求到来时,不再执行server bundle。因为entry-server.js写成是一个模块,所以这种执行结果更加符合模块的执行思路(仅在第一次需要的时候执行一次,其余都只是读取值,而不执行)。而且server bundle的执行环境与server进程一致,共享同一个global对象
    3. "once",与false类似,唯一的区别在于server bundle的执行环境与server进程是隔离的,即global对象不同。

    结论:使用false或“once”能提升性能,因为server bundle仅执行一次,使用once更好,因为环境隔离,防止污染服务器进程

    服务端

      将entry-server.js作为entry、new VueSSRServerPlugin作为插件,会打包生成一个server-bundle.json,只需要将这个文件路径传递给createBundleRenderer作为第一个参数即可。

      entry-server.js用于数据预获取,然后返回app。如果没有异步操作预获取数据的话,直接在export default app即可,否则有异步操作,要求代码如下:

    import { app } from './app.js'
    
    export default context => {
        return new Promise((resolve, reject) => {
            // 处理异步数据、router onReady等
            // 在异步回调中执行 resolve(app) 即可
        })
    }
    View Code

    客户端

      生成清单文件。通过entry-client.js生成client-manifest.json清单文件,再传递给createBundleRenderer。

      这个json文件包含了其他一些打包生成的js文件的信息(每个异步组件打包成一个独立的js文件):

    {
      "publicPath": "/dist/",
      "all": [
        "0.bundle.js",
        "1.bundle.js",
        "main.bundle.js",
        "manifest.bundle.js",
        "0.bundle.js.map",
        "1.bundle.js.map",
        "main.bundle.js.map",
        "manifest.bundle.js.map"
      ],
      "initial": [
        "manifest.bundle.js",
        "main.bundle.js"
      ],
      "async": [
        "0.bundle.js",
        "1.bundle.js"
      ],
      "modules": {
        "58087528": [
          2,
          6
        ],
        "145842ed": [
    View Code

      manifest.bundle.js作用是加载chunk,有如下代码(这个在router例子中是通过commonChunkPlugin生成的):

    /******/         // start chunk loading
    /******/         var head = document.getElementsByTagName('head')[0];
    /******/         var script = document.createElement('script');
    /******/         script.type = 'text/javascript';
    /******/         script.charset = 'utf-8';
    /******/         script.async = true;
    /******/         script.timeout = 120000;
    /******/
    /******/         if (__webpack_require__.nc) {
    /******/             script.setAttribute("nonce", __webpack_require__.nc);
    /******/         }
    /******/         script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
    View Code

      main.bundle.js体积最大,行数为15k以上,里面包含了entry-client.js的逻辑(估计还有vue的代码才会这么大)。

      entry-client.js中为客户端需要执行的逻辑,但template中没有这个脚本对应的标签,需要通过createBundleRenderer来自动注入,所以要生成客户端的清单文件,传递给这个函数。

    手动注入资源

      当我们需要更加细粒度地控制资源或者不通过模板注入资源的时候,可以手动注入资源。

      在创建renderer的时候,设置 inject:false即可。

    CSS的管理

      推荐在vue单文件的style标签内管理css,它有如下好处:

    • 提供作用域
    • postcss预处理器
    • 开发阶段的热加载
    • vue-loader内置的vue-style-loader在我们使用bundleRender的时候,可以提供组件的关键css(要启用template选项)。
    • main chunk中的css被抽取出来,有利于缓存(使用的是extra-text-webpack-plugin),抽取出来的css被自动注入到template中(需要做额外配置)。

    管理header

      提供了一种手段来从组件内设置外部的context,即在组件created或者beforeCreated的时候,通过this.$ssrContext可以直接访问到context,添加数据后可以通过context插值的形式将组件内的数据显示到template上。

      因为浏览器端装在的时候,并没有$ssrContext这个对象,所以需要进行环境判断(DefinePlugin)

    缓存

      虽然vue ssr已经很快了,但性能比不上纯字符串模板渲染,因为ssr需要创建组件实例和虚拟dom节点,所以巧妙使用缓存策略,可以提升性能。

    页面级别的缓存

      大部分时候应用程序需要依赖额外的数据,动态的内容不能被缓存,然而如果内容和用户无关(not user-specific),如相同的url对所有用户都显示相同的页面,我们可以使用一个策略叫做“微缓存”来大幅度提升高拥堵时程序的性能。这通常在nginx层实现,但我们也可以在node层实现,思路如下:首先根据请求,判断响应的数据是否可以被缓存,可以的话就去拿缓存看看能不能命中。命中则直接返回缓存,否则进行渲染renderToString,然后把结果缓存起来(url为key,html为value,缓存的期限较短,如1s)。这种策略可以使服务器最多1s渲染一次页面。

    组件级别的缓存

      vue-server-renderer内置了这种缓存,启用这个功能,只需要在创建renderer的时候,提供一个缓存实现。启用组件级别的缓存,对组件的定义有如下要求:

    1. 拥有一个唯一的name属性,缓存的时候会用这个name作为key
    2. 拥有一个能反映组件外观的映射函数serverCacheKey,这个函数返回一个常量(如总是返回true)的话会导致组件总是被缓存,这在静态组件中比较好用。猜测这个原理:组件渲染的内容为value,而key = comp.name + comp.serverCacheKey(comp.props),以两个值来映射一个渲染结果,因为很好理解组件的渲染结果 = 组件 + 组件的props。

      因为当一个组件缓存被renderer命中时,这个组件的子组件也不会再次渲染。所以当子组件依赖于全局状态、或者子组件会对context进行修改的时候,这个组件就不应该被缓存

  • 相关阅读:
    html 复选框
    html 单选框
    C++创建类的对象(类的初始化)的方法
    C++创建类的对象(类的初始化)的方法
    C#基础实现URL Unicode编码,编码、解码相关整理
    C#基础实现URL Unicode编码,编码、解码相关整理
    js 发送http请求
    js 发送http请求
    C++的三种实例化对象方式
    C++的三种实例化对象方式
  • 原文地址:https://www.cnblogs.com/hellohello/p/8243400.html
Copyright © 2011-2022 走看看