vue2 + vuex + vue-router + ssr


Vue 全家桶 生活如此多娇


 

前戏

什么?你还不知道 Vuex!什么?你还不知道 ssr!

没关系,接下来,我们一起学习下 Vue 技术栈目前最前沿的东西。

故事的起因在于最近接了一个需求,要做一个类似 IconFont 的网站,图标都是采用 SVG 格式,功能点可以简化为 SVG 文件的上传和下载。

然后我就开始思考这次的架构,由于我做过很多 SPA,对于 MVVM 模式的框架比较熟悉,包括 React 和 Vue 这样的 View 层框架,很清楚它们的短板。SPA 相比较门户网站来说,SEO 方面就会差很多,而且一般对首屏渲染速度要求也不会很高。

但是,这次要做的项目后期对 SEO 和首屏渲染都会有要求,但是不用前端框架来进行开发,效率会慢很多。就在这时,我想起了尤大大前段时间推出的 Gayhub 项目:vue-hackernews

这个项目采用的技术架构是 Vue2 + Vuex + Vue-router + SSR(server-side-render) + firebase,构建用的是 webpack,它吸引我的地方主要在于服务器端渲染,这一点对 SEO 来说非常棒,而且首屏渲染也会快点。

以前在写 Vue 项目的时候,就遇到一个问题“兄弟组件之间的消息传递”,常用的办法当然是通过父组件来间接处理,比如你的页面上有很多弹框,但是同一时刻只能有一个,而且每个弹框都由子组件的状态决定,那么一般都是用利用 Vue 里的事件机制来解决。这就会导致如果状态太多,开发者管理起来非常难受,这个问题其实很常见,例如 React 就推出了 Redux / Flux 等状态管理工具来规范代码。基于这些,Vue 也推出了 Vuex 来方便开发,这些插件或者工具的设计理念都来自于操作系统中的总线机制。

至于 Firebase,近期也很火,它本质上就是一个云数据库,帮开发者管理数据,用户只需要调用它的 API 就可以方便的进行数据库的增删改查,这也就是高大上的 noBackend(无后端开发)。

最后,也就是最重要的 SSR(服务器端渲染),这项技术在前端也很常见,它一般也被称作“直出”,关于这点不是本文的核心,这里推荐一篇文章:Web性能优化之 “直出” 理论与实践总结,作者是手Q的开发哥,讲的很棒。

扯了很多闲话,前戏也差不多了。本文要说什么了?在参考尤大大的这个项目进行开发过程中,遇到了很多问题,所以这里主要进行一次回顾和总结。本来是想拿我自己的项目来讲,但是由于个人还是觉得有点 Low,而且我的服务器端渲染也还存在问题,因此就直接拿vue-hackernews讲解,这期间也找尤大大解惑过,大神回复还是很快滴,小激动(这点后面会重点说)。

正文

一开始看 vue-hackernews 这个项目时,还是比较懵逼,原因主要有2点:

  1. 以前我用的都是 Grunt 和 Gulp,对于复杂的 webpack 构建不熟悉;
  2. Vue2 的服务器端渲染流程比较绕,涉及的技术点也很多;

webpack构建

尤大大这次的构建设计主要分为前端和服务端两部分,接下来,我带大家一个个文件的来看。这里也推荐一篇文章:基于webpack和vue.js搭建的H5端框架,讲的更加详细,我这里主要从整体去把握开发思路。

build/webpack.base.config.js
这个文件比较简单,它是整个构建的基本配置,包括 entry(入口文件:client-entry.js)、output(输出文件)、vendor(第三方库单独打包)、alias(别名)和各种 loader(加载器)。这里之所以称它为基本配置,是因为在下面的前端(客户端)和服务端构建中都是建立在它的基础上,换句话说,也就是说尤大大将整体 webpack 构建中相同的部分提取出来,放在 base.config 中。
其中,src/client-entry.js 入口文件中就做了3件事:

  1. 重置 store(后面再说)
  2. 挂载 Vue
  3. 注册一个 Service Worker,用来预缓存应用中剩下的路由。还不清楚什么是 Service Worker 的小伙伴,速度去做下功课 ——> Service Worker 入门

