让vue-cli初始化后的项目集成支持SSR

文章转自悠扬小Q,原文地址:
http://blog.myweb.kim/vue/%E8%AE%A9vue-cli%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%8E%E7%9A%84%E9%A1%B9%E7%9B%AE%E9%9B%86%E6%88%90%E6%94%AF%E6%8C%81SSR/?utm_campaign=ligang&utm_source=csdn&utm_medium=blog

当前 SPA 架构流行的趋势如日中天,但在 SEO 方面好像一直是个痛点,所以众多流行的 mv* 等框架也为此痛点提出了解决方案。
vue 官方提供了快速构建项目的工具 vue-cli,其方便快捷性众所周知。本文章来分享一下使用vue cli构建项目后如何集成 SSR(server side render 服务器端渲染),本文主要说明使用两种方式来实现SSR的效果。

两个示例的git地址:

  • vue-prerender-demo
  • vue-ssr-demo

此文章面向对 vue 较为熟悉以及对 vue-cli 有些许了解的同学。

1. 我的环境

  • node v8.5.0
  • npm v5.0.0
  • vue-cli v2.8.2
  • MacOS 10.12.3

以上并不是重要因素~~

2. 方式一:使用prerender-spa-plugin插件获得SSR的效果。

2.1 说明

插件地址:prerender-spa-plugin

严格上来说,此种实现方式并非叫做 SSR,而是预渲染。不过效果上是一样的,甚至某种程度上来说可能要比 SSR 更好。相比官方提供的 SSR 繁琐配置,prerender 配置更简单快捷,如无特殊要求只需在 webpack 中加一个 plugin 的配置即可。 但是此方式只支持h5 history方式的路由,不支持hash方式的路由。

让vue-cli初始化后的项目集成支持SSR_第1张图片

prerender 主要是利用phantom js模拟浏览器环境,将指定的路由页面放在 phantom j s中运行,这样.vue便会在 phantom 中工作并完成渲染,prerender再去获取渲染后的dom结构并将其写入对应路由的 html 文件。
服务启动后,在真实浏览器环境中输入对应的路由地址,服务器便会将 prerender 已渲染好生成的 html 返回给浏览器,从而达到了 SSR 的效果。

2.2 初始化

确保 vue-cli 安装成功,执行命令:

vue init webpack vue-prerender-demo //此文章都是在webpack基础上配置的

回车之后构建工具会提示一些项目信息的相关设置。这里的我选择了vue-router、代码检查ESLintStandard,没有选择集成测试与单元测试,安装包太耗时了。

初始化完毕:

cd vue-prerender-demo
npm install
npm run dev

进入项目,安装完毕,启动项目,确保项目正常工作。

2.3 配置

为了方便测试效果,我对初始化好的 demo 做了以下修改:

  1. 将路由的mode修改为history
  2. 增加一个Test组件,与Hello组件评级,作为一个单独的路由页面
  3. 修改router/index.js中的配置,增加/test路由。
  4. 在Hello组件中加入了/test, 在Test组件中加入了回到首页

如此之后查看页面,从首页->Test->首页之间跳转,路由可正常工作。

2.4 开始

那么接下来即可开始正式工作:

1. 安装 prerender-spa-plugin, 因为依赖phantom js,phantom 的安装比较蛋疼,太耗时了~

npm install prerender-spa-plugin -D

2. 开始 prerender 相关的配置:

修改 webpack.prod.conf.js,只在生产环境进行预渲染。

 //引用
  var PrerenderSpaPlugin = require('prerender-spa-plugin')
  //...
  plugins: {
      //....
      //配置 prerender-spa-plugin
      new PrerenderSpaPlugin(
          // 生成文件的路径,此处与webpack打包地址一致
          path.join(config.build.assetsRoot), //config.build.assetsRoot为vue cli生成的配置,打包后的文件地址
          // 配置要做预渲染的路由,只支持h5 history方式
          [ '/', '/test']
      )
      //....
  }   

3. 编译

运行命令:

npm run build

等待命令完成后,可以看到 dist 目录下的文件结构:
让vue-cli初始化后的项目集成支持SSR_第2张图片

