[Blog] Github静态博客SPA架构构建心得Mon Sep 11 2017

Github Page 只是一个静态资源服务器, 也就是说,不存在后台逻辑,也没有SQL操作,这样一来需要在Github上搭建自己的个人博客,就需要是纯静态,已经编译好的,数据存储在文本文件(模拟DB)的页面分布模式。这样的模式虽然不够灵活,但是也省去了后台逻辑的部分,并且同等对待资源文件和伪动态数据。这里,如果需要从头到位搭建一个自定义一个静态博客,就需要Node+Shell等工具的配合.


文件分布

文件的分布方式是比较随意的, 但是规划好方便配合自动化工具做处理。我个人来说推荐的分布方式如图:

  • articles 目录放置文章, 静态页面或者元数据都可以
  • static 目录放置JS,CSS,WebFonts等静态文件,当然这些文件也可以分布到CDN
  • favicon 站点图标,最好配备一个
  • index.html 为首页的入口, 默认进入Github Page时,首次会加载的页面


路由策略

一共有三种,也许还有别的,不过这里只说三种 :

  1. 纯静态资源

  2. HASH作为路由,加载局部资源的SPA

  3. HTML5 History State API作为路由 加载局部资源的SPA


第一种,纯静态资源

这种策略是等于每一篇文章都是一个独立的页面, 类似于 Hexo, Jelly 这种,通过工具生成,用户只需要通过访问对应的路径就能读到文章, 分布方式如下图:

这种分布方式有好处,那就是静态资源文件索引容易,自动化工具易于编写, 对于SEO来说是比较有利的,例如,一个基本的可访问的文章路径 : https://myblog.io/articles/xxxx.html ,这样就可以访问到对应的文章了。

但是文章页面和其他页面的js逻辑需要做好区分,另外就是如果不是SPA的模式,纯跳页体验不是很好,毕竟Github的静态资源服务器速度很差,尤其是国内,才给了1M~2M的出口带宽。所以这种纯静态的页面首选最后把lib资源放在其他CDN服务上,另外就是保证外站资源的可访问性,不然容易阻塞页面。


第二种,基于Hash Router的单页面

基于Hash的单页面博客,每个文章都可以只是片段数据。通过现有的MVC或MVVM框架,请求对应的文章数据渲染。同时使用hash作为历史记录, 数据分布方式下图:

页面入口只有一个index.html ,任何文章的访问形式都是基于hash的,例如我想阅读文章-1, 那么对应的路径可能会规划成: https://myblog.io/#/[email protected]=1

以上URL的路径是指,已article作为匹配入口,访问对应规则下的1.json,通过ajax请求对应的json数据/artices/1.json ,返回时渲染到视图中即可,使用hash Router的好处在于,兼容性非常的好,而且hashchange Event易于理解和封装, 但是它也有缺点, 比如对于SEO极其不友好,除此之外,hash只是一个伪装路由,所以做首屏渲染有很多的限制,甚至可以说是不可行, 毕竟真实的地址并非是元指向。


第三种, 基于HTML5 HistoryStateAPI的单页面

基于HTML5 HistoryAPI的,这里给出MDN中对应的链接: Manipulating the browser history - MDN

其实HTML5 HistoryAPI已经早不是什么新鲜的事情了,在4年前HTML5就已经有了,在国外的站点中普遍被应用到,其中包括 Github,Google, Facebook都有不同程度的应用,HTML5 + HistoryAPI 配合ajax请求可以做到无刷新页面前进回退, 并且创建历史记录,更加强大的可以将其封装成pjax,至于pjax是什么,请看链接: jQuery-Pjax (pjax = pushState + ajax) - Github

言归正传,使用HTML5 HistoryAPI 有一个问题,需要服务端的配合,也就是,虽然pushState 和 replaceState都是伪造状态,但是用户一旦刷新页面,如果找不到对应的静态资源,则无法显示页面,所以文件的分布,我们需要准备两套数据:

其中 article 存放了文章的完整静态页面,也就是说,用户只要刷新页面,就可以看到完整的文章 而 articles 中存放的只是文章的片段数据,这和hash策略使用的方法类似,用户进入页面之后,处于SPA状态下,通过ajax交互加载片段数据从而渲染出对应的文章。

HTML5 HistoryAPI 单页面的优点

  1. 渐进增强,假如页面js死掉了,用户可以通过跳页的方式来阅读文章
  2. 体验极好,HistoryState的路由可以被分,对于SEO来说是极其有利的 3. 首屏渲染,不管是文章也好,列表也好,只要生成了对应的静态资源,就可以第一时间被渲染出来
  3. SPA架构, 对于静态资源来说,节省了大量的http请求,响应速度也非常快
  4. 方便压缩和打包, 集成自动化工具


路由规则

目前本人已经从hash策略迁移到了 HTML5 HistoryState的Router策略上,并且全部是自己的封装。基于Ax的HTML5路由模块Ax-Router, 博客对应的路由规则:

当然,这种规则只是其中的一种方式,不同的视图策略,可以是不同的router策略,总体原则上是遵循:

  1. route定义尽量的简短,在功能单一的情况下,尽量不要配置的太复杂
  2. route做到尽量灵活,可以支持多个回调和多参数
  3. route做到轻量化, 唯一性


静态资源的打包

开发Github Page时候,在博客编写前应该预先规划好视图,以及文件的布局方式,方便日后的开发与维护, 至于是使用哪种模块化方案, 用RequireJS也好,Webpack也好,都无所谓,反正打包都需要写配置。

RequireJS 需要使用官方的rjs进行打包操作,具体可以参考一下官方的例子: Rjs - master/build/example.build.js ,同时RequireJS本身提供了几个插件模块:

  • text 模块,用于打包模版文件,静态数据文件
  • async 模块,用于异步加载外站脚本资源,例如Google Map API 等
  • almond ,上线时,将RequireJS主库替换成almond,它更快更小(当然它不支持async,注意)

Webpack 通过中间件Loader来实现各种优化, 其中必备的几个Loader我在这里列出来一下:

  • css-loader
  • clean-css-loader
  • post-css-loader post-css-pxtorem
  • url-loader
  • file-loader
  • raw-loader
  • babel-loader

至于Webpack Plugin 就仁者见仁智者见智了,怎么使用就看博客是怎么架构的,根据文件分布和打包需求来指定不同的Plugin. 需要根据优化程度的不同来指定策略。想要了解更多关于RequireJS 和 Webpack 两个模块化方案的对比,可以看我这篇文章 杂谈- 模块化架构视图分块与视图嵌套的选择;

注意, 打包文件尽量扁平化,缩短需要访问的路径:


关于后期处理

这里提供几个优化点:

  1. 站内图片最好使用CDN链接
  2. 避免过多的http请求, 可以使用LazyLoad
  3. 如果有跳转页面,可以使用prefetch预先加载所需资源
  4. 固定布局

补一张图,经过优化之后的页面在Google Page Insights上跑分高达98分

Google Page Insights / yj1028.me

更多思考欢迎以 Email的方式与我沟通交流,一起探讨

[email protected]