小强赵的个人站点

精进自己,服务他人

cellar 技术文档

我觉的一个博客系统最起码要具备三个功能:写文章,展示文章,管理文章;还有三个加分点:良好的SEO,结构清晰可二次开发,免费。cellar 恰好具备这六点,让我从技术角度为你娓娓道来。

框架概述

cellar 的定位是博客静态站点生成和管理工具,为了方便将内容生成为静态站点并且方便二次开发,cellar 的使用采取命令行的形式,除了最基本的 NodeJs 不需要安装任何辅助工具,技术栈上你只需要了解 html、css、js 就可以对本工具进行二次开发。

在代码层面 cellar 将功能分为三层:

还有一个辅助工具 server,自开发的轻量级 web 容器,方便运行静态站点和实验研究。代码目录如下:

${root}
    ├── server 
    └── tool   站点辅助工具
        ├── init-site
        └── publish
    └── web    站点的全部资源都在这里

他们之间的具体联系和技术细节下面分章节具体讲述,由于上面介绍的三层功能都是上层为下层提供辅助服务,理解了下层上层就非常好理解了,所以我们从 web 开始介绍。

前端功能模块

概述

先看目录结构:

web 站点的全部资源都在这里
    ├── articles 博客的全部文章和 Demo 都在这里
    ├── dist     站点源码被工程化压缩后放在这里,供发布后的站点直接使用
    └── src      站点的功能代码
        ├── components 自定义组件,博客站点的全部功能模块都实现了组件化,也就是整站组件化
        └── dep        依赖的第三方库

cellar 生成的博客是一个单页应用,为了可以被搜索引擎自动收录我们在首页的 noscript 标签中生成了网站的标题和文章列表,noscript 标签中的内容没有样式但内容齐全,列表中的每篇文章有一个链接,链接到对应文章的详情页,文章详情页也是自动生成的,这样搜索引擎可以方便的抓取到博客的全部内容。在文章的详情页单独放了一段 js,如果用户通过搜索引擎来到博客,通过改变路径格式再回到单页应用的详情上来,代码如下:

var href = window.location.href.replace(/[^:|\/]\//,function (matchStr){
    return matchStr + 'index.html#!';
});
window.location.href = href.replace('/main.html', '');

js 放在 noscript 标签的前面,不需要等 noscript 中的内容加载完成就可以执行,在一定程度上保证了加载速度。虽然首页的文章列表使用后台直接生成可以进一步加快首页的渲染,但是在这个连 css 和装饰图都模块化的站点中样式很难被单独提前加载,提前展示内容意义不大体验也不是很友好,另外当前方案首页初次平均加载时间已小于1秒,继续优化弊大于利。

功能架构上使用 Vue 最为基础,Vue-router作为路由,jQuery 予以辅助,自己封装 webpack 和 gulp 作为工程化打包工具。Vue 提供了很好的组件化支持,cellar整个站点共有6个组件,而且只有6个组件和一个首页实现了整站组件化。下面一一介绍这六个组件

入口模块

main 是站点的入口。这里引入最基础的 css 样式重置库 normalize.css 和 整站公用样式 main.csscellar 采用响应式布局,在移动和 PC 上都能优雅的展示。另外整个单页应用的模块路由管理也在此模块中。如果你对 Vue 不是很熟练,建议开发前去读一下它的官方 API,其中组件化开发的思想和技巧是需要你掌握的,这里有相关资料的传送门。其中文章列表模块和文章详情模块互斥,用路由处理他们的呈现逻辑。

图标模块

icon 中是两个 CSS 文件,webpack 提供了强大的模块定义和引用功能,这两个 CSS 文件可以作为两个单独的模块,在其他模块中可以 require('../icon/tag.css') 这样引入样式,然后在元素上直接将 icon-tag 添加到 class 属性中就可以在页面上得到一个 tag 图标了。这两个图标都是用一个元素结合 before 和 after 伪元素使用纯 CSS 绘制。

头模块和尾模块

headerfooter 是站点的头和尾,都是非常简单的模块。头模块中预留了导航功能,现在内容简单还没有必要开启此功能。

文章列表模块

