MarkHub,一个基于 nodejs 的简约博客系统。
起因
起因是想制作一个简单的单页网站,使用 markdown 语法生成,正巧 github 有个 API 可以将 markdown 文件转为 HTML。这促使我写了个 gfm2html。基本上就是解析md文件内容,然后POST
请求发给 github,将渲染后的内容插入 HTML 的body
中,然后再输出。
但这只是纯 HTML,没有 CSS 样式,搜索 github 找到了这个:github-markdown-css,这和 github 渲染的 markdown 配合完美,开箱即用。
至此,一个大胆的想法开始萌生,既然能生成单页,那我能不能直接做一个 github markdown 样式风格的博客系统?说干就干。
制作
参考hexo
等现成的博客系统,一个基本的框架和实现思路:
- markdown渲染器。负责md文件的解析和渲染。
- 模板引擎。定义基本 HTML 模板,将渲染后的内容和变量插入模板中,动态生成不同的页面和文章。
- 配置文件。定义站点基本配置,导航菜单,可选功能等。
- Front-matter。定义每篇文章的标题,标签,发布时间等。
- 首页索引生成、翻页、归档页面、标签系统、评论系统、代码块高亮等等。
markdown渲染器
既然要做"github like",那相关依赖的选择就很有限,github 支持一些特殊扩展,这在标准 markdown 中是没有的:
- tasklist,带有复选框的任务列表。
- footnote,定义参考文献的脚注等。
- alert,一种提示框。GFM 标准里面都没有,属于是 github 的私有实现。
综合考虑使用 markdown-it 和相关插件来实现。这也是本项目基于 nodejs 的原因。不得不说,npm 的包真的很丰富,可以轻松找到各种库和插件,引入也很方便。
模板引擎
选择 EJS,比较简单和易用。对于我这种小型博客系统足够了。
标签系统
hexo
等博客系统,同时拥有标签和分类,但总觉得在语义和功能是有点重复的,实际上就使用标签就足够了,避免选择困难症。实际上就是懒得实现。
评论系统
评论系统之前一直使用waline,但还是有些缺点:
- 虽然服务端可以部署在
Vercel
等serverless
等平台,但数据存储还是要准备一个数据库。即client -> vercel -> mysql
。 - 独立部署由于依赖过多,
node_modules
文件夹一坨,2万多个文件,实在谈不上轻量。
基于以上考虑,并且要贯彻"github like",就直接使用了giscus,这是个利用 GitHub Discussions 实现的评论系统,无需部署后端和准备存储服务。但也有缺点:
- 评论需要登录 github 账号。但这也许是优点,因为不必考虑垃圾信息和
Qos
限制。 - 数据全在 github,迁移不太方便。
- 没有浏览量统计,这在 waline 有原生实现。
由于 waline 引入并不复杂,也顺带实现了,以防以后需要。
浏览量统计
对于文章,浏览量统计必不可少,由于 giscus 没有这个功能,需要自己实现,于是又实现了 PageCounter 项目:
无需自建后端,使用Cloudflare Workers
作为中间件接收前端请求,使用Cloudflare D1
作为后端数据库存储数据。两者配合完美。
作为知名"赛博菩萨"网络机构,免费计划的Workers
每天10万个请求,D1
每个数据库最大5GB存储空间,对于小型项目足够了。
代码高亮
代码高亮使用highlightjs,拥有 github 风格。并且容易和 markdown-it 集成。
JavaScript 和 TypeScript
不得不说 JavaScript 是个很容易上手的语言,这是我第一次使用 JavaScript,看了点基本语法和10分钟速成教程就直接开撸。
对于习惯了 c/c++ 等强类型语言的来说,js 的弱类型和某些语法属于有点"逆天"。比如你不必预先定义对象的属性,后期可以动态随意添加新的属性并赋值:
const obj = {
name: "myname"
};
obj.age = 18;
这种灵活性极大的增加了开发效率,但到后期就开始感到有些吃力,对象传来传去,有什么字段全在脑子里面,vscode 的 linter 此时已经形同虚设,难以捕捉错误,只能在运行后一点点调试。此时想到了 TypeScript,开始尝试用 ts 定义类型,但最终还是放弃了。原因在于:
- 使用 ts 还得摆弄 vscode 插件,配置 ESLint 规则等。
- 本项目依赖 YAML 和 JSON 操作和传递数据,而这返回
any
类型,预先定义各种字段或属性过于繁琐也不太优雅,但根据“最佳实践”,在 ts 中应该避免使用any
类型。如果直接使用any
或者unknown
,lint 又不能捕捉错误,那和直接用 js 又有什么区别?
所以,直接js启动就完事了。但同时我又希望各种依赖项目是 ts 的,至少能提供d.ts
给我做类型提示,有种背刺社区的感觉。。🤣
CSS
页面布局可以把各部分想象成为一个个“容器”,从上到下依次叠放即可。通过 CSS 定义各容器的属性和值,以及间距。
左右间距
页面主体内容区域定义max-width
,然后左右外间距margin
主要靠auto
,这在各种宽度的设备上都能自动适应居中。唯一需要做的就是在小尺寸移动设备稍微缩小左右内边距padding
,使用媒体查询检测移动设备:
main {
max-width: 900px;
margin: 0 auto;
padding: 0px 30px;
}
@media (max-width: 767px) {
main {
padding: 0px 20px;
}
}
注意媒体查询中的max-width
和块级元素中max-width
不是一回事,前者是的作用对象是整个视口viewport
或者浏览器窗口的宽度,而后者只是这个元素的宽度。
上下间距
各元素之前的上下间距,为了简化逻辑,统一只定义各元素的下边距margin-bottom
。必要的时候添加padding
。
暗黑主题适配
使用媒体查询中的prefers-color-scheme
检测。利用 css 层叠性原则,即在后的样式会覆盖前面定义的样式,先通过变量定义各颜色值,然后查询到prefers-color-scheme: dark
就进行覆盖,实现暗黑主题自动切换颜色。
body {
--def-color: #1f2328;
--def-href-color: #0969da;
--def-bgc: #ffffff;
}
@media (prefers-color-scheme: dark) {
body {
--def-color: #e6edf3;
--def-href-color: #4493f8;
--def-bgc: #0d1117;
}
}
body {
color: var(--def-color);
background-color: var(--def-bgc);
}
搜索引擎优化
sitemap
站点地图主要是列出网站的URL,旨在帮助搜索引擎更好地理解和抓取网站内容。和文章生成逻辑一样,定义模板然后生成。值得注意的是,根据 Google 搜索文档 <priority>
和<changefreq>
将被忽略。
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://www.example.com/page1/</loc>
<lastmod>2005-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>http://www.example.com/page2/</loc>
<lastmod>2005-01-02</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
Structured Data
结构化数据通过一种标准化的格式(通常是 JSON-LD)将网页的内容信息清晰地传递给搜索引擎。它能使搜索引擎更容易理解网页的具体含义,从而有利于提高网页的排名和展示效果。在每篇文章中插入即可。
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "手搓了一个博客系统",
"datePublished": "2024-09-26",
"dateModified": "2024-09-26",
"author": {
"@type": "Person",
"name": "fdxx"
}
}
</script>
Cloudflare Pages
本博客系统输出只有静态文件,完全可以放Cloudflare Pages
加速访问速度,基于Cloudflare
的全球 CDN 将网站文件存储在距离用户最近的数据中心,从而实现全球加速访问。
PageSpeed Insights
使用 google 的网页检测或者开发者控制台的 Lighthouse,查看还有哪些可以优化的地方。本网站测试:
经过优化,评分基本到顶了。
结语
总的来说,实现这个博客系统花了不小心思和时间,但成就感满满。对于 HTML、CSS、JavaScript 我几乎没啥经验,需要一边想实现细节,一边学习语言特性和语法,好在现在是AI时代,不懂直接问 chatgpt,学习效率直接拉满。熟悉了前端基本技能栈,似乎随便搞个网站也能轻松拿捏了。