无服务器动态博客系统Dolan:从设想到现实

起因

本站最早于 2020 年建立。当时正值网课时期,我就想着搭建一个博客记录一下自己的编程 / 学习过程。一路磕磕绊绊,尝试了多种不同的博客系统:

  1. 免费的 WordPress 部署平台(三蛋host),不过其国内访问速度实在不尽人意,承诺SLA为0%,同时还在网站中强制添加广告,这换谁不跑路啊;
  2. Hexo,一个静态博客生成器,目前使用人数十分庞大,同时有丰富的插件,是一个比较理想的博客系统。但是Hexo的坑实在是太多了,经常碰到因为Node版本过高 / 过低引起的问题虽然用一个fnm就能解决,同时生成速度在插件多了之后直线下滑,完全无法接受,遂弃用;
  3. Gridea,一个国人做的带GUI静态博客系统,好用,但是可拓展性不高,主题也不是很丰富,用了一段时间后也抛弃了;
  4. Hugo,一个用Go编写的静态网站生成器,生成速度很快,同时也具有插件系统与强大的自定义功能,是我使用时间最长的博客系统。

使用Hugo搭建的博客是存活时间最长的。我一共尝试了两个主题:MemE,以及自己移植的Tony。这套配置的易用性极高,同时高度可定制化,生成速度也十分令人满意。但是人心不足蛇吞象,有了一个能用的博客,没地方折腾了,怎么办呢?首先想到的一个优化点就是在线编辑文章。在本地 / 云端编辑固然好,还能实时预览,但难免受到开发环境的影响。

有一个轻量级的解决方案是以HexoPlusPlusWexagonalQexo等为代表的在线编辑器(不是开发环境)。这一类项目的特点是基于Github API无服务器,通过前端的富文本预览,然后再利用Github API上传博客源文件,从而模拟出一个在线编辑文章的环境,配合上集成部署实时生成网页,能够满足极大部分下的使用场景。不过,它们的适配对象都是Hexo,Hugo怎么办呢??理论上,在这些项目上添加支持也不难,但CYF和Abudu写的代码确实屎山质量不高,难不成自己再造一个轮子???

好吧,确实去造轮子了() 既然造了轮子,那我们就造个大的,直接造个博客系统罢!

选型

由于我们要做的是一个无服务器的博客系统,由于运行环境的限制(诸如性能、FS),我们无法做到上传主题、实时更改主题、从模板渲染等操作。同时,我们最好做到前后端分离,让每个部分各司其职,发挥最高的效能:比如后台就没必要和API捆绑在一起,可以独立部署;前端也如此。那我们的项目大体结构如下:

  1. API,提供数据支持。部署在Serverless上。
  2. 后台,提供管理页面。静态网页,部署在哪都行。
  3. 前端,用户访问界面。理论上可以是一个SPA,也可以进行服务端渲染,这个看自己有没有SEO需求了。

API最初的技术栈是Node(Koa),打算部署在Vercel上。但是Vercel的Node运行时速度实在感人,常常超时,遂放弃。

后面我了解到Deno背后的公司Deno Land Inc.推出了一个在线部署服务,叫做Deno Deploy,可以将Deno项目部署到云端,同时节点众多,访问速度理想;其又针对Deno进行了大量的优化,性能极高。于是,API的技术栈就定为了Deno(Oak)。

这里插一个题外话,Oak是一个受到Koa启发而创建的Deno HTTP框架,二者API基本相同,可以毫不费力地把Koa项目迁移过去。

随后是后台。后台的开发过程稍显曲折,最开始的技术栈是Vue3 + Quasar,但是Quasar的API稍显不足,许多功能都需要自己手动实现。后面看到抖音开源了一个SemiUI,说实话,挺漂亮的,于是捣鼓了很久,把完成了一半的后台迁移到了React。技术栈为:React + SemiUI + UnoCSS。

最后,是前端。写前端我的第一反应是使用一个SSR框架,即Nuxt / Next,但Next貌似支持的Serverless平台并不丰富,同时Nuxt3声称基于Nitro原生支持多平台(其中就包括Deno Deploy)。卧槽这不得给他冲爆,技术栈为Nuxt3,移植了一个hugo-theme-meme

当然,前端并非只能使用我做的这个dolan-client-meme。由于其本质只是从API获取数据并渲染(这也是为什么它叫做client而不是theme的原因),因此任何人都能够做自己的client。

再插个嘴:目前Nuxt3和Nitro对Deno Deploy的支持并没有在文档中体现出来,不过Nitro已经有了对它的初步支持。我发起了一个PR(目前已经合并)来修复一些bug,但目前包含这个Commit的版本还没发布,因此我自己先发布了一个包(@so1ve/nitropack)暂时用着,同时做了一些黑魔法,见dolan-client-meme/deno-fix.ts

开发

API

众所周知,开发初期搭建项目结构是最搞人心态的一件事了……不过好在Deno所需的配置并不多,最多就是import_map.jsondeno.jsonc两个配置文件。同时Oak也是一个很轻量级的框架,因此主要的目录结构如下:

│   .env
│   .gitignore
│   config.ts
│   deno.jsonc
│   deno.lock
│   import_map.json
│   LICENSE
│   README.md
├───.vscode
├───src
│   ├───protected_routes.ts
│   ├───server.ts
│   └───unprotected_routes.ts
│
└───────controller
    ├───lib
    │   └───init_values
    ├───middleware
    ├───types
    └───utils

其中两个*_routes.ts结尾的文件是路由文件,分为需要登录 / 不需要登录的两组路由。

后台

后台的UI前面已经说过选择了SemiUI,迎面而来的还有另一个问题:Markdown编辑器选什么呢?我考虑了以下几个编辑器:

后面两个首先出局,for-editor界面古早而且很久没更新,react-markdown-editor倒是挺活跃,不过它的功能不多而且不能通过插件扩展功能,于是放弃。

为什么最后选择了Milkdown而不是ByteMD呢?嗯我也不太清楚为什么,到后面想重构一下换成ByteMD发现工作量挺大的,于是搁置了() 主要原因是ByteMD默认会对HTML内容进行序列化,无法在文章中直接插入HTML,但是Milkdown@7则支持插入HTML。

然后是配置编辑器 / MD第二编辑器,Monaco Editor,这个没啥好说的

前端

TODO

如何使用呢?

目前文档在https://dolan.js.org,大家可以先看看

TODO