​ 相比原配置打包出来的内容多出了一个test目录,此目录对应prerender配置中的/test路由,那么配置的/就是dist/index.html吗?对的,是这样。

​ 打开dist/index.html查看一下内容,此文件内有很多东西,不再是以前孤单单的一个

,现在body里面的dom结构其实是与在浏览器中渲染的/路径页面dom结构是一致的!test/index.html便是对应访问/test渲染后的dom结构。

4. 验证

可忽略此步骤。 这里面使用了python作为快速启动的server。

为了在真实环境中确认最终效果是正确的,我在本地使用 python 启动了一个 http 服务(没有使用 webpack 与 node 作为服务)

cd dist //进入到对应目录
python -m SimpleHTTPServer 8888 //将dist作为根目录,启动8888端口,

在浏览器中直接输入localhost:8888/test,并右键选择查看显示网页源代码

让vue-cli初始化后的项目集成支持SSR_第3张图片

/test的 response 内容中我们可以看到返回的是渲染之后的 dom 结构,搜索引擎的小蜘蛛可以顺利的获取到内容,从而达到了 SEO 的效果。

2.5 优缺点

简单、易上手、无需配置即可满足基本的 SEO 要求

只支持h5 history

不适合频繁变动的页面,如果这个页面希望做SEO优化。因为预渲染只是类似于快照的概念。

此方式实现的 demo 地址: vue-prerender-demo

3. 方式二:使用官方提供的轮子在node端做SSR

3.1 说明

本示例只说明如何完成一个相对基础的 SSR,vuex以及缓存等可参考官网说明。

官方文档地址:https://ssr.vuejs.org/zh/

官方文档开篇便说明 「vue 与 library 的版本最低要求、为什么使用 SSR 以及对比上面提到的 prerender-spa-plugin 插件」。

以及如下:

本指南将会非常深入,并且假设你已经熟悉 Vue.js 本身,并且具有 Node.js 和 webpack 的相当不错的应用经验。如果你倾向于使用提供了平滑开箱即用体验的更高层次解决方案,你应该去尝试使用 Nuxt.js。它建立在同等的 Vue 技术栈之上,但抽象出很多模板,并提供了一些额外的功能,例如静态站点生成。但是,如果你需要更直接地控制应用程序的结构,Nuxt.js 并不适合这种使用场景。

官方 vue ssr demo : HackerNews Demo

相对于 prerender 插件来对比,SSR 上手复杂度可以用天差地别来形如了吧~~
跟随本章节可以实现一个基础版本的 SSR 构建配置。

3.2 约束

如果你打算为你的vue项目在node使用 SSR,那么在通用代码中,我们有必要并且需要遵守下面的这些约定:

通用代码: 在客户端与服务器端都会运行的部分为通用代码。

  1. 注意服务端只调用beforeCreat与created两个钩子,所以不可以做类似于在created初始化一个定时器,然后在mounted或者destroyed销毁这个定时器,不然服务器会慢慢的被这些定时器给榨干了
  2. 因单线程的机制,在服务器端渲染时,过程中有类似于单例的操作,那么所有的请求都会共享这个单例的操作,所以应该使用工厂函数来确保每个请求之间的独立性。
  3. 如有在beforeCreat与created钩子中使用第三方的API,需要确保该类API在node端运行时不会出现错误,比如在created钩子中初始化一个数据请求的操作,这是正常并且及其合理的做法。但如果只单纯的使用XHR去操作,那在node端渲染时就出现问题了,所以应该采取axios这种浏览器端与服务器端都支持的第三方库。
  4. 最重要一点: 切勿在通用代码中使用document这种只在浏览器端可以运行的API,反过来也不可以使用只在node端可以运行的API。

3.3 准备工作

使用 vue-cli再次初始化一个项目:

vue init webpack vue-ssr-demo

然后,

cd vue-ssr-demo
npm install
npm run dev

确保初始化的项目可正常运行,接下来开始慢慢折腾吧~~

3.4 开始折腾

1. 首先安装 ssr 支持

 npm i -D vue-server-renderer

重要的是 vue-server-renderer 与 vue 版本必须一致匹配

