大家好! Xesite v4 现已完成并已推出。我本来希望早点发布这篇文章,但这确实有很多内容发生了变化,我仍在研究其中的一些内容。以下是 Xesite v4 中更改的快速概述:
- Lume用于构建页面
- Tailwind用于设计一切
- 该网站现在可以在生产中自动重建自身,以反映网站配置、订阅会员、博客文章或我的简历的更改。
- 该网站现已托管在Fly.io上
- 我现在使用MDX来写博客文章
因此,对于那些确实认为我的博客是静态网站的人来说,您现在就是对的。这是一个。
我为什么要这样做?
从较高的层面来看,Xesite v3(Rust 的最后一个版本)的架构足以满足我的需求。我通过lol_html实现了可扩展性并定义了自己的自定义 HTML 元素。所有内容都尽可能编译为本机 Rust 代码,并且我可以精确控制输出。
可以说,我确实有一个静态站点生成器,但它有点半途而废,并将所有内容都存储在内存中。
然而,这种方法存在一些问题:
由于 NixOS 的实现方式,我无法在不重新部署其所在的整个服务器的情况下触发网站内容的更新。这不是 NixOS 工作方式的错误,而是我实现它的方式的错误。
公平地说,我尝试在组合中添加动态更新,但我遇到了涉及我如何在 Rust 中设计事物的状态争用的问题。我本可以解决这个问题,但这需要做很多工作。它可能最终会导致我将每个页面渲染到磁盘并提供该磁盘文件夹,但这并不是我真正想要的。
我想采用Tailwind,这样我就可以更自由地设计我的帖子,但我并没有真正找到适合它的方法,因为 Tailwind 解析器无法理解我正在使用的 HTML 模板。
我使用 proc 宏Maud来编写 HTML,但是 Tailwind 解析器无法处理从 Maud 模板中读取类名。以下是我想要移植的网站上的 JSX 组件示例:
export default function BlockQuote ( { children } ) { return ( < div className = " mx-auto mt-4 mb-2 rounded-lg bg-bg-2 p-4 dark:bg-bgDark-2 md:max-w-lg xe-dont-newline " >
> { children }
</ div > ) ; }
在 Maud 中,模板如下所示:
use maud :: Markup ;
pub fn blockquote ( body : Markup ) -> Markup { html! { . "mx-auto mt-4 mb-2 rounded-lg bg-bg-2 p-4 dark:bg-bgDark-2 md:max-w-lg xe-dont-newline" { "> " ( body ) } } }
这一切都很好,但真正的麻烦出现在将其传递给 lol_html 时。 lol_html 没有获取组件子组件的概念(因为这是为了对 HTML 元素进行流式替换而设计的),因此为了在 lol_html 中实现此功能,我无法使用该模板函数。我必须这样写:
use lol_html :: { element , RewriteStrSettings } ;
let mut html = magic_get_html_for_post! ( ) ;
let html = rewrite_str ( & html , RewriteStrSettings { element_content_handlers : vec! [ // ... element! ( "xeblog-blockquote" , | el | { el . before ( "<div class=\"mx-auto mt-4 mb-2 rounded-lg bg-bg-2 p-4 dark:bg-bgDark-2 md:max-w-lg xe-dont-newline\">> " ) ; el . after ( "</div>" ) ; el . remove_and_keep_content ( ) ; } ) // .. ] , .. RewriteStrSettings :: default ( ) } ) ;
您可以看到这很快就会变得相当难以维护。
在工作中我接触到了一种叫做MDX的新技术,看起来它真的可以解决所有这些问题。它有点像 React 和 JSX 与 Markdown 的邪恶组合,但它真的很酷。我可以在 React 中编写组件并在我的博客文章中使用它们,而不是使用定制语法或 Rust 来定义组件。这真的很酷,我很高兴看到我能用它做什么。
最大的问题是这些东西的旧格式:
这些小对话片段移动起来非常痛苦!
此前,它们是通过破解 Markdown 解析器来完成的,这种方式在加利福尼亚州已知会导致癌症,这使得它们看起来像这样:
[ Wow this is text that I am saying! ]( conversation://Mara/hacker )
对于 lol_html 流程,我必须显式地命名我的 HTML 元素,令人厌恶,所以它看起来像这样:
< xeblog-conv name = " Mara " mood = " hacker " > Wow this is text I am saying! </ xeblog-conv >
但即使这样在实践中也很烦人,因为我无法在对话片段中使用换行符,而不以难以诊断的方式破坏一切。我最终以难以读写的方式在各处使用<br />
、 <ul>
、 <li>
和其他此类元素:
< xeblog-conv name = " Mara " mood = " hacker " > Okay so when you use the [ rilkef method ]( /blog/experimental-rilkef-2018-11-30/ ) to dynamically reparse the flux matricies, you need to follow these steps: < br /> < ul > < li > First, desalinate the yolo manifold </ li > < li > Then make sure you have Ubuntu up to date </ li > < li > Finally, watch < a href = " https://youtu.be/MpJsYFZtQbw " > this video </ a > to find out any missing steps </ li > </ ul > </ xeblog-conv >
这太糟糕了。主要是。我讨厌它。我希望能够像这样写我的对话:
< XeblogConv name = " Mara " mood = " hacker " > Okay so when you use the [ rilkef method ]( /blog/experimental-rilkef-2018-11-30/ ) to dynamically reparse the flux matricies, you need to follow these steps:
- First, desalinate the yolo manifold - Then make sure you have Ubuntu up to date - Finally, watch [ this video ]( https://youtu.be/MpJsYFZtQbw ) to find out any missing steps </ XeblogConv >
这就是 MDX 的真正优势。它结合了 React 和 Markdown,给你超能力。
移民
主要痛点是迁移。在大多数情况下,“新风格”语法基本上无需任何编辑即可转移。我选择修复一些小的拼写和语法错误,但其中大部分都完好无损地迁移过来。
我可能错过了一些东西,而且考虑到我拥有的文章数量(到年底超过 500 篇),我几乎肯定错过了一些东西。如果我这样做了,请告诉我!对不起!
当谈到 CSS 时,我从一个空白的 HTML 文件开始,然后从我的生产网站复制渲染的 HTML。复制完基本结构后,我开始研究Tailwind UI ,列出我想要使用的组件的简短列表。
我有将受 Gruvbox 启发的主题添加到 Tailwind 的现有经验,因此我复制了 Tailwind 配置文件,并继续复制我之前的样式,结合了 Tailwind UI 和其他一些地方的部分内容以获取灵感。我必须对颜色进行一些细微的改变,但在大多数情况下,这是一个相当简单的过程。
我最担心的部分是 Tailwind 中的散文格式。它没有遵循我旧的散文格式风格,所以我不得不做一些小的改变。我对此还不太满意(它使散文文本对我的口味来说有点太暗了),但我会在适当的时候到达那里。
隧道尽头的光
作为使用 Tailwind、React 和所有初创公司的额外好处,我可以为我制作的假冒产品制作讽刺登陆页面。这对我来说是一个巨大的胜利,因为我非常喜欢抽象的方法和取笑我自己的行业的方式。
动态更新
Xesite v4 最大的变化是它现在可以动态自我更新。这对我来说是一个巨大的胜利,因为这意味着我可以更新我的博客文章、简历和其他内容,而无需重新部署整个服务器。我所做的就是将东西推送到 GitHub,它会在一分钟内自行更新。
这要归功于我对我的网站采用了动态方法。本质上,它可以归结为:应用程序本身提供静态站点,但每次发生更改时都会重建静态站点。
这是一个有点奇怪的概念,所以让我更详细地解释一下。我制作了所有这些的图表,您需要单击才能展开,因为它有点密集:
当您考虑一下时,静态站点生成器实际上只是一个编译器。它接受源文件形式的输入并输出一个包含 HTML 的文件夹。当我评估静态站点生成器时, Lume的一个功能一直对我来说很突出:共享数据。
我网站的很多内容实际上存储在一系列越来越大的Dhall文档中。这包括我的薪资透明度历史记录、 signalboost页面,甚至我简历的关键部分的所有内容。我希望能够在我的博客文章中使用这些数据,但我不想将其复制并粘贴到各处。
我确实制作了 v4 的草稿,将所有内容更改为 TypeScript,并使用tyson即时解析,但我不喜欢将所有内容都放在难以阅读的文件中的想法。我在这里使用 Dhall 的方式有一种超现实的美感,我想保持这个梦想。
我解决这个问题的方法是让 Go 重建过程将一堆 Dhall 数据转储到 Lume 共享数据中。可以说,如果 Lume 支持加载 Dhall 数据,这个问题就可以解决,但我只是同时使用 JSON 将其组合在一起。这可能在未来会得到改进,但它具有工作的优点。
令人惊讶的是,这意味着我可以以相同的流程将顾客信息放入正确的位置。我不需要做任何特别的事情来使这项工作正常进行,它就可以工作。
将此与将正确的 JSON 文件转储到正确的位置以便Typst在构建我的简历时拾取相结合,您就拥有了一个非常强大的系统。
一旦这一切都正常工作,我就添加了动态更新系统。这工作起来是这样的:
- Fly 服务器在磁盘上保留我网站的 git 存储库的副本,在应用程序启动时克隆一个新副本(TODO:修复)
- 当我在 GitHub 上向该网站提交内容或有人在 Patreon 上注册时,他们会将 webhook 发送到我的网站
- Webhook 会触发重建,从 GitHub 获取新的提交,然后使用我上面概述的整个过程重建站点。
这是您达到这一点的方式:
现在更有意义了!我对结果感到非常满意,并且很高兴看到未来我能用它做些什么。
我环顾四周,似乎没有这个概念的名称。为了引发有人在互联网上对我进行错误的称呼,我将其称为动态方法。它是一个动态网站,当情况发生变化时会重建其静态网站。
飞
不久前,我因为撰写有关它们的文章而从 Fly 获得了一些免费积分。请在阅读本节时牢记这一点。我的设置对 Fly 没有任何硬性要求,但事实上它们具有开箱即用的任播路由,这确实使 XeDN 和 xesite 变得很方便。
我的网站现在有一些移动组件。以下是对正在发生的事情的快速概述:
xesite
是为您正在阅读的网站提供服务的二进制文件。这就是所有重建之类的事情。它是用 Go 编写的,也是我最熟悉的。
你之前不是用 Go 用 Rust 重写了它吗?为什么要回去?
Go 是我最好的语言。它不是一个完美的山上闪亮之城,但我可以不假思索地编写和维护它。我还不能对 Rust 说同样的话。可以说没有什么可以阻止它成为那样,但我想要一些更容易实现的东西,因为这已经是几个月的工作了。编辑所有文章花了很长时间。
patreon-saasproxy 和 OAuth2“有趣”
当它启动时,它会联系patreon-saasproxy获取Patreon的身份验证令牌。最初,我打算将其设为 Patreon API 的完整反向代理,但我使用的 Patreon API 绑定不支持此操作,因此我只是将其设为令牌源。
Go oauth2 库似乎在设计时并没有考虑到这种用例。为了让事情正常工作,我必须像这样编写自己的TokenSource :
type remoteTokenSource struct { curr * oauth2 . Token lock sync . Mutex remoteURL string httpClient * http . Client }
func ( r * remoteTokenSource ) fetchToken ( ) ( * oauth2 . Token , error ) { resp , err := r . httpClient . Get ( r . remoteURL ) if err != nil { return nil , err } defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK { return nil , web . NewError ( http . StatusOK , resp ) }
var tok oauth2 . Token if err := json . NewDecoder ( resp . Body ) . Decode ( & tok ) ; err != nil { return nil , err }
return & tok , nil }
func ( r * remoteTokenSource ) Token ( ) ( * oauth2 . Token , error ) { r . lock . Lock ( ) defer r . lock . Unlock ( )
if r . curr == nil { tok , err := r . fetchToken ( ) if err != nil { return nil , err } r . curr = tok return tok , nil }
if r . curr . Expiry . Before ( time . Now ( ) ) { tok , err := r . fetchToken ( ) if err != nil { return nil , err } r . curr = tok }
return r . curr , nil }
它有效,但有点hacky。理想情况下,我希望将来使其更加通用(这样我就可以让它管理来自不同 OAuth2 源的其他令牌),但这具有目前有效的优点。我有点讨厌 Patreon API 是废弃软件,但我可以感受到。
氙DN
目前xesite
和 XeDN 之间没有直接依赖关系,但实际上xesite
提供的所有服务都以某种方式依赖于 XeDN。如果您想了解有关 XeDN 的更多信息,可以阅读以下文章:
米
我并没有在我的博客上真正详细地提到 mi(我可能要等到我重写了其中的很大一部分才详细介绍),但它基本上是一个个人 API 服务器,可以执行一系列操作我觉得对自己方便的事情。
其中之一是一些代码,它将抓取我博客的 JSONFeed,抓取新文章,并在几个地方宣布它们。
我真的希望这可以包括 Patreon,但他们似乎对维护他们的 API 没有兴趣。我不确定我是否想要对他们的网络应用程序进行逆向工程以使其工作,但我可能必须这样做。不过那是另一次了。
结论
Xesite 会留下来。我希望这能让您大致了解我所做的一切。我对结果感到非常满意,并且很高兴看到未来我能用它做些什么。
哦,顺便说一下,因为 MDX 允许我在博客文章中嵌入 React 组件,所以我可以这样做:
我现在可以在我的博客文章中嵌入任意 HTML 和 React 组件!这是我最近的故事帖子如何运作的关键部分。想象一下我能用这个做什么!
我学到的东西
语义导入版本控制实际上并没有那么糟糕。我决定在为该版本的网站编写代码时使用它,因为我想给它一个公平的评估。没关系。我不同意设计决策,但在实践中很好。
我的文章比我想象的要多。我知道我有很多东西,但接触每一个文件让我意识到这些年来我写了多少。我真的为此感到自豪。
React 和 Tailwind 非常强大。 Xeact已经不足以满足我的需求了,因为我已经无法满足它的需求了。遇到这种情况有点糟糕,但我很高兴能够使用 Xeact 来帮助我学习完成这项工作所需的知识。
我需要修复的错误
- 该网站尚未构建系列索引或标签索引页面。系列索引很快就会创建,但我还不确定如何处理标签。
- 该网站尚未显示以分钟为单位的阅读时间。我正在等待 Lume 修补pagefind以更好地处理这个问题。
- 通过pagefind搜索超级卡顿。我将努力改进这一点,但这只是暂时的。
- 该网站还没有合适的 404 页面。
- ? 帖子必须重命名,而且我将旧名称转发到新位置的所有尝试都没有成功。
这是接下来的一百篇文章。外出注意安全!