2016-10-27 21:49:21

Vue2.0服务端渲染: 博客前台升级记录

终于做到了我最想做的部分服务端渲染(ssr)了, 仍然记录一波. 本次升级的是vue-cli 1.0的项目, 因此依赖升级十分麻烦

Vue2.0与服务端渲染(ssr)

Vue2.0使用了与Vue1.0完全不同的虚拟DOM,因此可以在服务端提前渲染出来,解决单页应用的痛点之一:SEO

  • 技术基础:
    • 虚拟dom
  • 目的:
    • SEO
    • 减少首屏加载时间

Vue2.0与Vue1.0的差别

这里提一下Vue2.0与1.0的差别, 因为有些差别是和ssr息息相关的, 比如单向数据流这个变更导致了this变瘦了不少, 因此让vue-resource 没法用在ssr环境中

  • 虚拟dom
  • 单向数据流
    • vuex已加入全家桶套餐
    • vue1.0的sync和once这种双向绑定已经在2.0中被取消.
  • API变更
    • 大部分是API改名
  • API去除
    • 例如,指令中不再支持模版以及filter

SSR流程图

Vue2.0开启ssr

npm install --save vue-server-renderer lru-cache es6-promise serialize-javascript

服务端框架可以自行选择安装express或koa
除此之外, 如果项目是用vue-cli 1.0构建的, 可以选择将webpack及其相关中间件移动到devDenpendency中

拆分入口文件

vue2使用了虚拟DOM, 因此对浏览器环境和服务端环境要分开渲染, 要创建两个对应的入口文件.

  • 浏览器入口文件
    • 使用$mount直接挂载
  • 服务端入口文件
    • 使用vue的ssr功能直接将虚拟DOM渲染成网页

修改原vue-cli入口文件main.js, 将.$mount()语句去除, 并导出Vue和VueRouter的实例

添加client-entry.js,这里得加上promise的垫片, 以防旧浏览器出错:

require('es6-promise').polyfill()
import { app, store } from './main'

app.$mount('#app')

添加server-entry:

import { app, router, store } from './main'

const isDev = process.env.NODE_ENV !== 'production'

export default context => {
  router.push(context.url)

  const s = isDev && Date.now()
  return Promise.all(router.getMatchedComponents().map(component => {
    if (component.preFetch) {
      return component.preFetch(store)
    }
  })).then(() => {
    isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
    return app
  })
}

数据依赖方面, 官方实例中选择自己实现组件的一个函数接口preFetch, 需要注意此接口要求返回Promise
如果选择不实现preFetch接口, 可以用如下语句替换Promise.all

  return new Promise((resolve, reject)=>{
    resolve(app);
  })

修改webpack配置

开发与构建过程中,需要如下四种配置:

  • 开发时打包:
    • 打包客户端
    • 打包服务端
  • 构建时打包:
    • 打包客户端
    • 打包服务端

从vue-cli构建而来的项目需要修改所有的webpack配置

webpack1.x建议配置:链接
webpack2.x建议配置:链接

再修改package.json,添加或替换scripts为如下语句

    "dev": "node server",
    "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
    "build-c": "webpack --config buildack.client.config.js",
    "build-s": "webpack --config buildack.server.config.js",
    "build": "npm run build-c && npm run build-s",
    "start": "npm run build && node server"

webpack2

支持ES6 Module。只修改.babelrc,就可以进行:

tree-shaking

  • 目的
    • 去除未使用代码,减小客户端打包文件大小
  • 技术基础
    • 静态分析 AST
    • ES6 module的严格设计
      • 包名不允许变量,只能是字符串常量
      • 只允许在模块顶层export,不能在function或if中
      • 模块初始化时所有的import必须已经导入完成
      • 禁止修改import进来的变量,类似const声明

添加服务端dev-server

  • 推荐服务端配置:vue-hacker-news2.0
  • 如果为了避免跨域而使用了本地webpack代理,不要忘记在dev-server这里也加上webpack代理中间件
app.use(require('webpack-hot-middleware')(clientCompiler))

Object.keys(proxyTable).forEach(function(context) {
  var options = proxyTable[context]
  app.use(proxyMiddleware(context, options))
})

添加SSR的服务端

  • 推荐使用koa2等Node服务端框架,以防未来有异步流程控制的需求。
  • 可以参考vue ssr官方演示项目的服务端实现
  • SSR服务端适合定时任务,例如rss,sitemap等
  • 再安装服务端的依赖
    npm install --save vue-server-renderer lru-cache serialize-javascript
    

去除vue-resource

不支持vue2的SSR

vue-resource还有一个功能: URI TEMPLATE

                        ?conditions={"type":0}
                          ||
                          ||
                          \/
                        ?conditions["type"]=0

URI TEMPLATE默认开启,无法关闭

推荐服务端与客户端同构的axios或superagent

实现组件级缓存

主要添加serverCacheKey接口,此接口仅传入父组件传入的属性,输出此组件的缓存键值

export default {
  props: {
    article: {
      type: Object,
      required: true
    }
  },
  serverCacheKey: props => {
    return `${props.article._id}::${props.article.updatedAt}`
  }
}
  • 只适合列表单元: id + 更新时间
  • 不适合子组件对全局状态有依赖的组件
  • 官方demo未缓存前3000ms首屏,缓存后100ms首屏

本文链接:https://smallpath.me/post/vue-ssr-log

-- EOF --