Web多文档页面 - 简单重复的事情变得更简单

项目中有相关页面是展示产品文档的,给到的文档资料的前情是:

  • 不同产品文档信息存储在各自仓库(各仓库使用了gitbook目录结构)
  • 生成页面形式为常规左导航,右内容
  • 生成页面需要局部刷新的交互效果
  • 文档资源需要存储在cdn上
  • 无后台介入,导航菜单数据需要从summary中提取

现有的md-to-htmlgitbook类的静态页面方案,可以使用gitbook实现基本效果,但考虑到与页面设计不符,可扩展性又不够强,后续有增加数据请求的需求,未选择此方案。Pass掉gitbook后,基本就是自己实现一套页面生成方案,也是较为容易的。

  • 关心页面实现,MD编译页面局部刷新更新两部分思路即可。
  • 考虑项目从文档更新到最终上线过程的优化,可了解下后边的脚本实现思路。

MD编译

根据给到的文档,基本思路如下:

  • 左导航:将原SUMMARY.md文档转为json数据
  • 右内容:部署CDN前统一将md内容编译为html标签形式

执行编译工作,SUMMARY.mdjson文件伪代码:

var jsonObj = []

// readFile单行数据
var datas = fs.readFileLine('xxx.md','utf-8')

for (var line in datas) {
  // 单行数据处理
  var regData = line.trim().replace(/^#+\s+(.*)/,'$1')

  // 写入json对象
  jsonObj.push(regData); 
}

// 输出文件
var objStr = JSON.stringify(jsonObj,null, 4);
fs.writeFileSync('out/path',objStr,'utf-8')

mdhtml可选用marked库处理,基本思路如下:

// 引入依赖`marked`
const marked = require('marked');

// 读取`md`文件
const source = fs.readFileSync('product.md','utf-8');

// 编译
const marks = marked(source)

// 输出
fs.writeFileSync(outPath,marks,'utf-8');

页面局部刷新更新

本例使用了react,可使用react-router,设置导航目录NavLink,渲染用于显示内容的组件Article(其他框架也都有成熟的hash方案)。

<HashRouter>
    // 导航路由部分
    <ul>
        <li><NavLink to={linkURL1}>{tit1}</NavLink></li>
        <li><NavLink to={linkURL2}>{tit2}</NavLink></li>
        ...
    </ul>   
    // 内容展示部分   
    <Route path="/:id*" component={Article} />
</HashRouter>

页面渲染流程

  • ComponentDidMount页面渲染完毕,请求编译好的Summary.json,展现文档导航区域内容
  • 文档导航内容点击后,根据url的hash值更改,发起请求获取编译后的内容页product.md后,写入内容区域

CDN发布

本地资源请求测试无误后,CDN部署静态资源,切换开发资源路径,小手一点发布,似乎没事了。真的没事了?

盘点

盘点下上线过程中,发现还有不少需要人为重复操作的环节:

  • 文档更新:逐一更新各仓库,缺乏集中管理
  • Web文档新页面:手动创建
  • 提交仓库:手动提交仓库静态资源发布CDN,没有增量更新匹配工作

产品上线后,维护的工作自然是少不了,能从代码上将后续这部分工作简化,岂不更好?
行动,将手动过程通通更改为脚本批量执行。

文档统一管理更新

多产品的文档是在多个仓库管理中存储,项目上线后每次部署更新手动文档仓库,遂根据需求,利用git的子模块概念:主仓库下,通过添加子模块(如本例中的产品git仓库),批量更新子模块.

知识储备:git, shell脚本

一键克隆

送佛送到西,我们将clone仓库这一步也完整考虑一下,得到如下clone.sh脚本,配置npm命令,实现一键clone产品a,b,c...
注意此处使用了submodule - git的子模块概念,从而实现主(当前开发仓库)从(子产品的独立git仓库)仓库的一对多管理关系,便于统一维护更新。

#!/bin/sh

cdir=`pwd`

prodName=(
    "product_a"
    "product_b"
    "product_c"
)

for name in ${prodName[@]}
do
  git submodule add git@github.com:iuap3/${name}.git ./md/${name}
done

一键更新

以下命令写入适当脚本,指定简化命令即可一键更新:

git submodule foreach git pull origin master

升级版CDN发布

发布CDN,使用的阿里oss,cdn发布只能实现文件逐一上传。文档的体量有1000+,遂思考:上传前,是否可以筛选只提交本次更新过的文件?
整理需求如下:

  • 命令式一键发布CDN
  • 只提交更新处理过的文档

自动发布CDN选择了jenkins部署,定时更新任务。

两次CDN如何区分提交的不同修改?答案是只要获取到上次CDN提交时的git版本,及本次提交的版本,利用git diff获取两次${gitLastRev},${gitRev}提交状态为:

  • M:修改
  • A:增加
  • R:重命名
  • C:拷贝

四种状态的文件(D - 删除的文件可忽略不计)。

git diff --name-status ${gitLastRev} ${gitRev}

利用OSS提供的NODE SDK,依次将需要提交的文件发送到CDN服务器上,整合到脚本中,实现一键发布CDN.

一键创建文档页面

文档上线html页面时,开发中的数据和组件做了彻底的分离解耦,模板只需传入产品名称,批量创建后执行正常编辑即可生成所需页面:

  • 创建模板html,js页面
  • 利用模板引擎handlebars,写入data,使用node创建不同产品data的待编译文件 至此,文档从clone,到update更新,进行Markdown文档编译,批量模板生成web页面, 正常产出页面,发布CDN,均能正常通过一键命令执行。
# 初次更新仓库 - 克隆子模块
npm run clone

# 批量更新子模块
npm run update

# MD文档编译
npm run md

# 模板生成预编译Web文档页面
npm run doc

# 提交仓库更新后,发布cdn
npm run cdn

# build产出页面
npm run build

项目的一个需求,实现的几处优化:

优化前 优化后 实际效果
数据和内容耦合度高 解耦数据 CDN发布即实现页面内容更新,避免文档更新需要多次部署
静态资源批量上传 比较cdn资源,只上传更新后文件 上传时间由20min左右,缩减为按秒计算
各功能模块单独手动该处理 实现一键命令更新 减少手动更新维护,增加易用性
手动发布CDN 使用自动构建工具定时发布 减少手动更新维护

项目中实现的小功能:

  • cdnfilter - cdn上传准确筛选,调用SDK完成一键上传
  • md2json.js - 特定markdown转为json文件(md文档要求的特殊性,单独发布)
  • pathJoin.js - 实现前端字符串url及文件路径拼接,自动处理连接符,在线demo
    • 1.处理文件结尾路径的冗余连接符'/'
    • 2.处理合并中间冗余的连接符'/'
    • 3.处理合并中缺失的连接符'/'
@2017-08-16 22:25