article-list,将数据循环一下展示到页面中,数据在articles/articles.json(此文件由工程化辅助工具 publish 生成) 中,前端直接请求此文件来获取文章列表数据。还有发布时间也没做显示,欢迎贡献代码。

文章详情模块

article-detail,文章详情模块的作用是异步获取文章的内容载体main.md文件,然后将文件内容解析成 html 填充到页面中展示。

文章主体内容载体为markdown文档,主体内容包括标题、概述、文章内容,采用 markdown 形式让你更关注内容,格式交给解析器和样式来处理,这能保证你所有的文章在格式上的统一。如果你需要为每一篇文章定义不同的标题或段落片段的格式,我想你更需要一个富文本编辑器,请出门左转注册某博客。

cellar 用的 markdown 解析器是开源库 marked,这个是一个轻量级的库,提供了对 markdown 基本语法的解析功能,同时提供了方便的自定义解析接口。还有一个好处是这个库封转的很好前后端通用,cellar 在后端生成文章单页供搜索引擎抓取,在前端直接获取 markdown 文档然后在浏览器中解析。下面是自定义解析的示例代码:

var renderer = new marked.Renderer();
var options = {
    renderer: renderer
};    

// 重写链接的解析规则
renderer.link = function (href, title, text) {
    var attrStr = ''
        + ' href="' + href + '"'
        + (title === null ? '' : ' title="' + title + '"')
        + ' target="_blank"';
    return '<a' + attrStr + '>' + text + '</a>';
};

// 输出解析后的 html,options 参数可选
marked(mdContent, options);

另外详情页的文章目录也是通过 markdown 的解析器生成的数据,当前目录只支持到二级目录。

工程化辅助工具

网站初始化工具

init-site,此工具的职责就是初始化博客整站配置。博客第一次初始化,修改了网站的整站设置信息(如title、主标题、副标题)或者修改了功能性代码需要重新打包压缩,就需要执行下面命令启动该工具:

node tool/init-site

此工具集成了 webpack,用于打包功能模块的各种资源,webpack的配置信息在文件 webpack-config.js 中,作为一个 node 模块向 main.js 提供配置数据。在 webpack 的打包回调函数中做了版本处理,这样打出来的包的文件名会是 v1.2.js 这种简约优雅的形式,并且会将版本信息(包括 哈希标识、生成时间、版本号)自动写入 version-map.js 中。

在一些功能模块和页面中有网站整站设置信息,为了减少请求和代码复杂度,直接将这些数据固化到了文件中,而这些文件通过网站初始化工具高效方便地统一管理,在 config.js 中有网站title、主标题、副标题的配置项,还有需要统一处理的文件的模板,这对于配置网站和二次开发提供了有力的支持。

模板引擎使用的是 ejs,在 init-site 中模板引擎的开始和结束标记是 {{}},用来实现整站设置信息,在内容发布工具 publish 中引擎的开始和结束标记是 <%%>,通过这样的一个技巧让不同的工具来处理不同层面的信息。

如果你需要对前端的功能模块进行二次开发,那么调试的支持是必不可少的,可以加 -d 参数启动该工具,通过 sources map 技术来调试,命令如下:

node tool/init-site -d

另外 init-site 会自动调用 publish,因为整站设置信息和代码更新后必然需要重新发布。

内容发布工具

publish,它的职责是自动处理文章并将处理得到的博客必不可少的资源(包括功能代码和文章)发布到另一个文件夹中。默认文件夹是与 cellar 同级的 my-blog,可以在 config.js 文件中更换路径。具体上依次共做了如下六件事:

另外还向外提供了一个 copyInitSiteFile 方法,来复制只有博客初始化才需要复制的 favicon.ico 和 前端功能模块压缩文件,这个方法会在 init-site 中被调用。

许可协议

上面提到的所有依赖库都是 MIT 许可协议,Cellar 本身也是 MIT 协议,所以你可以“肆无忌惮”的使用和修改 Cellar 的全部代码。

作者寄语

这个系统是我对前端模块化和工程化的一个探索,抛开这个工具业务本身,在技术和工程层面也很值得探索,期待更多的 fork,watch,star and push.