精华内容
下载资源
问答
  • Vue服务端渲染

    千次阅读 2019-08-09 18:26:20
    一、前言 1、服务端渲染图解 2、简介服务端渲染 ...

    一、前言                                                                    

                                                    1、服务端渲染图解

                                                    2、简介服务端渲染

                                                    3、vue-cli脚手架项目创建,实现客户端渲染和服务端渲染

                                                    4、演示demo地址:https://github.com/4561231/ssr-vue

     

    二、主要内容                                                             

    1、服务端渲染图解参照另一篇:服务端渲染和客户端渲染

     

    2、简介服务端渲染

    Vue.js是构建客户端应用程序的框架,默认情况下,可以在浏览器中输出vue组件,进行生成Dom和操作DOM, 然而,也可以将同一个组件渲染成为服务端的字符串,将他们直接发送到浏览器,最后将这些静态标记“激活”为客户端上完全可以交互的应用程序。也就是你先在前端写好组件页面,然后交到服务端,服务端需要通过他自家的某个程序插件,然后将客户端的组件生成对应的html字符串,最后发送给浏览器。然后浏览器响应出来页面。

     

    3、 新建项目,安装依赖路,创建服务端代码

      (1)server.js

     

    const Vue = require('vue')
    const express = require('express')();
    
    const renderer = require('vue-server-renderer').createRenderer();
    
    //创建vue实例
    const app = new Vue({
        template:'<div>hello vue</div>'
    })
    
    //服务器渲染的核心就在于:
    //通过vue-server-renderer插件的renderToString()方法,将vue实例转化为字符串插入到html中
    express.get('/',(req,res)=>{
        renderer.renderToString(app, (err,html)=>{
            if(err){
                return res.state(500).end('运行错误')
            }
              //返回给浏览器一串html字符串
            res.send(`<!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>服务端渲染</title>
            </head>
            <body>
                ${html}
            </body>
            </html>`)
        })
    })
    
    express.listen(8888, ()=>{
        console.log('服务器已经启动')
    })

     

     

     

      (2)具体实现原理

      (3)测试,发现响应回来的文件里面有内容,这样也说明了服务端渲染是对SEO引擎比较好的

     

       (4)小结:我们使用服务端渲染是为了弥补单页面应用SEO能力不足的问题,实际上我们第一次在浏览器地址栏输入地址的时候,并且得到返回页面之后,所有的操作仍然是单页面应用在控制的,我们所做的服务端渲染,只是在平时返回单页面应用hml上增加了对应路由的页面信息,让爬虫好爬取到。

    所以项目可以分为客户端渲染和服务端渲染。

     

    4、用vue-cli项目实现服务端渲染

      (1)npm创建项目

      (2)npm run build的时候打包走的是webpack.prod.config.js这个文件,现在我们新建一个webpack.server.config.js这个文件,在服务器打包的时候走这个文件

      package.json里面加入如下代码

    "server":"webpack --config build/webpack.server.conf.js"

           webapck.server.config.js添加如下代码

     

    //引入webpack的主要配置
    const webpack = require('webpack')
    const merge = require('webpack-merge')
    
    //引入webpack.base.config这个文件是依赖这个基础文件的
    const base = require('./webpack.base.config')
    
    module.exports=merge(base, {
        target:'node',//这里要写node 目的是让后端支持require语法
        entry:"./src/entry-server.js",//当你服务端在打包的时候,就会走这个入口
        output:{
            filename:'bundle.server.js',//打包后生成的文件
            libraryTarget:'commonjs2'
        },
        plugins:[]
    })

     

      (2)由于以前生成vue实例的方式是单例的,现在我们需要在每次请求的时候生成一个vue组件

      在mian.js中修改

     

    /* eslint-disable no-new */
    //El:’#app相当于document.getElementbyId(‘#app’ 但是在node.js中识别不了这种语法,所以我们不能这样写
    /*new Vue({
      el: '#app',
      router,
      components: { App },
      template: '<App/>'
    })*/
    import {createRouter} from './router'
    export function createApp(){
    const router = createRouter();
        const app = new Vue({
            router,
            components:{App},
            template:'<App/>'
        })
    return {app};
    }

     

      同理最好将路由也写成构造函数形式

     

    export default createrRouter(){
      return new Router({
        mode:'history',
        routes:[
        {
          path:'/',
          name:'Home',
          component:Home
        },
        {
          path:'/about',
          name:'About',
          component:About
        },
    
        {
          path:'/test',
          name:'Test',
          component:Test
        }
    
        ]
      })

     

      (3)npm run server打包生成文件,并在服务端引入客户端生成的打包文件

          打包生成文件

                

      在服务端引入打包生成的文件

    //需要在服务端配置打包生成的客户端文件
    const createApp = require('./dist/bundle.server.js')['default']

       (4)服务端拿到客户端打包生成的文件,进行处理

     

    const Vue = require('vue')
    const express = require('express')();
    //需要在服务端配置打包生成的客户端文件
    const createApp = require('./dist/bundle.server.js')['default']
    const renderer = require('vue-server-renderer').createRenderer();
    //服务器渲染的核心就在于:
    //通过vue-server-renderer插件的renderToString()方法,将vue实例转化为字符串插入到html中
    express.get('*',(req,res)=>{
        const context = {url:req.url};
        createApp(context).then(app=>{//这个app就是刚刚打包之后的app
              renderer.renderToString(app, (err,html)=>{
            if(err){
                return res.state(500).end('运行错误')
            }
              //返回给浏览器一串html字符串
            res.send(`<!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>服务端渲染</title>
            </head>
            <body>
                ${html}
            </body>
            </html>`)
        })
        })//取到entry.js里面的
    })
    
    express.listen(8888, ()=>{
        console.log('服务器已经启动')
    })

     

     

       (5)测试看到一开始请求的时候就会出现内容,可以看到服务器返回的html文件中,已经有对应页面的SEO信息了。

     

     

     

      

     

              但是,还没有成功,因为现在反回过来的只是一个页面对应信息,如果你现在切换路由又会对服务器发送一次请求,单页面应用还没成功。但是vue的特点就是利用单页面,

     

    接下来需要配置客户端渲染

      (1)在package.json中配

     "client": "webpack --config build/webpack.client.conf.js"

      (2)新建webpack.client.conf.js

     

    const webpack = require('webpack')
    const path = require('path')
    
    function resolve(dir){
      return path.join(__dirname,'..',dir)
    }
    
    module.exports={
      entry:"./src/entry-client.js",//打包时走这个文件
      output:{
        path:path.resolve(__dirname,'../dist'),
        publicPath:'/dist/',
        filename:'bundle.client.js'
      },
    
      plugins:[],
    
      resolve:{
        extensions:['.js','.vue','.json'],
        alias:{
          'vue$':'vue/dist/vue.esm.js',
          '@':resolve('src'),
        }
      },
      module: {
        rules: [
          {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
              compilerOptions:{
                preserveWhitespace:false
              }
            }
    
          },
          {
            test: /\.js$/,
            loader: 'babel-loader',
            include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
          }
        ]
      }
    }

     

      (3)entry-client.js

     

    //客户端也要创建,因为客户端渲染和服务端渲染是两个不同的vue实例
    import{createApp} from './main'
    
    const {app} =createApp();
    const router =app.$router;
    
    //这里可以拿到app了,
    window.onload=function(){
        app.$mount('#app')//在window加载完成之后
    }

     

      (4)npm run client打包生成文件

      (5)同样需要在服务器中添加打包的客户端文件

     

    //客户端渲染文件
    const exp = require('express');
    ///将静态文件目录设置为:项目根目录+/dist
    express.use('/',exp.static(__dirname+'/dist'));
    //客户端打包的文件
    const clientBundleFileUrl = '/bundle.client.js'
    //在下面的模板中用script的方式引入
    <script src="${clientBundleFileUrl}"></script>

     

      (6)启动服务器,测试

    4、总结:

                (1)要做ssr服务端渲染首先需要一个Sever entry他的作用是渲染SEO的信息

                (2)如果你仅仅只有这四步操作,并没有实现单页面应用,而是每次点击的时候都会对服务端发起请求

     

           

     

       (3)要实现单页面应用,需要做客户端打包,然后将客户端打包的文件混入到服务端

      (4)单页面应用,只有第一次加载的时候才会发送请求,点击a标签的时候加载的是组件,

     

     5、

     

     

    三、总结                                                                    

    https://ssr.vuejs.org/zh/guide/structure.html#介绍构建步骤

    展开全文
  • 好在Vue 2.0后是支持服务端渲染的,零零散散花费了两三周事件,通过改造现有项目,基本完成了在现有项目中实践了Vue服务端渲染。 关于Vue服务端渲染的原理、搭建,官方文档已经讲的比较详细了,因此,本文不是抄袭...
  • 随着各大前端框架的诞生和演变,SPA开始流行,单页面应用的优势...好在Vue 2.0后是支持服务端渲染的,零零散散花费了两三周事件,通过改造现有项目,基本完成了在现有项目中实践了Vue服务端渲染。 关于ssr服务端渲染...

    随着各大前端框架的诞生和演变,SPA开始流行,单页面应用的优势在于可以不重新加载整个页面的情况下,通过ajax和服务器通信,实现整个Web应用拒不更新,带来了极致的用户体验。然而,对于需要SEO、追求极致的首屏性能的应用,前端渲染的SPA是糟糕的。好在Vue 2.0后是支持服务端渲染的,零零散散花费了两三周事件,通过改造现有项目,基本完成了在现有项目中实践了Vue服务端渲染。

    关于ssr服务端渲染的原理、搭建,官方文档已经讲的比较详细了,因此,本文不是抄袭文档,而是文档的补充。特别是对于如何与现有项目进行很好的结合,还是需要费很大功夫的。本文主要对我所在的项目中进行Vue服务端渲染的改造过程进行阐述,加上一些个人的理解,作为分享与学习。

    概述

    本文主要分以下几个方面:

    • 什么是服务端渲染?服务端渲染的原理是什么?
    • 如何在基于KoaWeb Server Frame上配置服务端渲染?

      • 基本用法
      • Webpack配置
      • 开发环境搭建

        • 渲染中间件配置
    • 如何对现有项目进行改造?

      • 基本目录改造;
      • 在服务端用vue-router分割代码;

        • 在服务端预拉取数据;
        • 客户端托管全局状态;
        • 常见问题的解决方案;

    什么是服务端渲染?服务端渲染的原理是什么?

    Vue.js是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue组件,进行生成 DOM和操作 DOM。然而,也可以将同一个组件渲染为服务器端的 HTML字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

    上面这段话是源自Vue服务端渲染文档的解释,用通俗的话来说,大概可以这么理解:

    • 服务端渲染的目的是:性能优势。 在服务端生成对应的HTML字符串,客户端接收到对应的HTML字符串,能立即渲染DOM,最高效的首屏耗时。此外,由于服务端直接生成了对应的HTML字符串,对SEO也非常友好;
    • 服务端渲染的本质是:生成应用程序的“快照”。将Vue及对应库运行在服务端,此时,Web Server Frame实际上是作为代理服务器去访问接口服务器来预拉取数据,从而将拉取到的数据作为Vue组件的初始状态。
    • 服务端渲染的原理是:虚拟DOM。在Web Server Frame作为代理服务器去访问接口服务器来预拉取数据后,这是服务端初始化组件需要用到的数据,此后,组件的beforeCreatecreated生命周期会在服务端调用,初始化对应的组件后,Vue启用虚拟DOM形成初始化的HTML字符串。之后,交由客户端托管。实现前后端同构应用。

    如何在基于KoaWeb Server Frame上配置服务端渲染?

    基本用法

    需要用到Vue服务端渲染对应库vue-server-renderer,通过npm安装:

    
    npm install vue vue-server-renderer --save
    

    最简单的,首先渲染一个Vue实例:

    
    
    // 第 1 步:创建一个 Vue 实例
    const Vue = require('vue');
    
    const app = new Vue({
      template: `&lt;div&gt;Hello World&lt;/div&gt;`
    });
    
    // 第 2 步:创建一个 renderer
    const renderer = require('vue-server-renderer').createRenderer();
    
    // 第 3 步:将 Vue 实例渲染为 HTML
    renderer.renderToString(app, (err, html) =&gt; {
      if (err) {
          throw err;
      }
      console.log(html);
      // =&gt; &lt;div data-server-rendered="true"&gt;Hello World&lt;/div&gt;
    });
    

    与服务器集成:

    
    
    module.exports = async function(ctx) {
        ctx.status = 200;
        let html = '';
        try {
            // ...
            html = await renderer.renderToString(app, ctx);
        } catch (err) {
            ctx.logger('Vue SSR Render error', JSON.stringify(err));
            html = await ctx.getErrorPage(err); // 渲染出错的页面
        }
        
    
        ctx.body = html;
    }
    

    使用页面模板:

    当你在渲染Vue应用程序时,renderer只从应用程序生成HTML标记。在这个示例中,我们必须用一个额外的HTML页面包裹容器,来包裹生成的HTML标记。

    为了简化这些,你可以直接在创建renderer时提供一个页面模板。多数时候,我们会将页面模板放在特有的文件中:

    ```<!DOCTYPE html> <html lang="en"> <head><title>Hello</title></head> <body> <!--vue-ssr-outlet--> </body> </html> ```

    然后,我们可以读取和传输文件到Vue renderer中:

    
    const tpl = fs.readFileSync(path.resolve(__dirname, './index.html'), 'utf-8');
    const renderer = vssr.createRenderer({
        template: tpl,
    });
    

    Webpack配置

    然而在实际项目中,不止上述例子那么简单,需要考虑很多方面:路由、数据预取、组件化、全局状态等,所以服务端渲染不是只用一个简单的模板,然后加上使用vue-server-renderer完成的,如下面的示意图所示:

    如示意图所示,一般的Vue服务端渲染项目,有两个项目入口文件,分别为entry-client.jsentry-server.js,一个仅运行在客户端,一个仅运行在服务端,经过Webpack打包后,会生成两个Bundle,服务端的Bundle会用于在服务端使用虚拟DOM生成应用程序的“快照”,客户端的Bundle会在浏览器执行。

    因此,我们需要两个Webpack配置,分别命名为webpack.client.config.jswebpack.server.config.js,分别用于生成客户端Bundle与服务端Bundle,分别命名为vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json,关于如何配置,Vue官方有相关示例vue-hackernews-2.0

    开发环境搭建

    我所在的项目使用Koa作为Web Server Frame,项目使用koa-webpack进行开发环境的构建。如果是在产品环境下,会生成vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json,包含对应的Bundle,提供客户端和服务端引用,而在开发环境下,一般情况下放在内存中。使用memory-fs模块进行读取。

    
    const fs = require('fs')
    const path = require( 'path' );
    const webpack = require( 'webpack' );
    const koaWpDevMiddleware = require( 'koa-webpack' );
    const MFS = require('memory-fs');
    const appSSR = require('./../../app.ssr.js');
    
    let wpConfig;
    let clientConfig, serverConfig;
    let wpCompiler;
    let clientCompiler, serverCompiler;
    
    let clientManifest;
    let bundle;
    
    // 生成服务端bundle的webpack配置
    if ((fs.existsSync(path.resolve(cwd,'webpack.server.config.js')))) {
      serverConfig = require(path.resolve(cwd, 'webpack.server.config.js'));
      serverCompiler = webpack( serverConfig );
    }
    
    // 生成客户端clientManifest的webpack配置
    if ((fs.existsSync(path.resolve(cwd,'webpack.client.config.js')))) {
      clientConfig = require(path.resolve(cwd, 'webpack.client.config.js'));
      clientCompiler = webpack(clientConfig);
    }
    
    if (serverCompiler &amp;&amp; clientCompiler) {
      let publicPath = clientCompiler.output &amp;&amp; clientCompiler.output.publicPath;
    
      const koaDevMiddleware = await koaWpDevMiddleware({
        compiler: clientCompiler,
        devMiddleware: {
          publicPath,
          serverSideRender: true
        },
      });
    
      app.use(koaDevMiddleware);
    
      // 服务端渲染生成clientManifest
    
      app.use(async (ctx, next) =&gt; {
        const stats = ctx.state.webpackStats.toJson();
        const assetsByChunkName = stats.assetsByChunkName;
        stats.errors.forEach(err =&gt; console.error(err));
        stats.warnings.forEach(err =&gt; console.warn(err));
        if (stats.errors.length) {
          console.error(stats.errors);
          return;
        }
        // 生成的clientManifest放到appSSR模块,应用程序可以直接读取
        let fileSystem = koaDevMiddleware.devMiddleware.fileSystem;
        clientManifest = JSON.parse(fileSystem.readFileSync(path.resolve(cwd,'./dist/vue-ssr-client-manifest.json'), 'utf-8'));
        appSSR.clientManifest = clientManifest;
        await next();
      });
    
      // 服务端渲染的server bundle 存储到内存里
      const mfs = new MFS();
      serverCompiler.outputFileSystem = mfs;
      serverCompiler.watch({}, (err, stats) =&gt; {
        if (err) {
          throw err;
        }
        stats = stats.toJson();
        if (stats.errors.length) {
          console.error(stats.errors);
          return;
        }
        // 生成的bundle放到appSSR模块,应用程序可以直接读取
        bundle = JSON.parse(mfs.readFileSync(path.resolve(cwd,'./dist/vue-ssr-server-bundle.json'), 'utf-8'));
        appSSR.bundle = bundle;
      });
    }
    

    渲染中间件配置

    产品环境下,打包后的客户端和服务端的Bundle会存储为vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json,通过文件流模块fs读取即可,但在开发环境下,我创建了一个appSSR模块,在发生代码更改时,会触发Webpack热更新,appSSR对应的bundle也会更新,appSSR模块代码如下所示:

    
    let clientManifest;
    let bundle;
    
    const appSSR = {
      get bundle() {
        return bundle;
      },
      set bundle(val) {
        bundle = val;
      },
      get clientManifest() {
        return clientManifest;
      },
      set clientManifest(val) {
        clientManifest = val;
      }
    };
    
    module.exports = appSSR;
    

    通过引入appSSR模块,在开发环境下,就可以拿到clientManifestssrBundle,项目的渲染中间件如下:

    
    const fs = require('fs');
    const path = require('path');
    const ejs = require('ejs');
    const vue = require('vue');
    const vssr = require('vue-server-renderer');
    const createBundleRenderer = vssr.createBundleRenderer;
    const dirname = process.cwd();
    
    const env = process.env.RUN_ENVIRONMENT;
    
    let bundle;
    let clientManifest;
    
    if (env === 'development') {
      // 开发环境下,通过appSSR模块,拿到clientManifest和ssrBundle
      let appSSR = require('./../../core/app.ssr.js');
      bundle = appSSR.bundle;
      clientManifest = appSSR.clientManifest;
    } else {
      bundle = JSON.parse(fs.readFileSync(path.resolve(__dirname, './dist/vue-ssr-server-bundle.json'), 'utf-8'));
      clientManifest = JSON.parse(fs.readFileSync(path.resolve(__dirname, './dist/vue-ssr-client-manifest.json'), 'utf-8'));
    }
    
    
    module.exports = async function(ctx) {
      ctx.status = 200;
      let html;
      let context = await ctx.getTplContext();
      ctx.logger('进入SSR,context为: ', JSON.stringify(context));
      const tpl = fs.readFileSync(path.resolve(__dirname, './newTemplate.html'), 'utf-8');
      const renderer = createBundleRenderer(bundle, {
        runInNewContext: false,
        template: tpl, // (可选)页面模板
        clientManifest: clientManifest // (可选)客户端构建 manifest
      });
      ctx.logger('createBundleRenderer  renderer:', JSON.stringify(renderer));
      try {
        html = await renderer.renderToString({
          ...context,
          url: context.CTX.url,
        });
      } catch(err) {
        ctx.logger('SSR renderToString 失败: ', JSON.stringify(err));
        console.error(err);
      }
    
      ctx.body = html;
    };
    
    

    如何对现有项目进行改造?

    基本目录改造

    使用Webpack来处理服务器和客户端的应用程序,大部分源码可以使用通用方式编写,可以使用Webpack支持的所有功能。

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

    
    src
    ├── components
    │   ├── Foo.vue
    │   ├── Bar.vue
    │   └── Baz.vue
    ├── frame
    │   ├── app.js # 通用 entry(universal entry)
    │   ├── entry-client.js # 仅运行于浏览器
    │   ├── entry-server.js # 仅运行于服务器
    │   └── index.vue # 项目入口组件
    ├── pages
    ├── routers
    └── store
    

    app.js是我们应用程序的「通用entry」。在纯客户端应用程序中,我们将在此文件中创建根Vue实例,并直接挂载到DOM。但是,对于服务器端渲染(SSR),责任转移到纯客户端entry文件。app.js简单地使用export导出一个createApp函数:

    
    import Router from '~ut/router';
    import { sync } from 'vuex-router-sync';
    import Vue from 'vue';
    import { createStore } from './../store';
    
    import Frame from './index.vue';
    import myRouter from './../routers/myRouter';
    
    function createVueInstance(routes, ctx) {
        const router = Router({
            base: '/base',
            mode: 'history',
            routes: [routes],
        });
        const store = createStore({ ctx });
        // 把路由注入到vuex中
        sync(store, router);
        const app = new Vue({
            router,
            render: function(h) {
                return h(Frame);
            },
            store,
        });
        return { app, router, store };
    }
    
    module.exports = function createApp(ctx) {
        return createVueInstance(myRouter, ctx); 
    }
    
    注:在我所在的项目中,需要动态判断是否需要注册 DicomView,只有在客户端才初始化 DicomView,由于 Node.js环境没有 window对象,对于代码运行环境的判断,可以通过 typeof window === 'undefined'来进行判断。

    避免创建单例

    Vue SSR文档所述:

    当编写纯客户端 (client-only) 代码时,我们习惯于每次在新的上下文中对代码进行取值。但是,Node.js 服务器是一个长期运行的进程。当我们的代码进入该进程时,它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,它将在每个传入的请求之间共享。如基本示例所示,我们为每个请求创建一个新的根 Vue 实例。这与每个用户在自己的浏览器中使用新应用程序的实例类似。如果我们在多个请求之间使用一个共享的实例,很容易导致交叉请求状态污染 (cross-request state pollution)。因此,我们不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例。同样的规则也适用于 router、store 和 event bus 实例。你不应该直接从模块导出并将其导入到应用程序中,而是需要在 createApp 中创建一个新的实例,并从根 Vue 实例注入。

    如上代码所述,createApp方法通过返回一个返回值创建Vue实例的对象的函数调用,在函数createVueInstance中,为每一个请求创建了VueVue RouterVuex实例。并暴露给entry-cliententry-server模块。

    在客户端entry-client.js只需创建应用程序,并且将其挂载到DOM中:

    
    import { createApp } from './app';
    
    // 客户端特定引导逻辑……
    
    const { app } = createApp();
    
    // 这里假定 App.vue 模板中根元素具有 `id="app"`
    app.$mount('#app');
    

    服务端entry-server.js使用default export 导出函数,并在每次渲染中重复调用此函数。此时,除了创建和返回应用程序实例之外,它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配和数据预取逻辑:

    
    import { createApp } from './app';
    
    export default context =&gt; {
      const { app } = createApp();
      return app;
    }
    

    在服务端用vue-router分割代码

    Vue实例一样,也需要创建单例的vueRouter对象。对于每个请求,都需要创建一个新的vueRouter实例:

    
    function createVueInstance(routes, ctx) {
        const router = Router({
            base: '/base',
            mode: 'history',
            routes: [routes],
        });
        const store = createStore({ ctx });
        // 把路由注入到vuex中
        sync(store, router);
        const app = new Vue({
            router,
            render: function(h) {
                return h(Frame);
            },
            store,
        });
        return { app, router, store };
    }
    

    同时,需要在entry-server.js中实现服务器端路由逻辑,使用router.getMatchedComponents方法获取到当前路由匹配的组件,如果当前路由没有匹配到相应的组件,则reject404页面,否则resolve整个app,用于Vue渲染虚拟DOM,并使用对应模板生成对应的HTML字符串。

    
    const createApp = require('./app');
    
    module.exports = context =&gt; {
      return new Promise((resolve, reject) =&gt; {
        // ...
        // 设置服务器端 router 的位置
        router.push(context.url);
        // 等到 router 将可能的异步组件和钩子函数解析完
        router.onReady(() =&gt; {
          const matchedComponents = router.getMatchedComponents();
          // 匹配不到的路由,执行 reject 函数,并返回 404
          if (!matchedComponents.length) {
            return reject('匹配不到的路由,执行 reject 函数,并返回 404');
          }
          // Promise 应该 resolve 应用程序实例,以便它可以渲染
          resolve(app);
        }, reject);
      });
    
    }
    

    在服务端预拉取数据

    Vue服务端渲染,本质上是在渲染我们应用程序的"快照",所以如果应用程序依赖于一些异步数据,那么在开始渲染过程之前,需要先预取和解析好这些数据。服务端Web Server Frame作为代理服务器,在服务端对接口服务发起请求,并将数据拼装到全局Vuex状态中。

    另一个需要关注的问题是在客户端,在挂载到客户端应用程序之前,需要获取到与服务器端应用程序完全相同的数据 - 否则,客户端应用程序会因为使用与服务器端应用程序不同的状态,然后导致混合失败。

    目前较好的解决方案是,给路由匹配的一级子组件一个asyncData,在asyncData方法中,dispatch对应的actionasyncData是我们约定的函数名,表示渲染组件需要预先执行它获取初始数据,它返回一个Promise,以便我们在后端渲染的时候可以知道什么时候该操作完成。注意,由于此函数会在组件实例化之前调用,所以它无法访问this。需要将store和路由信息作为参数传递进去:

    举个例子:

    &lt;template&gt;
      &lt;div&gt;&lt;/div&gt;
    &lt;/template&gt;
    
    &lt;script&gt;
    export default {
      // ...
      async asyncData({ store, route }) {
        return Promise.all([
          store.dispatch('getA'),
          store.dispatch('myModule/getB', { root:true }),
          store.dispatch('myModule/getC', { root:true }),
          store.dispatch('myModule/getD', { root:true }),
        ]);
      },
      // ...
    }
    &lt;/script&gt;
    
    

    entry-server.js中,我们可以通过路由获得与router.getMatchedComponents()相匹配的组件,如果组件暴露出asyncData,我们就调用这个方法。然后我们需要将解析完成的状态,附加到渲染上下文中。

    
    const createApp = require('./app');
    
    module.exports = context =&gt; {
      return new Promise((resolve, reject) =&gt; {
        const { app, router, store } = createApp(context);
        // 针对没有Vue router 的Vue实例,在项目中为列表页,直接resolve app
        if (!router) {
          resolve(app);
        }
        // 设置服务器端 router 的位置
          router.push(context.url.replace('/base', ''));
        // 等到 router 将可能的异步组件和钩子函数解析完
        router.onReady(() =&gt; {
          const matchedComponents = router.getMatchedComponents();
          // 匹配不到的路由,执行 reject 函数,并返回 404
          if (!matchedComponents.length) {
            return reject('匹配不到的路由,执行 reject 函数,并返回 404');
          }
          Promise.all(matchedComponents.map(Component =&gt; {
            if (Component.asyncData) {
              return Component.asyncData({
                store,
                route: router.currentRoute,
              });
            }
          })).then(() =&gt; {
            // 在所有预取钩子(preFetch hook) resolve 后,
            // 我们的 store 现在已经填充入渲染应用程序所需的状态。
            // 当我们将状态附加到上下文,并且 `template` 选项用于 renderer 时,
            // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
            context.state = store.state;
            resolve(app);
          }).catch(reject);
        }, reject);
      });
    }
    

    客户端托管全局状态

    当服务端使用模板进行渲染时,context.state将作为window.__INITIAL_STATE__状态,自动嵌入到最终的HTML 中。而在客户端,在挂载到应用程序之前,store就应该获取到状态,最终我们的entry-client.js被改造为如下所示:

    
    import createApp from './app';
    
    const { app, router, store } = createApp();
    
    // 客户端把初始化的store替换为window.__INITIAL_STATE__
    if (window.__INITIAL_STATE__) {
      store.replaceState(window.__INITIAL_STATE__);
    }
    
    if (router) {
      router.onReady(() =&gt; {
        app.$mount('#app')
      });
    } else {
      app.$mount('#app');
    }
    

    常见问题的解决方案

    至此,基本的代码改造也已经完成了,下面说的是一些常见问题的解决方案:

    • 在服务端没有windowlocation对象:

    对于旧项目迁移到SSR肯定会经历的问题,一般为在项目入口处或是createdbeforeCreate生命周期使用了DOM操作,或是获取了location对象,通用的解决方案一般为判断执行环境,通过typeof window是否为'undefined',如果遇到必须使用location对象的地方用于获取url中的相关参数,在ctx对象中也可以找到对应参数。

    • vue-router报错Uncaught TypeError: _Vue.extend is not _Vue function,没有找到_Vue实例的问题:

    通过查看Vue-router源码发现没有手动调用Vue.use(Vue-Router);。没有调用Vue.use(Vue-Router);在浏览器端没有出现问题,但在服务端就会出现问题。对应的Vue-router源码所示:

    
    VueRouter.prototype.init = function init (app /* Vue component instance */) {
        var this$1 = this;
    
      process.env.NODE_ENV !== 'production' &amp;&amp; assert(
        install.installed,
        "not installed. Make sure to call `Vue.use(VueRouter)` " +
        "before creating root instance."
      );
      // ...
    }
    
    • 服务端无法获取hash路由的参数

    由于hash路由的参数,会导致vue-router不起效果,对于使用了vue-router的前后端同构应用,必须换为history路由。

    • 接口处获取不到cookie的问题:

    由于客户端每次请求都会对应地把cookie带给接口侧,而服务端Web Server Frame作为代理服务器,并不会每次维持cookie,所以需要我们手动把
    cookie透传给接口侧,常用的解决方案是,将ctx挂载到全局状态中,当发起异步请求时,手动带上cookie,如下代码所示:

    
    // createStore.js
    // 在创建全局状态的函数`createStore`时,将`ctx`挂载到全局状态
    export function createStore({ ctx }) {
        return new Vuex.Store({
            state: {
                ...state,
                ctx,
            },
            getters,
            actions,
            mutations,
            modules: {
                // ...
            },
            plugins: debug ? [createLogger()] : [],
        });
    }
    

    当发起异步请求时,手动带上cookie,项目中使用的是Axios

    
    // actions.js
    
    // ...
    const actions = {
      async getUserInfo({ commit, state }) {
        let requestParams = {
          params: {
            random: tool.createRandomString(8, true),
          },
          headers: {
            'X-Requested-With': 'XMLHttpRequest',
          },
        };
    
        // 手动带上cookie
        if (state.ctx.request.headers.cookie) {
          requestParams.headers.Cookie = state.ctx.request.headers.cookie;
        }
    
        // ...
    
        let res = await Axios.get(`${requestUrlOrigin}${url.GET_A}`, requestParams);
        commit(globalTypes.SET_A, {
          res: res.data,
        });
      }
    };
    
    // ...
    
    • 接口请求时报connect ECONNREFUSED 127.0.0.1:80的问题

    原因是改造之前,使用客户端渲染时,使用了devServer.proxy代理配置来解决跨域问题,而服务端作为代理服务器对接口发起异步请求时,不会读取对应的webpack配置,对于服务端而言会对应请求当前域下的对应path下的接口。

    解决方案为去除webpackdevServer.proxy配置,对于接口请求带上对应的origin即可:

    
    const requestUrlOrigin = requestUrlOrigin = state.ctx.URL.origin;
    const res = await Axios.get(`${requestUrlOrigin}${url.GET_A}`, requestParams);
    
    • 对于vue-router配置项有base参数时,初始化时匹配不到对应路由的问题

    在官方示例中的entry-server.js

    
    // entry-server.js
    import { createApp } from './app';
    
    export default context =&gt; {
      // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
      // 以便服务器能够等待所有的内容在渲染前,
      // 就已经准备就绪。
      return new Promise((resolve, reject) =&gt; {
        const { app, router } = createApp();
    
        // 设置服务器端 router 的位置
        router.push(context.url);
    
        // ...
      });
    }
    

    原因是设置服务器端router的位置时,context.url为访问页面的url,并带上了base,在router.push时应该去除base,如下所示:

    router.push(context.url.replace('/base', ''));
    
    

    小结

    本文为笔者通过对现有项目进行改造,给现有项目加上Vue服务端渲染的实践过程的总结。

    首先阐述了什么是Vue服务端渲染,其目的、本质及原理,通过在服务端使用Vue的虚拟DOM,形成初始化的HTML字符串,即应用程序的“快照”。带来极大的性能优势,包括SEO优势和首屏渲染的极速体验。之后阐述了Vue服务端渲染的基本用法,即两个入口、两个webpack配置,分别作用于客户端和服务端,分别生成vue-ssr-client-manifest.jsonvue-ssr-server-bundle.json作为打包结果。最后通过对现有项目的改造过程,包括对路由进行改造、数据预获取和状态初始化,并解释了在Vue服务端渲染项目改造过程中的常见问题,帮助我们进行现有项目往Vue服务端渲染的迁移。

    文章最后,打个广告:腾讯医疗部门招前端工程师啦,HC无限多,社招、校招均可内推。如果有想来腾讯的小伙伴,可以添加我的微信:xingbofeng001,如果有想交朋友、交流技术的小伙伴也欢迎添加我的微信~

    来源:https://segmentfault.com/a/1190000018577041

    展开全文
  • Vue服务端渲染(Nodejs)

    千次阅读 2019-12-19 15:56:29
    Node某一路由返回的页面利用vue component和服务端数据进行服务端的页面渲染,(没有进行服务端渲染的原因是数据来源为服务端,其他模板引擎使用res.render()方法实现相同服务端渲染功能) 建议代码结构:M...

    主要参考官方文档:
    https://ssr.vuejs.org/zh/

    安装:
    npm install vue vue-server-renderer --save

    应用场景:
    Node某一路由返回的页面利用vue component和服务端数据进行服务端的页面渲染,(没有进行服务端渲染的原因是数据来源为服务端,其他模板引擎使用res.render()方法实现相同服务端渲染功能)
    建议代码结构:MVC中的Controller层,一个页面对应一个controller

    关键代码:

    homepage.js:

    const Vue = require('vue');
    const fs = require('fs');
    const path = require('path');
    
    const renderer = require('vue-server-renderer').createRenderer({
      template: fs.readFileSync(path.join(__dirname,'../','views','home.html'), 'utf-8')
    }); //定义返回的html文件的地址
    
    exports.postLoginToHome = function (req,res,next) {
      const username = req.body.username;
      const subComponent = {
        template: `<span>Hi</span>`
      } //子组件定义
      const homeComponent = new Vue({ //定义根组件 注意:这里不用el定位,而是<!--vue-ssr-outlet--> 
            data: function() {
                return {
                  username: username
                }
              },
            template:`
            <div class="homePageWrapper">
              <ul>
                <li id="username">{{username}}</li>
              </ul>
              <sub-component></sub-component>
            </div>
            `,
            components: {
              'sub-component':subComponent //使用多个组件
            }
      });
      user.login(user,
        function(){
          res.redirect("/")
        },
        function(){
          renderer.renderToString(homeComponent, (err, html) => {
            res.send(html); // html 将是注入应用程序内容的完整页面
          })
        });
    };
    

    home.html:

      <div id="homeComponent">
        <!--vue-ssr-outlet--> 
      </div>
    

    注意 <!–vue-ssr-outlet–> 注释 – 这里将是应用程序 HTML 标记注入的地方
    在Router处理文件里引入postLoginToHome方法即可:
    home.js:

    const homepageController =require("../controllers/homepage"); //controller homepage.js地址
    const router = express.Router();
    
    router.post('/home/:userId', homepageController.postLoginToHome); //应用方法 与特定路由绑定
    
    module.exports = router;
    
    展开全文
  • vue-ssr ssr与csr 什么是服务端渲染 1. 什么是服务端渲染(ssr)? SSR是Server Side Render简称;页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的html。 服务端渲染是一种方式,不限定语言,不管...

    开篇说明:文章资料内容参考
    vue-ssr
    ssr与csr
    什么是服务端渲染

    1. 什么是服务端渲染(ssr)?

    SSR是Server Side Render简称;页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的html。
    服务端渲染是一种方式,不限定语言,不管是以前的jsp,php,asp.net还是现在的node.js,都可以做服务端渲染的事情。
    服务端渲染把一部分的视图业务逻辑交给服务端,这让服务端承受压力。

    2. 什么是浏览器端渲染 (CSR)?

    CSR是Client Side Render简称;页面上的内容是我们加载的js文件渲染出来的,js文件运行在浏览器上面,服务端只返回一个html模板。

    像常用的一些框架:react,vue等都是通过在一个html中用js去控制视图的渲染,操作dom的方式,都是浏览器端渲染。

    3. 为什么要使用服务端渲染

    一是更好的SEO,二是更快的渲染速度。

    由于是服务端进行渲染,浏览器只需要渲染出服务端返回的html文件即可,减少了操作dom的时间,这在运行缓慢或者网络不好的设备上,会比较明显。
    但是我认为这并不算是值得我们去使用服务端渲染的地方。
    更好的SEO,才是目前需要使用ssr的真正目的。

    5. vue-ssr

    我读了vue文档里面关于ssr的内容,里面对实现vue项目的服务端渲染提出了2种解决方案。

    一种是使用预渲染(Prerendering),在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。这对在项目中需要seo部分完全静态的页面来说是非常高效的。

    第二种是使用服务器端渲染(ssr)。需要开启一个node的服务,使用vue-server-renderer插件,把vue项目的内容动态渲染成一个html文档,返回给浏览器,达到了服务器渲染的目的。

    vue-server-renderer 依赖一些 Node.js 原生模块,因此只能在 Node.js 中使用。

    其实vue文档中对vue项目如何原理说的很详细了,这里我附上一张里面的配图。
    vue-ssr

    这张图阐述了vue-ssr,在本地开发环境,与经过webpack打包后生产环境的一些文件结构,与实际的服务端渲染过程。但是在这里我用流程图的方式结合我的个人理解去绘制流程图,阐述一下vue-ssr的简单流程。

    在这里插入图片描述
    6. vue-ssr与传统的php,jsp的ssr有啥不一样,有啥优势?

    我们来看一个php的页面传统ssr
    可以看到每一次的页面跳转,浏览器都会获取到一个html文档,每次都进行页面重新渲染的操作,而且每次跳转都会伴随页面的闪烁,体验感非常不好。

    再来看vue-ssr,只有在首次访问的时候,浏览器会获取一个已经组织完成的html文档,而后续的页面切换操作,都是基于当前的html获取该路由下的组件js,对页面进行动态渲染,就使用者体验来说,与单页面应用一致,而且也可以满足seo的要求。

    7. vue-ssr与普通的vue开发相比有啥不一样?有啥需要注意的?
    这一块,在vue文档中已经说得很清楚了。

    一是开发条件所限。一些前端的插件并不能在node下运行,需要经过特殊处理;一些浏览器特定的代码,只能在某些生命周期钩子函数 (lifecycle hook) 中使用。

    二是涉及构建设置和部署的更多要求。需要跑一个node程序来对应的服务。

    三是更多的服务器端负载。由于把一部分的原本属于浏览器的渲染压力迁移到了node服务端,势必会对服务器性能是种考验。

    最后,使用vue-ssr之前还是得想清楚一个问题:你是否真的需要它?

    展开全文
  • Vue 服务端渲染(SSR)

    千次阅读 2018-07-02 17:23:23
    Vue 服务端渲染(SSR)什么是服务端渲染,简单理解是将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。于传统的SPA(单页应用)相比,服务端...
  • 新学了vue.js中的路由 在之前写的vue的demo上加上了简单的路由例子(来自vue-router 2),但是加上点击后只有地址栏变化,内容并不变.且之前用jquery写的一些效果也失效了.最后找到原因是因为同一个id被启动了两次(第一...
  • [Java教程]Vue 2.0 服务端渲染入门0 2017-02-25 12:00:181 什么是服务端渲染 SSRserver side render就是通过后端吐模板,而不是通过前端ajax获取数据,拼接字符串。2 为什么需要SSR需要SEO,因为爬虫不会等待ajax...
  • 开篇 在开始之前我们需要先来搞清楚一个问题:...知其然,知其所以然,我们需要先搞清楚服务端渲染的基本概念和原理,服务端渲染为什么会出现,到底解决了我们的什么问题,掌握整体的渲染逻辑和思路,我们才能在学习工
  • 基于webpack4搭建Vue服务端渲染(SSR)

    千次阅读 2019-04-08 22:28:26
    了解服务端渲染是偶然间一位朋友问了我Nuxt.js的问题,孤陋寡闻的我竟然不知道服务端渲染是什么!赶紧利用空余时间充电,了解大概后觉得正好能解决我当前正遇到的问题,随着我项目的功能逐步扩展首屏加载白屏越来越...
  • 本文实例讲述了vue2路由基本用法。分享给大家供大家参考,具体如下: Vue-router 是给Vue.js 提供路由管理的插件,利用hash 的变化控制动态组件的切换。以往页面间跳转都由后端MVC 中Controller 层控制,通过<a> ...
  • vue ssr 服务端渲染的理解

    千次阅读 2018-09-05 22:20:16
    为每个请求创建一个新的根 Vue 实例 router 服务器端数据预取 混合 修改title 提取css //安装 npm install vue-server-renderer --save // 第 1 步:创建一个 Vue 实例 const Vue = require('vue') const...
  • Vue SSR 服务端渲染深度解析及实践

    千次阅读 2019-06-03 17:33:50
    使用了Vue或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是...
  • 关于vue服务端渲染 2 数据预存取

    千次阅读 2018-07-14 14:07:26
    在我看来服务端的主要痛点就是数据的存取,有各种不同的解决方法但是哪一种都感觉不够完美。 这里通过vuex来进行服务端和客户端的数据同步,主要根据是服务端渲染完成之后如果存在...import Vue from 'vue' import...
  • 去除路由中的#,只需要在router.js文件中加一个mode: 'history' //在router.js文件中 const router = new Router({ // mode: 'history', routes: [ { path: '/', redirect: '/login' }, { path: '/login', ...
  • 手把手教你 Vue 服务端渲染

    千次阅读 2019-04-09 10:23:16
    服务端渲染 = SSR = Server-Side Rendering Vue 服务器渲染 可以说是我们学习 Vue 技术的最后一个环节了;也是上手难度稍为高一点的一个环节。 目前还没有发现很好的学习资料或者教程,文档也不是特别明白,这也导致...
  • Vue Router 路由路由守卫)---route

    千次阅读 多人点赞 2020-07-11 22:28:55
    简单来说,导航守卫就是路由跳转前、中、后过程中的一些钩子函数,这个函数能让你操作一些其他的事儿,这就是导航守卫。 官方解释,vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航。 2、导航守卫...
  • 之后我们就可以在前端访问后台路由的时候用这个生成的renderer来进行渲染啦~不过要当心哦!这个renderer是异步生成的,需要等待onReady的执行完毕: router.get('/(.*)', async (ctx, next) => { try { //本地环境...
  • Nuxt.js Vue服务端渲染

    2021-06-11 16:48:04
    Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。 ❀特性 基于 Vue.js 自动代码分层 服务端渲染 强大...
  • 通常情况下,前端配置主要放在router.js文件中,如果新加一个菜单的话,就直接在文件中新增一个路由。当然按照后端可维护性得到做法,可以把菜单作为配置,配置起来,不用每次都修改文件内容。 二、通常在文件中...
  • 而NUXT集成了利用Vue开发服务端渲染的应用所需要的各种配置,也集成了Vue2、vue-router、vuex、vux-meta(管理页面meta信息的),利用官方的脚手架,基本上是傻瓜式操作,不用写路由配置,不用写webpcak配置就可以跑...
  • Vue SSR服务端渲染 vue预渲染

    千次阅读 2020-05-17 18:04:22
    原始的服务器渲染是整个web项目放入后端,提供路由访问。好处坏处也很多,举例:MVC模式。JavaWeb 原始服务渲染 优点: 安全性:因为整个项目都在后端同一个服务器里。通过后端控制层提供路由访问 首屏加载快:...
  • VueJS与vue-router如何重定向到服务器路由我在烧瓶内的服务器的路由设置了如下所示:@app.route('/logout')def logout():logout_user()return render_template('login.html')在VUE我想重定向路由,这样我直接到服...
  • vue服务端渲染

    2021-02-04 19:02:58
    一、基本原理1、服务端渲染实现原理机制:在服务端拿数据进行解析渲染,直接生成html片段返回给前端。...b、服务端渲染生成html,前端通过ajax获取然后使用js动态添加...2、服务端渲染的优劣服务端渲染的好处:a、seo问...
  • 同构应用的构建流程是比较复杂的,由于同构应用的原理,代码既会运行在服务端,也会运行在客户端,所以我们在构建时需要同时进行服务端构建和客户端构建。 服务端渲染使用的是构建完毕的server bundle来进行。客户端...
  • Vue 服务端渲染

    2021-03-07 07:22:21
    关于 vue服务端渲染,目前主要有俩种解决方案,使用 vue-server-renderer 或者使用 nuxt 。但个人感觉使用 nuxt 写法太死,以及即使你用 nuxt 写出了 vue ssr 页面,可能你也只是说熟悉了 nuxt 这个框架,而不...
  • 记录vue项目切换路由后不会重复请求接口的问题。
  • 初学ssr入坑初学vue服务端渲染疑惑非常多,我们大部分前端都是半路出家,上手都是前后端分离,对服务端并不了解,不说java、php语言了,连node服务都还没搞明白,理解服务端渲染还是有些困难的;网上有非常多的vue...
  • Vue 服务端渲染 & 预渲染

    千次阅读 2018-06-22 09:48:08
    什么是服务端渲染 为什么使用服务端渲染 服务端渲染 or 预渲染 区别 如何使用预渲染 如何搭建一个预渲染开发环境 Tip 写在最后 简介 关于 Vue 的 SPA 说的已经太多太多了,它为我们带来了极速的开发体验,...
  • npm i vue-server-renderer -S 调用后整体不会有javaScript输出,输出的是一个json npm i koa -S node框架KOA npm i koa-router -S KOA框架的路由工具 npm i axios -S 在node端发生请求 npm i ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,633
精华内容 5,853
关键字:

vue服务端路由

vue 订阅