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,但还是有些缺点:

  • 虽然服务端可以部署在Vercelserverless等平台,但数据存储还是要准备一个数据库。即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,查看还有哪些可以优化的地方。本网站测试:

alt text

经过优化,评分基本到顶了。

结语

总的来说,实现这个博客系统花了不小心思和时间,但成就感满满。对于 HTML、CSS、JavaScript 我几乎没啥经验,需要一边想实现细节,一边学习语言特性和语法,好在现在是AI时代,不懂直接问 chatgpt,学习效率直接拉满。熟悉了前端基本技能栈,似乎随便搞个网站也能轻松拿捏了。