来自 shell 的小型热重载静态站点生成器。假设 Bash 4.4+。
警告:这里有牦牛!
shite
的工作是帮助我制作我的网站:https://evalapply.org 因此, shite
的范围、(错误)功能集、抛光将始终是生产级的,其中生产“可以在我的机器上运行” )”:)
目录
嗯, shite
目标是制作网站。
它是一个由流水线工作流程组成的小型发布系统,可以选择由文件事件流驱动(用于热重载位)。
上个世纪的 Perl/PHP 绅士黑客不会感到惊讶。
它的存在是因为有人吹着愚蠢的口哨并给牦牛剃毛。
这基本上就是它的作用(参考: shite_templating_publish_sources
函数)。
cat " ${watch_dir} /sources/ ${url_slug} " |
__shite_templating_compile_source_to_html ${file_type} |
__shite_templating_wrap_content_html ${content_type} |
__shite_templating_wrap_page_html |
${html_formatter_fn} |
tee " ${watch_dir} /public/ ${slug} .html "
# The complete "business logic" is 300-ish lines as of this comment,
# counted as all lines except comments and blank lines.
grep -E -v " s?#|^$ "
./bin/{events,metadata,templating,utils,hotreload}.sh |
wc -l
在你变得太兴奋之前,我可以警告你,麻省理工学院的许可证意味着如果这个小垃圾制造商不能让你的狗屎工作,我不必给予狗屎。贡献中充满了更多警告。
最后但并非最不重要的一点是,我特此下令,本文中的所有文本均以肖恩·康纳利 (Sean Connery) 的方式阅读。
在我shite
梦里,我渴望……
最重要的是,保持它(“业务逻辑”)小。小到足以在我的脑海中缓存、调试和重构。
无需超级用户许可即可安装和使用。
极度避免工具链和构建依赖关系。没有 gems/npms/venvs/what-have-yous。因此,Bash 是一种语言,因为 Bash 无处不在。当需要特定的高级功能时,还可以使用pandoc
或tidy
等标准包。
在良好的 ol'heredocs 中使用普通 HTML 设置的无依赖模板。
简单的元数据系统、内容命名空间、静态资产组织等。
Web 服务器可选(或任何类型的服务器进程)。毕竟,我们的目标是静态网站,它与file://
导航配合得很好。
用小的、可组合的、纯功能的、类似 Unix 工具的部分构建它,因为我非常喜欢这类东西。
为自己提供一个类似 REPL 的无缝编辑-保存-构建-预览工作流程。
长时间停顿后,我不小心重新开始写博客。在我能够将文字输入云端之前,我对“现代”静态站点生成器感到困惑。因为 WordPress 太上个世纪了(或者我是这么告诉自己的)。然后我对 SSG Jamstack 定制模板构建等魔法感到恼火。现在我正走在制作这个的黑暗道路上。它的博客地址为:shite:来自 shell 的静态站点:第 1/2 部分
我主要在“热重载”模式下使用 shite,主要是为了撰写帖子(在 org 模式下)并实时预览它们(在 Firefox 中)。不太重要的是,热预览对样式和/或页面模板的修改。最重要的是,在无休止地写一篇文章之后,我在“不热重载”模式下使用它来进行完整的站点重建。
下面的演示示例。
基本上这意味着,如果我在sources
下创建、更新、删除任何文件,它必须自动转换为 HTML,在本地发布到public
,并在我的网站打开的 Web 浏览器中引起适当的页面导航或重新加载操作。
在全新的终端会话或 tmux 窗格中调用“主”脚本。
./shite.sh
根据我在./shite.sh
中的shite_global_data
数组中设置的默认值,它有助于在 Firefox 中打开索引文件。
在 Emacs 或 Vim 中,打开sources
下的一些内容文件。编辑、保存并观看浏览器中显示的内容。 (是的,指定 Emacs/Vim 很愚蠢,因为我根据 inotify 事件触发热操作。显然不同的编辑器对文件更新的方式不同。我使用 Emacs 或 Vim,所以我会监视它们引起的事件,因此它可以在我的机器上运行。: ))。
浏览器通常会记住滚动位置,这很整洁。有时热重载是很糟糕的。所以我只需按空格键并保存内容文件即可再次触发热重载。
转到一些静态资源,例如 CSS 样式表。改变一些东西,比如背景颜色值。保存并在浏览器中观察颜色变化。
调整templates.sh
中的一些模板片段——比如博客文章模板。然后切换到一些博客文章内容文件并修改它以使用修改后的模板触发页面构建(例如按空格键并保存)。
这是一个黑客行为。源下的根index.org 页面很特殊。如果我修改它,那么这意味着我想重建索引页、标签的帖子列表,并重建相关的元文件,如 RSS feed、站点地图、robots.txt 等。
在一个干净的新终端会话中,使用“no”调用shite.sh
,并且可以选择使用部署环境的base_url
:
重建“本地”file:/// 导航的完整站点。真正的“无服务器”:)
./shite.sh " no "
重建完整网站以在我的域下发布。
./shite.sh " no " " https://evalapply.org "
这些标志改变系统的行为。
SHITE_BUILD
设置为“hot”将在“monitor”模式下运行事件系统,从而驱动热重载行为。将其设置为“no”将抑制浏览器热重载。SHITE_DEBUG_TEMPLATES
设置为“调试”将导致在发布任何模板化源内容之前首先获取模板。shite
内部非常 Unixy。或者说我是这么认为的。
代码是函数式编程风格的 Bash。一切都是函数。大多数函数都是纯函数——它们本身就是 Unix 的小工具。大多数逻辑都是面向管道的。这效果出奇的好,因为 Shell 对于 FP 来说并不是一个坏地方。
在使用shite
编写时,我还希望获得类似 REPL 的实时交互式体验,因为我喜欢在 Clojure 和 Emacs 等实时/交互式运行时中工作。
因此, shite
已经成为这种完全反应式事件驱动的系统,能够进行热构建并在保存时重新加载。
目录命名空间主要分为三个:
sources
包含“源”内容,例如用 orgmode 编写的博客文章,以及 CSS、Javascript 和其他静态资源。public
目标bin
URL 命名方案遵循sources
下的子目录结构,并按原样复制到pubilic
目录结构下。由于这是 bog 标准 URL 命名空间方案,因此它也直接适用于已发布的内容。就像这样:
file:///absolute/path/to/shite/posts/slug/index.html
http://localhost:8080/posts/slug/index.html
https://your-domain-name.com/posts/slug/index.html
所有“公共”函数都被命名为shite_the_func_name
。所有“私有”函数都被命名为__shite_the_func_name
。
功能的存在是为了:
在一个干净的新终端会话中:
source ./bin/utils_dev.sh
shitTABTAB
或__shiTABTAB
进行自动补全。type -a func_name
以打印函数的定义并读取其 API。shite_global_data
和shite_page_data
。模板适用于页面片段(如页眉、页脚、导航)和完整页面定义(如默认页面模板)。这些内容以纯 HTML 形式编写在此处文档中。 ./bin/templates.sh
提供了这些。
模板填充有来自不同来源的变量数据:
shite_global_data
包含站点范围的元数据, shite_page_data
包含特定于页面的元数据。某些外部进程必须在处理任何页面之前预先设置这些数组。例如,一个完整的页面可以构造如下:
cat ./sample/hello.md |
pandoc -f markdown -t html |
cat << EOF
<!DOCTYPE html>
<html>
<head>
$( shite_template_common_meta )
$( shite_template_common_links )
${shite_page_data[canonical_url]}
</head>
<body ${shite_page_data[page_id]} >
$( shite_template_common_header )
<main>
$( cat - )
</main>
$( shite_template_common_footer )
</body>
</html>
EOF
shite
的元数据系统被定义为键值对。键命名元数据项,并将与该类型的任何值相关联。下面的例子。
如前所述,运行时元数据由关联数组shite_global_data
和shite_page_data
在环境中携带。这些可以通过直接构建来填充,也可以从外部源更新。
每个页面可以在页面顶部的“前面的内容”中指定自己的元数据。这将与从其他来源派生的页面元数据一起使用。
shite
希望我们使用与给定内容类型兼容的语法来编写前文,如下所示。
使用注释行# SHITE_META
来划分shite
也应该解析为特定于页面的元数据的组织风格元数据。
# SHITE_META
#+title: This is a Title
#+slug: this/is/a/slug
#+date: Friday 26 August 2022 03:38:01 PM IST
#+tags: foo bar baz quxx
# SHITE_META
#+more_org_metadata: but not processed as shite metadata
#+still_more_org_metadata: and still not processed as shite metadata
* this is a top level heading
this is some orgmode content
#+TOC: headlines 1 local
** this is a sub heading
- this is a point
- this is another point
- a third point
编写 Jekyll 风格的 YAML 前面的内容,放在---
分隔符之间。
---
TITLE : This is a Title
slug : this/is/a/slug
DATE : Friday 26 August 2022 03:38:01 PM IST
TAGS : foo BAR baz QUXX
---
# this is a heading
this is some markdown content
## this is a subheading
- this is a point
- this is another point
- a third point
我们可以简单地使用标准<meta>
标签,遵守以下约定: <meta name="KEY" content="value">
。
< meta name =" TITLE " content =" This is a Title " >
< meta name =" slug " content =" this/is/a/slug " >
< meta name =" DATE " content =" Friday 26 August 2022 03:38:01 PM IST " >
< meta name =" TAGS " content =" foo BAR baz QUXX " >
< h1 > This is a heading </ h1 >
< p > This is some text </ p >
< h2 > This is a subheading </ h2 >
< p >
< ul >
< li > This is a point </ li >
< li > This is another point. </ li >
< li > This is a third point. </ li >
</ ul >
</ p >
这里是牦牛!
完全被 Clojure/Lisp/Spreadsheet 风格的即时交互式工作流程宠坏了,我也想在制作中进行热重载和热导航。
但似乎不存在一个独立的实时 Web 开发服务器/工具,它不希望我下载一半的已知互联网作为依赖项。正如我之前所说,这是我非常不想做的事情。
DuckSearch 提供了 Emacs 不耐烦模式,这非常热门,但我不想将其硬连线到我的 Emacs。幸运的是,它还提供了这个令人兴奋的脑波,其中包括“inotify-tools”和“xdotool”:github.com/traviscross/inotify-refresh
火热副本!
因为还有什么比我的电脑对我按下 F5 键更热的呢?仿佛它知道我内心深处真正想要的是什么。
事件子系统与其他所有子系统正交,并与系统的其余部分组合在一起。
该设计是沼泽标准流架构,即。监视文件系统事件,然后过滤、删除重复、分析并将它们路由 (tee) 到不同的事件处理器。目前只有两个这样的处理器;一个用于编译和发布与事件关联的页面或资产,另一个用于根据同一事件热重新加载浏览器(或热导航)。
基本上是这样的:
# detect file events
__shite_detect_changes ${watch_dir} ' create,modify,close_write,moved_to,delete ' |
__shite_events_gen_csv ${watch_dir} |
# hot-compile-and-publish content, HTML, static, etc.
tee >( shite_templating_publish_sources > /dev/null ) |
# browser hot-reload
tee >( __shite_hot_cmd_public_events ${window_id} ${base_url} |
__shite_hot_cmd_exec )
事件只是一个 CSV 记录流,结构如下:
unix_epoch_seconds,event_type,base_dir,sub_dir,url_slug,file_type,content_type `
我们使用事件记录的不同部分来引发不同类型的操作。
前面链接的 inotify-refresh 脚本尝试定期刷新一组浏览器窗口。然而,我们希望非常渴望。对我们的内容文件和/或静态资产的任何编辑操作都必须在显示我们的内容的浏览器选项卡中即时触发热重载/导航操作。
我们想要定义不同的重新加载场景:互斥的、集体详尽的存储桶,我们可以将要监视的文件事件映射到其中。
如果我们这样做,那么我们可以将更新建模为一种预写日志,通过分析管道冲压事件,将它们与完全匹配的场景关联起来,然后最终引发操作。例如:
刷新当前选项卡时
回家什么时候
导航到内容时
由于我们让计算机模拟我们自己的键盘操作,因此它可能会干扰我们个人的操作。如果我们坚持在文本编辑器中写我们的狗屎,并让计算机来做热重载的事情,我们应该保持不生气。
世界上有很多牦牛。
对于真正普遍的多站点发布魔力:
shite
应该在我的 PATH 上可用这是一头小牦牛。我可能很快就会把它剃掉。
显然,人们可以使用流行的 git 主机的 CI 作业来触发shite
构建。但是,当我们已经进步到 1900 年代末的最先进技术时,为什么还要使用笨重的本世纪技术……完全流式传输和完全反应式呢?
除了讽刺之外,我不明白为什么不能在我运行的远程计算机上使用相同的事件系统来添加热部署支持。
在远程盒子上:
sources
的克隆被供奉sources
(减去浏览器观看)是实时的。在我的本地盒子上:
https://mydomain.com/posts/hello/index.html
上按 F5通过 SSH 执行某些操作,将浏览器刷新带回本地,以防热部署到远程服务器。
也许是一些“开发/起草”时间设置/拆卸场景?也许我们用一个“dev_server”函数来启动一个新的垃圾写作会话?
如果你一路走到这里,仍然想做出贡献......
为什么?
为什么以一切神圣和美好的名义,你想要这么做?这不是很明显是一个傻瓜的作品吗?您没有听说过 Bash 甚至不是真正的编程语言吗?难道你的公关将永远消失,你的评论将陷入无名的空白,这不是显而易见的吗?
是的,发送补丁是一个糟糕的主意。
但请通过电子邮件告诉我你对你的屎制造商的希望和梦想!我在 gmail 上以我的名字点姓氏阅读电子邮件。
我们可以一起吹口哨,用我们自己独特的方式吹着愚蠢的曲子,一起给我们各自的牦牛剃毛。
愿源头与我们同在。
本作品获得 MIT 许可证和 CC By-SA 4.0 许可证的双重许可。
SPDX 许可证标识符:mit 或 cc-by-sa-4.0