我觉的一个博客系统最起码要具备三个功能:写文章,展示文章,管理文章;还有三个加分点:良好的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.css
,cellar
采用响应式布局,在移动和 PC 上都能优雅的展示。另外整个单页应用的模块路由管理也在此模块中。如果你对 Vue 不是很熟练,建议开发前去读一下它的官方 API,其中组件化开发的思想和技巧是需要你掌握的,这里有相关资料的传送门。其中文章列表模块和文章详情模块互斥,用路由处理他们的呈现逻辑。
icon
中是两个 CSS 文件,webpack 提供了强大的模块定义和引用功能,这两个 CSS 文件可以作为两个单独的模块,在其他模块中可以 require('../icon/tag.css')
这样引入样式,然后在元素上直接将 icon-tag
添加到 class
属性中就可以在页面上得到一个 tag 图标了。这两个图标都是用一个元素结合 before 和 after 伪元素使用纯 CSS 绘制。
header
和 footer
是站点的头和尾,都是非常简单的模块。头模块中预留了导航功能,现在内容简单还没有必要开启此功能。
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
文件中更换路径。具体上依次共做了如下六件事:
articles
文件夹下的文件生成文章数组供后面程序使用。index-template.html
模板和文章列表数据的结合,生成出对搜索引擎友好的首页。另外还向外提供了一个 copyInitSiteFile
方法,来复制只有博客初始化才需要复制的 favicon.ico
和 前端功能模块压缩文件,这个方法会在 init-site
中被调用。
上面提到的所有依赖库都是 MIT 许可协议,Cellar 本身也是 MIT 协议,所以你可以“肆无忌惮”的使用和修改 Cellar 的全部代码。
这个系统是我对前端模块化和工程化的一个探索,抛开这个工具业务本身,在技术和工程层面也很值得探索,期待更多的 fork
,watch
,star
and push
.