2. 增加路由test与页面

随便写了个计数器,以验证服务端渲染时,vue 的机制会正常工作。

  <template>
    <div>
      Just a test page.
      <div>
        <router-link to="/">Homerouter-link>
      div>
      <div><h2>{{mode}}h2>div>
      <div><span>{{count}}span>div>
      <div><button @click="count++">+1button>div>
    div>
  template>
  <script>
    export default {
      data () {
        return {
        mode: process.env.VUE_ENV === 'server' ? 'server' : 'client',
          count: 2
        }
      }
    }
  script>

3. 在src目录下创建两个js:

   src
   ├── entry-client.js # 仅运行于浏览器
   └── entry-server.js # 仅运行于服务器

4. 修改router配置。

无论什么系统路由总是最重要的,服务器端渲染自然也要公用一套路由系统,并且为了避免产生单例的影响,这里主要只为每一个请求都导出一个新的router实例:

   import Vue from 'vue'
   import Router from 'vue-router'
   import HelloWorld from '@/components/HelloWorld'

   Vue.use(Router)

   export function createRouter () {
     return new Router({
       mode: 'history', // 注意这里也是为history模式
       routes: [
         {
           path: '/',
           name: 'Hello',
           component: HelloWorld
         }, {
           path: '/test',
           name: 'Test',
           component: () => import('@/components/Test') // 异步组件
         }
       ]
     })
   }

5. 改造main.js

main.js初始化的只适合在浏览器的运行,所以要改造两端都可以使用的文件,同样为了避免产生单例的影响,这里将导出一个createApp的工厂函数:

   import Vue from 'vue'
   import App from './App'
   import { createRouter } from './router'

   export function createApp () {
     // 创建 router 实例
     const router = new createRouter()
     const app = new Vue({
       // 注入 router 到根 Vue 实例
       router,
       render: h => h(App)
     })
     // 返回 app 和 router
     return { app, router }
   }

6. entry-client.js加入以下内容:

   import { createApp } from './main'
   const { app, router } = createApp()
   // 因为可能存在异步组件,所以等待router将所有异步组件加载完毕,服务器端配置也需要此操作
   router.onReady(() => {
     app.$mount('#app')
   })

7. entry-server.js

   // entry-server.js
   import { createApp } from './main'
   export default context => {
     // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
     // 以便服务器能够等待所有的内容在渲染前,
     // 就已经准备就绪。
     return new Promise((resolve, reject) => {
       const { app, router } = createApp()
       // 设置服务器端 router 的位置
       router.push(context.url)
       // 等到 router 将可能的异步组件和钩子函数解析完
       router.onReady(() => {
         const matchedComponents = router.getMatchedComponents()
         // 匹配不到的路由,执行 reject 函数,并返回 404
         if (!matchedComponents.length) {
           // eslint-disable-next-line
           return reject({ code: 404 })
         }
         // Promise 应该 resolve 应用程序实例,以便它可以渲染
         resolve(app)
       }, reject)
     })
   }

8. webpack配置

vue相关代码已处理完毕,接下来就需要对webpack打包配置进行修改了。

官网推荐下面这种配置:

 build
  ├── webpack.base.conf.js  # 基础通用配置
  ├── webpack.client.conf.js  # 客户端打包配置
  └── webpack.server.conf.js  # 服务器端打包配置

vue-cli初始化的配置文件也有三个:base、dev、prod ,我们依然保留这三个配置文件,只需要增加webpack.server.conf.js即可。

9. webpack 客户端的配置

修改webpack.base.conf.jsentry入口配置为: ./src/entry-client.js。这样原 dev 配置与 prod 配置都不会受到影响。

服务器端的配置也会引用base配置,但会将entry通过merge覆盖为 server-entry.js。

生成客户端构建清单client manifest

好处:

  1. 在生成的文件名中有哈希时,可以取代 html-webpack-plugin 来注入正确的资源 URL。
  2. 在通过 webpack 的按需代码分割特性渲染 bundle 时,我们可以确保对 chunk 进行最优化的资源预加载/数据预取,并且还可以将所需的异步 chunk 智能地注入为

你可能感兴趣的:(Vue)