build/webpack.client.config.js
客户端构建中,主要做了4件事:

  1. 在 base.config 的基础上加了一个别名:’create-api’: ‘./create-api-client.js’
  2. 设置系统变量 process.env.NODE_ENV 和 process.env.VUE_ENV
  3. 利用 webpack.optimize.CommonsChunkPlugin 配合 base.config 中的 vendor,更好的缓存第三方库文件
  4. 在 base.config 的基础上增加插件,主要目的在于压缩 CSS 和 JS 文件。它们分别是:
  • html-webpack-plugin
    这是个自动生成 HTML 的插件,用来将项目中的 src/index.template.html 模板 HTML 经过一系列的构建处理,生成最终在 dist 目录中的目标 HTML,也就是用户最后访问的页面。
  • extract-text-webpack-plugin
    这个插件的作用是将项目中的 CSS 抽离成一个单独的样式文件,使它适用于首次渲染
  • sw-precache-webpack-plugin
    配合前面 src/client-entry.js 文件中注册的 Service Worker,开启预缓存剩下路由。这点可以参考 React 中的做法:使用 React.js 的渐进式 Web 应用程序:第 2 部分 - 页面加载性能

build/webpack.server.config.js
服务端配置中,包括 entry(入口文件:server-entry.js)、output(输出文件: dist/server-bundle.js)、别名(’create-api’: ‘./create-api-server.js’)和设置系统变量 process.env.NODE_ENV 和 process.env.VUE_ENV。对于这里的入口文件 create-api-server.js,下一节的服务器端渲染会详细介绍。

build/setup-dev-server.js
这个构建文件的功能是为了方便开发过程,主要利用了2个 webpack 插件,实现代码热更新和浏览器自动刷新。

  • webpack-hot-middleware
  • webpack-dev-middleware

服务器端渲染

前方高能!精华哟~
首先,我来说下 Vue 服务器端渲染的流程:

  1. 用户通过 URL 访问站点;
  2. 服务端根据该 URL 找到路由匹配的 Vue component(组件);
  3. 判断该组件中是否存在 preFetch 钩子(函数);
  4. 如果有,就去调用这个 preFetch 函数(一般都是网络请求),然后将获取数据存放进服务器端的 store 中(store 是 Vuex 中的概念,不清楚的看下节);
  5. 接着将服务器端 store 中的信息存进 context.initialState;
  6. 最后将 context.initialState 以内嵌 script 标签的形式赋值给 window.__INITIAL_STATE__ 并返回给前端,这样配合上 src/client-entry.js 中的 store.replaceState,前端访问的页面中就包含了 HTML + data,如此便完成了整个的服务器端渲染。

有一点需要好好理解下:如果你是通过 URL 去服务器端获取页面,比如首页,那么才会调用 preFetch获取数据,也就是服务器端渲染。而浏览器端的路由跳转,则是直接在前端去发请求获取数据。关于这点,可以看下面尤大大在知乎上给我的回复。

接下来,我们看下代码:

server-entry.js

server.js

此处,还有两点需要知道,其实就是两个函数,如下图:

server.js
  1. createRenderer:这里的 NPM 模块 vue-server-renderer 就是让整个渲染流程协作起来的关键,它可以将服务器端的 Vue instance(实例) 处理成 string(字符串)或者 stream(流)返回给前端。

  2. parseIndex:它的作用是将我们项目中的 /dist/index.html 以 ‘’标识符拆成两部分,分别是 head 和 tail,这样做的目的是为了在头尾间加入 vue的内容 + 服务器端渲染出的数据

最后,再安利两篇好文章:

晒两张尤大大给我的回复

Vuex状态管理

关于 Vuex 的状态管理,大家还是需要好好看看文档:Vuex中文文档

我这里要写的是,vuex 里的 mapState、mapGetters、mapMutations 和 mapActions 函数涉及到最新的 JavaScript 语法,而尤大大这个项目中使用的 JS loader 是 buble,不能直接支持语法解析。我在自己项目中的处理方式是将 buble 换成了 babel,而且需要使用 stage-2 阶段的语法,你可以安装以下模块:

  1. babel
  2. babel-core
  3. babel-loader
  4. babel-plugin-transform-object-rest-spread
  5. babel-plugin-transform-runtime
  6. babel-preset-es2015
  7. babel-preset-stage-2

.babelrc 文件中设置:

{
  "presets": ["es2015", "stage-2"],
  "plugins": ["transform-runtime", "transform-object-rest-spread"],
  "comments": false
}

webpack.base.config.js 文件中设置:

{
  test: /\.js$/,
  loader: 'babel-loader',
  exclude: /node_modules/
}

总结

对于该项目中用到的 firebase,主要就是 src/store 目录下的 api.js、create-api-client.js 和 create-api-server.js 文件,包括其中还涉及到关于缓存的技术点,这几天会专门写一篇博文来介绍。

同时需要注意,这个项目对 Node 有版本要求,必须是 6.0 版本以上才可以,这点利用 nvm 就能轻松实现。

附上我的项目地址:http://kisstome.com/svg

  1. 前端:https://github.com/xiabaiyang/wechat-iconfont.git
  2. 后台:https://github.com/xiabaiyang/uploadFile.git                   
MaybeXia wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创分享,您的支持将鼓励我继续创作!