<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>0WD0</title>
  <subtitle>I build tools for the seams between editors, operating systems, developer workflows, and agent runtimes.</subtitle>
  <id>https://0wd0.com/</id>
  <link href="https://0wd0.com/atom.xml" rel="self" type="application/atom+xml" />
  <link href="https://0wd0.com/" />
  <updated>2026-08-08T00:00:00.000Z</updated>
  <author><name>0WD0</name></author>
  <entry>
    <title>从 Org 到 Web：建站记</title>
    <id>https://0wd0.com/posts/building-this-site/</id>
    <link href="https://0wd0.com/posts/building-this-site/" />
    <published>2026-08-08T00:00:00.000Z</published>
    <updated>2026-08-08T00:00:00.000Z</updated>
    <summary>我为什么没有采用 Org → Markdown，以及 ox-edn、Loam 和 SvelteKit 如何把 Org 变成一个网站。</summary>
    <category term="org" />
    <category term="emacs" />
    <category term="clojure" />
    <category term="sveltekit" />
    <content type="html">&lt;article class=&quot;org-document&quot; data-page-id=&quot;building-this-site:0d3edb2c-00ce-4e89-8f69-bcd6acd34bb3&quot;&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我已经有一个用 Quartz 搭建、可以正常发布的博客，最后还是重新做了这个网站。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;页面生成早已有成熟答案。麻烦来自内容模型：我的写作和知识整理一直发生在 Emacs、Org 和 org-roam 里，旧网站接收的却是转换后的 Markdown。两边使用不同的内容模型，我就得决定哪些 Org 语义值得保留，再在下游逐项补回 ID、链接和页面关系。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;2026 年 5 月，我在知识库里写过一句话：
&lt;/p&gt;&lt;blockquote&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我们不需要先转换为 Markdown，这对于 Org 文件来说是语义丢失。
&lt;/p&gt;&lt;/blockquote&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这个想法最先落在了 &lt;a href=&quot;https://github.com/0WD0/majutsu/tree/main/docs&quot; rel=&quot;noopener noreferrer external&quot;&gt;Majutsu 的文档站&lt;/a&gt;上。那套尚未上线的站点已经使用 &lt;a href=&quot;https://github.com/0WD0/ox-edn&quot; rel=&quot;noopener noreferrer external&quot;&gt;ox-edn&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/0WD0/loam&quot; rel=&quot;noopener noreferrer external&quot;&gt;Loam&lt;/a&gt; 编译 Org，再通过自定义 Astro content loader 把产物交给 Starlight。它证明了内容层可以脱离 Markdown，也让我看到另一个边界：文档站的侧栏、目录、搜索和前后页有成熟答案，个人网站的页面结构却需要自己决定。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;到 2026 年 8 月 7 日，我已经不需要重新验证内容编译器了。新的问题是，什么样的 Web shell（借用 desktop shell 的说法，指承载已编译内容、负责路由、布局和交互的那一层）适合项目页、长文、threads、backlinks 和逐步增加的交互。于是我保留 ox-edn 和 Loam，把 Starlight 换成了 SvelteKit。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这篇文章记录的就是这三层边界。
&lt;/p&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;why-not-markdown&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#why-not-markdown&quot;&gt;我不想把 Org 当成一种比较奇怪的 Markdown&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;最顺手的方案是把 Org 转成 Markdown，再交给成熟的静态站点生成器。标题、段落、列表和代码块通常都能顺利通过，问题出在我实际依赖的那部分 Org。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我的 Org 文件里有 ID、&lt;code class=&quot;org-verbatim&quot;&gt;CUSTOM_ID&lt;/code&gt;、property drawer、tags、TODO、source block、target、footnote，以及 id: / fuzzy / file 等不同类型的链接。org-roam 又把稳定的 ID 作为节点身份。&lt;span data-org-link-occurrence id=&quot;org-link-1-0-2-3&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://0wd0.com/projects/majutsu/&quot;&gt;Majutsu&lt;/a&gt; 的手册本身就是 Org；&lt;span data-org-link-occurrence id=&quot;org-link-1-0-2-5&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://0wd0.com/projects/touchdeck/&quot;&gt;TouchDeck&lt;/a&gt; 的笔记会继续连到 niri、fcitx5 和 librime。这些内容共同组成一张持续变化的图。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;如果中间加入 Markdown，我还要定义一套映射规则：
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Org &lt;code class=&quot;org-verbatim&quot;&gt;ID&lt;/code&gt; 在 Markdown 里如何表示？
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;CUSTOM_ID&lt;/code&gt; 和标题 slug 谁决定最终锚点？
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;跨文件 &lt;code class=&quot;org-verbatim&quot;&gt;id:&lt;/code&gt; 链接在哪一层解析？
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;反向链接根据哪一份数据生成？
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;原始文件里的源码区间能否继续追踪？
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Org parser 的行为变化以后，哪一层负责同步？
&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;org-paragraph&quot;&gt;ox-hugo 和 Quartz 已经证明这条路线可以工作。放到我的网站里，Markdown 会成为一份需要长期维护的公共中间模型。我更希望下游直接接收 Emacs 解析出来的 Org。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;pipeline-overview&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#pipeline-overview&quot;&gt;三层管线各自负责什么&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;现在的构建过程是：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-emacs-mode=&quot;text-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;Org
 │  Emacs / org-element + ox-edn
 ▼
Envelope v1 / EDN
 │  Loam
 ▼
Manifest + Search + Graph + HTML + Org source
 │  SvelteKit
 ▼
Static website
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;三层各自只处理一类问题：
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;ox-edn 解析单份 Org，并把结果编码成 EDN；
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Loam 汇总所有文档，决定页面边界，解析跨文件链接，再生成全站产物；
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;SvelteKit 读取这些产物，负责布局、交互和静态页面。
&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;org-paragraph&quot;&gt;TypeScript 不解析 Org，Emacs 不决定页面长什么样，Loam 也不维护浏览器组件。多出来的两层就是用来守住这些边界的。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;ox-edn-frontend&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#ox-edn-frontend&quot;&gt;ox-edn：让 Emacs 负责解释 Org&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;ox-edn 的边界很窄：调用 &lt;code class=&quot;org-verbatim&quot;&gt;org-element&lt;/code&gt; 解析单份 Org，再把 AST 和导出信息编码成 EDN。它不决定 URL，也不生成网站。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;核心入口仍然是 Org 自己的 parser：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-emacs-lisp&quot; data-emacs-mode=&quot;emacs-lisp-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;(&lt;span class=&quot;ef-elisp-function ef-font-lock-function-name-face&quot; data-emacs-faces=&quot;elisp-function font-lock-function-name-face&quot;&gt;org-element-parse-buffer&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我没有在 JavaScript、Rust 或 Clojure 里再实现一个「足够像 Org」的 parser。headline、property、link 和 target 先由正在运行的 Emacs/Org 解释，ox-edn 只负责把结果带出 Emacs。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;生产构建输出带版本的数据结构 Envelope v1，大致长这样：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-clojure&quot; data-emacs-mode=&quot;clojure-ts-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;&lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;ef-font-lock-type-face&quot; data-emacs-faces=&quot;font-lock-type-face&quot;&gt;ox-edn&lt;/span&gt;&lt;span class=&quot;ef-default&quot; data-emacs-faces=&quot;default&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;schema-version&lt;/span&gt; &lt;span class=&quot;ef-font-lock-number-face&quot; data-emacs-faces=&quot;font-lock-number-face&quot;&gt;1&lt;/span&gt;
 &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;ef-font-lock-type-face&quot; data-emacs-faces=&quot;font-lock-type-face&quot;&gt;ox-edn&lt;/span&gt;&lt;span class=&quot;ef-default&quot; data-emacs-faces=&quot;default&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;exporter&lt;/span&gt;
 &lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;ox-edn&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:version&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:emacs-version&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:org-version&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;}&lt;/span&gt;

 &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;ef-font-lock-type-face&quot; data-emacs-faces=&quot;font-lock-type-face&quot;&gt;ox-edn&lt;/span&gt;&lt;span class=&quot;ef-default&quot; data-emacs-faces=&quot;default&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;source&lt;/span&gt;
 &lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:path&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;content/posts/building-this-site.org&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:sha256&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;...&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:encoding&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;}&lt;/span&gt;

 &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;ef-font-lock-type-face&quot; data-emacs-faces=&quot;font-lock-type-face&quot;&gt;ox-edn&lt;/span&gt;&lt;span class=&quot;ef-default&quot; data-emacs-faces=&quot;default&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;document&lt;/span&gt; &lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;{&lt;/span&gt;...&lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;ef-font-lock-type-face&quot; data-emacs-faces=&quot;font-lock-type-face&quot;&gt;ox-edn&lt;/span&gt;&lt;span class=&quot;ef-default&quot; data-emacs-faces=&quot;default&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;diagnostics&lt;/span&gt; &lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;[]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这里有三条刻意收紧的约束：
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;source path 必须是相对仓库根目录的 POSIX path，&lt;code class=&quot;org-verbatim&quot;&gt;/home/...&lt;/code&gt; 之类的机器绝对路径不能进入公开产物；
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;ox-edn 按字节读取源文件，验证内容可以无损往返 UTF-8，并计算 SHA-256，下游因此能确认 AST 对应的是哪一份源文件；
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;生产导出默认使用 strict diagnostic policy，源码区间越界、property 读取失败、未知节点和不可序列化的值都会让导出失败，已有产物不会被半成品覆盖。
&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;org-paragraph&quot;&gt;链接也按同样的边界处理。对于 Org 能在当前文档中解析的 ID、custom ID、fuzzy 和 radio link，ox-edn 会附上目标 ID 与源码区间等提示。跨文档解析留给 Loam，因为它需要看到完整的内容集合。
&lt;/p&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h3 class=&quot;org-heading&quot; id=&quot;htmlize-comparison&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#htmlize-comparison&quot;&gt;代码高亮沿用 Emacs，HTML 留给 Loam&lt;/a&gt;&lt;/h3&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Org 的 HTML exporter 已经可以借助 htmlize 处理代码高亮。&lt;code class=&quot;org-verbatim&quot;&gt;ox-html&lt;/code&gt; 进入对应语言的 major mode，让 font-lock 产生 face，再由 htmlize 输出 inline style 或 CSS class。如果目标只是一份 Org 对应一个 HTML 页面，我会直接使用 &lt;code class=&quot;org-verbatim&quot;&gt;ox-html + htmlize&lt;/code&gt;。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这个网站需要的边界更早一些。ox-edn 记录 face 及其字符区间，把 &lt;code class=&quot;org-verbatim&quot;&gt;span&lt;/code&gt; 留给后面的渲染阶段：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-clojure&quot; data-emacs-mode=&quot;clojure-ts-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;&lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:provider&lt;/span&gt; &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:emacs-font-lock&lt;/span&gt;
 &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:mode&lt;/span&gt; &lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;svelte-ts-mode&amp;quot;&lt;/span&gt;
 &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:runs&lt;/span&gt; &lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:start&lt;/span&gt; &lt;span class=&quot;ef-font-lock-number-face&quot; data-emacs-faces=&quot;font-lock-number-face&quot;&gt;1&lt;/span&gt;
         &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:end&lt;/span&gt; &lt;span class=&quot;ef-font-lock-number-face&quot; data-emacs-faces=&quot;font-lock-number-face&quot;&gt;7&lt;/span&gt;
         &lt;span class=&quot;ef-clojure-ts-keyword-face&quot; data-emacs-faces=&quot;clojure-ts-keyword-face&quot;&gt;:faces&lt;/span&gt; &lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;font-lock-keyword-face&amp;quot;&lt;/span&gt;&lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;]}]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Emacs 在这里回答「哪些字符带有哪些 face」。Loam 稍后把这些区间转成经过约束的 HTML class，网站样式表再决定颜色。当前 Emacs 主题的 RGB 值不会被写进内容。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这条路径保留了 htmlize 最吸引我的部分：代码仍由实际使用的 major mode 处理。代价也很明确，结果依赖本机安装的 major mode 和个人配置。固定版本的 Shiki 或 Tree-sitter grammar 更适合追求环境无关的通用 CI；我的目标则是让网页尽量复用日常编辑时的语言配置。
&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;loam-compiler&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#loam-compiler&quot;&gt;Loam：从 Org AST 得到全站页面&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;ox-edn 回答一份 Org 写了什么，Loam 处理这些 Org 在同一个网站里的关系。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;严格编译管线大致是：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-emacs-mode=&quot;text-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;load
  → validate
  → normalize
  → partition
  → index
  → resolve
  → render
  → emit
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;其中最重要的一步是 partition。一棵 Org AST 不必对应一个网页：页面可以来自完整的 Org 文件，也可以来自某个文件中的一个 subtree，这两种形态也正好对应 org-roam 的文件节点和标题节点。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;personal profile 目前采用两条页面归属规则：
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;文档根节点同时声明 &lt;code class=&quot;org-verbatim&quot;&gt;ID&lt;/code&gt; 和 &lt;code class=&quot;org-verbatim&quot;&gt;EXPORT_FILE_NAME&lt;/code&gt; 时，整个 Org 文件构成一个页面；
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p class=&quot;org-paragraph&quot;&gt;文档根节点没有声明这组属性时，带有页面身份和发布路径的一级 headline 各自构成一个页面，其 subtree 是该页面拥有的源码。
&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p class=&quot;org-paragraph&quot;&gt;页面归属只能由一个 &lt;code class=&quot;org-verbatim&quot;&gt;:partitioner&lt;/code&gt; 提供。这样，哪个 subtree 属于哪个 URL 不会受 extension 加载顺序影响。已有的 &lt;code class=&quot;org-verbatim&quot;&gt;:page-partitioners&lt;/code&gt; 仍可在分页后追加转换，但不能重新定义页面身份。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;文档页的 metadata 继续写在 Org 里：
&lt;/p&gt;&lt;pre class=&quot;org-example&quot;&gt;:PROPERTIES:
:ID: 0d3edb2c-00ce-4e89-8f69-bcd6acd34bb3
:EXPORT_FILE_NAME: posts/building-this-site
:LOAM_KIND: post
:LOAM_STATUS: stable
:LOAM_PUBLISHED_AT: 2026-08-08
:END:
#+FILETAGS: :org:emacs:sveltekit:
&lt;/pre&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Loam 随后为页面、Org ID、&lt;code class=&quot;org-verbatim&quot;&gt;CUSTOM_ID&lt;/code&gt;、headline、target 和 anchor 建立全局索引，再解析跨文件链接。正向链接、反向链接和 graph edge 都来自同一次解析，不需要由 Web 层重新推断。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;最终构建产物包括：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-emacs-mode=&quot;text-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;manifest.json
build-report.json
search-index.json
graph.json
pages/*.html
source/**/*.org
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;manifest.json&lt;/code&gt; 记录 route、title、description、tags、status、源码区间、headings、backlinks 和 outgoing links；&lt;code class=&quot;org-verbatim&quot;&gt;search-index.json&lt;/code&gt; 是搜索需要的精简数据；&lt;code class=&quot;org-verbatim&quot;&gt;graph.json&lt;/code&gt; 只保留节点和边；&lt;code class=&quot;org-verbatim&quot;&gt;source/**/*.org&lt;/code&gt; 保存每个公开页面实际拥有的 Org source。正文在这一层已经变成确定的 HTML fragment。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;emit 之前，Loam 会递归检查公开 manifest、report 和 HTML fragment。发现机器绝对路径就拒绝发布。strict renderer 遇到未知 Org node 也不会只渲染 children 后继续构建，任意 HTML export block 同样不能直接穿透到页面。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;sveltekit-shell&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#sveltekit-shell&quot;&gt;SvelteKit 只消费构建产物&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Loam 生成完整的一代产物后，SvelteKit 只读取 &lt;code class=&quot;org-verbatim&quot;&gt;.cache/loam&lt;/code&gt;。TypeScript 不读 EDN，也不重新解释 Org AST。catch-all route 从 manifest 枚举路径，构建时加载对应的 HTML fragment，再由 &lt;code class=&quot;org-verbatim&quot;&gt;adapter-static&lt;/code&gt; prerender 成普通静态文件。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;页面组件的核心接近这样：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-svelte&quot; data-emacs-mode=&quot;svelte-ts-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;&lt;span class=&quot;ef-font-lock-delimiter-face&quot; data-emacs-faces=&quot;font-lock-delimiter-face&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;ef-font-lock-function-name-face&quot; data-emacs-faces=&quot;font-lock-function-name-face&quot;&gt;article&lt;/span&gt; &lt;span class=&quot;ef-font-lock-property-name-face&quot; data-emacs-faces=&quot;font-lock-property-name-face&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;ef-font-lock-operator-face&quot; data-emacs-faces=&quot;font-lock-operator-face&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;ef-font-lock-string-face&quot; data-emacs-faces=&quot;font-lock-string-face&quot;&gt;&amp;quot;org-content&amp;quot;&lt;/span&gt;&lt;span class=&quot;ef-font-lock-delimiter-face&quot; data-emacs-faces=&quot;font-lock-delimiter-face&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ef-font-lock-misc-punctuation-face&quot; data-emacs-faces=&quot;font-lock-misc-punctuation-face&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;ef-font-lock-keyword-face&quot; data-emacs-faces=&quot;font-lock-keyword-face&quot;&gt;html&lt;/span&gt; data.html&lt;span class=&quot;ef-font-lock-bracket-face&quot; data-emacs-faces=&quot;font-lock-bracket-face&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;ef-font-lock-delimiter-face&quot; data-emacs-faces=&quot;font-lock-delimiter-face&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;ef-font-lock-function-name-face&quot; data-emacs-faces=&quot;font-lock-function-name-face&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;ef-font-lock-delimiter-face&quot; data-emacs-faces=&quot;font-lock-delimiter-face&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;{@html}&lt;/code&gt; 的信任边界在构建阶段。HTML 来自受控 Org source，并经过 Loam 的严格 renderer。外部内容一旦进入这条管线，就必须重新设计这层安全边界。
&lt;/p&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h3 class=&quot;org-heading&quot; id=&quot;why-sveltekit&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#why-sveltekit&quot;&gt;为什么是 SvelteKit&lt;/a&gt;&lt;/h3&gt;&lt;p class=&quot;org-paragraph&quot;&gt;到了这一层，输入已经是 HTML 和 JSON，Web shell 不会再影响 Org 的内容模型。Astro、Nuxt、Next.js，甚至一组手写模板都能接在 Loam 后面。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我已经在 Majutsu 文档站里用过 Astro + Starlight。那是一个合适的组合：Starlight 直接提供文档侧栏、页面目录、搜索、前后页和主题切换，我的工作集中在接入 Loam 的 loader 和少数组件上。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;个人网站的信息架构更自由。首页、项目页、thread、文章两侧的关系栏、revision history 和内部链接预览都要按自己的内容模型设计。继续使用 Starlight，接下来的工作会变成逐项替换它的文档外壳；预设更少的 SvelteKit 做起来更省力。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;部署方式也很明确：最终产物必须是纯静态文件。&lt;code class=&quot;org-verbatim&quot;&gt;adapter-static&lt;/code&gt; 保留了 SvelteKit 的路由和数据加载方式，生产服务器却只需要提供 HTML、CSS、JavaScript 和 JSON，不必运行 Node。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;在组件层面，Svelte、Vue 和 React 都能完成这些交互，最后的选择取决于交互的形状。这个网站没有组件库，也没有复杂的全局状态；目前的交互只有搜索面板、链接预览、修订记录和公式复制，状态大多留在单个组件里。Svelte 的单文件组件和局部 CSS 适合这种自定义页面，&lt;code class=&quot;org-verbatim&quot;&gt;$state&lt;/code&gt; / &lt;code class=&quot;org-verbatim&quot;&gt;$derived&lt;/code&gt; 又刚好对应局部状态和派生数据。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Vue 是最接近的组件层备选。Vue SFC、Composition API 和 Nuxt 的静态生成同样能完成这些工作。我没有做一轮框架 benchmark；选择依据是自己更喜欢 Svelte 在小型交互上的写法，也打算让将来的 Org component islands 使用 Svelte。这个网站不依赖组件库，React 庞大的生态在这里帮不上太多忙。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;还有一个原因来自前两层的设计：内容已经由 Loam 编译完成，我不需要前端框架再解析 Markdown、管理 front matter 或建立一套 content collection。SvelteKit 只接收构建产物，处理路由、组件和静态导出。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;SvelteKit 是一个有前情的务实选择，替换成本也被限制在 Web 层。以后即使换掉它，Org 的页面身份、链接和修订模型也不需要重做。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h3 class=&quot;org-heading&quot; id=&quot;sveltekit-build&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#sveltekit-build&quot;&gt;构建复用我日常使用的 Doom Emacs&lt;/a&gt;&lt;/h3&gt;&lt;p class=&quot;org-paragraph&quot;&gt;构建脚本递归查找 &lt;code class=&quot;org-verbatim&quot;&gt;content/&lt;/code&gt; 下的 &lt;code class=&quot;org-verbatim&quot;&gt;.org&lt;/code&gt; 文件，再通过 &lt;code class=&quot;org-verbatim&quot;&gt;emacsclient&lt;/code&gt; 把整批导出交给我日常使用的 Doom Emacs。Org parser、个人配置和语言到 major mode 的映射因此来自同一个 Emacs 环境。如果默认 server 不在运行，脚本会启动一个正常加载 Doom 配置的临时 daemon。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;所有文件成为 Envelope 后，构建脚本一次性交给 Loam：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-emacs-mode=&quot;sh-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;bb bundle --profile personal ...
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;搜索索引不会进入每个页面的 prerender payload。SearchPalette 第一次打开时才获取 &lt;code class=&quot;org-verbatim&quot;&gt;/search-index.json&lt;/code&gt;；文章页也只携带当前页面需要展示的 outgoing target 摘要。新增文章不会仅仅因为搜索索引变化，就改写每一篇旧文章的 HTML。
&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;org-publishing-landscape&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#org-publishing-landscape&quot;&gt;把这套架构放回 Org publishing 生态里&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;比较 ox-edn 时，需要把视角从代码高亮扩展到完整的 Org publishing：谁解释 Org，什么构成公开页面，全站关系又在哪一层建立。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;a href=&quot;https://orgmode.org/worg/org-blog-wiki.html&quot; rel=&quot;noopener noreferrer external&quot;&gt;Worg 的 Blogs and Wikis with Org&lt;/a&gt; 收录了不少不同年代的方案。我用它观察各个项目如何安排 Org parser、页面边界和 Web 层，功能数量只作参考。
&lt;/p&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h3 class=&quot;org-heading&quot; id=&quot;emacs-u76f4-u63a5-u751f-u6210-html&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#emacs-u76f4-u63a5-u751f-u6210-html&quot;&gt;Emacs 直接生成 HTML&lt;/a&gt;&lt;/h3&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;org-publish + ox-html&lt;/code&gt; 是最完整的官方 baseline。&lt;a href=&quot;https://orgmode.org/manual/Publishing.html&quot; rel=&quot;noopener noreferrer external&quot;&gt;Org manual 的 Publishing 章节&lt;/a&gt;把它定义为一套面向互链 Org 文件的发布系统：project 可以组合多个 component，HTML 之外的附件可以一同复制，file link 中的 heading、custom ID 和 target 也会转换成对应的网页锚点。它还内置 sitemap、跨文件 index 和基于 timestamp 的增量发布。公开内容天然是一份 Org 文件对应一份网页时，这套能力已经很完整。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;它的核心抽象始终是「选择一组文件，对每个文件调用 publishing function」。默认页面边界由文件决定。我的内容模型还需要另一层：完整文件可以成为页面，文件中的 subtree 也可以拥有独立 ID、URL 和精确 source。&lt;code class=&quot;org-verbatim&quot;&gt;org-publish&lt;/code&gt; 支持写 Elisp 改造 publishing function，但这套 logical page 模型需要自己实现。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;全站关系也会改变增量构建的含义。假设 B 新增一条指向 A 的链接，B 的源文件发生了变化，A 的 backlink 面板也要随之更新。基于源文件 timestamp 的发布模型只会自然地选中 B；目标页 A 的反向依赖需要额外追踪。Org manual 也提醒，&lt;code class=&quot;org-verbatim&quot;&gt;SETUPFILE&lt;/code&gt; 和 &lt;code class=&quot;org-verbatim&quot;&gt;INCLUDE&lt;/code&gt; 等外部依赖变化时可能需要强制 republish。Loam 会先建立完整索引，再把 backlinks、outgoing links 和前后页等关系计入每个页面的 &lt;code class=&quot;org-verbatim&quot;&gt;pageBuildDigest&lt;/code&gt;。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;HTML 作为主产物也限制了下游能看到的信息。搜索、graph、thread 和 revision history 需要结构化的页面 metadata 与链接边，单独遍历最终 HTML 很难还原 Org ID、源码区间和链接类型。&lt;code class=&quot;org-verbatim&quot;&gt;org-publish&lt;/code&gt; 提供 preparation function、completion function 和自定义 exporter，这些扩展点都能承载上述逻辑。代价是 partition、全局索引、依赖失效和多种构建产物会逐渐堆进同一套 Elisp 发布配置。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;a href=&quot;https://github.com/bastibe/org-static-blog&quot; rel=&quot;noopener noreferrer external&quot;&gt;org-static-blog&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/emacs-love/weblorg&quot; rel=&quot;noopener noreferrer external&quot;&gt;weblorg&lt;/a&gt; 把文章列表、归档、Feed、tag pages、模板和主题等常见站点需求继续补齐。如果只写传统博客，这条管线比 ox-edn → Loam → SvelteKit 短得多。我的网站则把 corpus compiler 单独放进 Loam，让 ox-html 和 Web shell 都保持较窄的职责。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h3 class=&quot;org-heading&quot; id=&quot;u4ece-u73b0-u6709-org-u5de5-u4f5c-u6d41-u4e2d-u9009-u62e9-u9875-u9762&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#u4ece-u73b0-u6709-org-u5de5-u4f5c-u6d41-u4e2d-u9009-u62e9-u9875-u9762&quot;&gt;从现有 Org 工作流中选择页面&lt;/a&gt;&lt;/h3&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;a href=&quot;https://github.com/novoid/lazyblorg&quot; rel=&quot;noopener noreferrer external&quot;&gt;lazyblorg&lt;/a&gt; 是与我的需求最接近的现成方案之一。文章可以位于普通 Org 文件的不同位置；除了与日期绑定的 temporal pages，它还提供持续更新的 persistent pages，以及 backlinks、TagTrees、Feed 和原始 Org source。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;它使用自己的 Python parser，并在部分语法上回退到 Pandoc，也有一套围绕 &lt;code class=&quot;org-verbatim&quot;&gt;:blog:&lt;/code&gt; tag、ID 和 DONE/LOGBOOK 的发布约定。这些约定与 Karl Voit 的工作流高度契合。我的工作流则要求网站复用正在运行的 Emacs/Org，包括自己的 Org fork 和 Doom 中的语言映射。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h3 class=&quot;org-heading&quot; id=&quot;org-u8f6c-u6210-markdown&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#org-u8f6c-u6210-markdown&quot;&gt;Org 转成 Markdown&lt;/a&gt;&lt;/h3&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这是我实际用过最久的一条路线。&lt;a href=&quot;https://ox-hugo.scripter.co/&quot; rel=&quot;noopener noreferrer external&quot;&gt;ox-hugo&lt;/a&gt; 支持 one-post-per-file 和 one-post-per-subtree，也会利用 property、tag inheritance 和 TODO state 生成 Hugo front matter。它对 Org 的支持远比「转一下 Markdown」这句话听起来完整。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;进入 Hugo 或 Quartz 后，系统里仍然会同时存在 Org 和 Hugo-flavored Markdown 两套内容模型：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-emacs-mode=&quot;text-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;Org / org-roam
      ↓ ox-hugo
Hugo-flavored Markdown + front matter
      ↓ Hugo / Quartz
Web content model
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我遇到的麻烦主要发生在写作阶段。为了让服务于 org-roam、agenda 和项目管理的 Org 同时满足 Hugo 的页面模型，我会提前考虑 heading 能否继续嵌套、subtree 是否必须成为 export leaf，以及某个 property 究竟服务于哪一层。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Loam 的 partitioner 仍然需要决定页面边界，但它直接在原始 Org AST 上完成这一步，不再生成另一份过渡 Org 或 Markdown。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h3 class=&quot;org-heading&quot; id=&quot;u72ec-u7acb-org-parser-u548c-graph-first-u5de5-u5177&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#u72ec-u7acb-org-parser-u548c-graph-first-u5de5-u5177&quot;&gt;独立 Org parser 和 graph-first 工具&lt;/a&gt;&lt;/h3&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;a href=&quot;https://github.com/teesloane/firn&quot; rel=&quot;noopener noreferrer external&quot;&gt;Firn&lt;/a&gt; 使用 Rust 和 Orgize 从 Org 文件生成静态站点；&lt;a href=&quot;https://orga.js.org/guides/orga-build/&quot; rel=&quot;noopener noreferrer external&quot;&gt;Orga / orga-build&lt;/a&gt; 把 Org AST 带进 JavaScript、Unified 和 JSX 生态。它们不要求构建机启动 Emacs，更容易进入普通 CI。相应的取舍是，编辑器和构建系统分别由两个 parser 解释 Org。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;另一类工具直接以 graph 为主界面。&lt;a href=&quot;https://github.com/ikoamu/publish-org-roam-ui&quot; rel=&quot;noopener noreferrer external&quot;&gt;publish-org-roam-ui&lt;/a&gt; 可以从 org-roam files + database 生成静态 digital garden；&lt;a href=&quot;https://github.com/tani/org-roam-ui-lite&quot; rel=&quot;noopener noreferrer external&quot;&gt;org-roam-ui-lite&lt;/a&gt; 也把静态导出作为主要能力。如果目标是把 org-roam graph 放到网上浏览，它们比重做一套个人网站直接得多。我的网站以长文和项目页为主，graph 处在辅助位置。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;把这些方案放在一起，我会这样比较：
&lt;/p&gt;&lt;div class=&quot;org-table-scroll&quot; style=&quot;overflow-x:auto;max-width:100%;margin:1em 0;-webkit-overflow-scrolling:touch&quot;&gt;&lt;table role=&quot;table&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot; style=&quot;border-collapse:collapse;width:max-content;min-width:32rem;max-width:none;font-size:.95em&quot;&gt;&lt;thead&gt;&lt;tr&gt;&lt;th scope=&quot;col&quot; nowrap style=&quot;padding:.45em .65em;border:1px solid #bbb;text-align:left;vertical-align:top;font-weight:600;white-space:nowrap&quot;&gt;方案&lt;/th&gt;&lt;th scope=&quot;col&quot; nowrap style=&quot;padding:.45em .65em;border:1px solid #bbb;text-align:left;vertical-align:top;font-weight:600;white-space:nowrap&quot;&gt;Org 语义由谁解释&lt;/th&gt;&lt;th scope=&quot;col&quot; nowrap style=&quot;padding:.45em .65em;border:1px solid #bbb;text-align:left;vertical-align:top;font-weight:600;white-space:nowrap&quot;&gt;公开页面来自哪里&lt;/th&gt;&lt;th scope=&quot;col&quot; nowrap style=&quot;padding:.45em .65em;border:1px solid #bbb;text-align:left;vertical-align:top;font-weight:600;white-space:nowrap&quot;&gt;Org 后的主要表示&lt;/th&gt;&lt;th scope=&quot;col&quot; nowrap style=&quot;padding:.45em .65em;border:1px solid #bbb;text-align:left;vertical-align:top;font-weight:600;white-space:nowrap&quot;&gt;全站能力&lt;/th&gt;&lt;th scope=&quot;col&quot; nowrap style=&quot;padding:.45em .65em;border:1px solid #bbb;text-align:left;vertical-align:top;font-weight:600;white-space:nowrap&quot;&gt;对我的取舍&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;org-publish + ox-html&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;GNU Emacs Org&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Org 文件&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;HTML&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;sitemap / index 内置，其余可由 Elisp 扩展&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;subtree 页面与关系依赖需要自己实现&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;org-static-blog / weblorg&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;GNU Emacs Org&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Org 文章文件&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;HTML&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;以 blog / site 为中心&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;管线简单，但页面模型以文章为主&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;lazyblorg&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Python parser + Pandoc fallback&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Org 文件中选出的 heading&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;HTML&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;persistent page、backlink、TagTrees、source&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;使用自己的 parser 和发布约定&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;ox-hugo → Quartz&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Emacs export + 下游 Markdown parser&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Org 文件或 &lt;code class=&quot;org-verbatim&quot;&gt;EXPORT_FILE_NAME&lt;/code&gt; subtree&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Hugo Markdown + front matter&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Quartz 提供 backlink、tag 和 graph&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;同时维护两层内容模型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Firn / Orga&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;Orgize / Orga parser&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;目录中的 Org 文件&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;AST / HTML&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;适合 wiki 和 Web 集成&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;编辑器与构建 parser 可能分叉&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;org-roam-ui publishing&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;org-roam DB + Org files&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;org-roam node&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;graph / search data&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;graph 和 backlink 是主模型&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;主要优化 graph browsing&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;ox-edn + Loam&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;我正在使用的 Emacs/Org&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;单个 Org 文件，或某个 Org 文件中的一个 subtree&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;EDN&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;ID、backlink、thread、graph、source、revision&lt;/td&gt;&lt;td style=&quot;padding:.45em .65em;border:1px solid #ccc;text-align:left;vertical-align:top;min-width:6em;word-break:normal;overflow-wrap:normal&quot;&gt;自己维护 compiler，复杂度最高&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这套架构用最高的维护成本换来了我最在意的 authoring model：日常的 Emacs / org-roam 工作流先存在，网站从中选出明确的公开内容，再沿用同一套 Org 语义生成 Web 页面。只写传统博客时，我会直接选择更短的管线。
&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;personal-site&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#personal-site&quot;&gt;发布边界：知识库与公开网站分开管理&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我的日常工作本来就在代码和 Org 之间来回。Majutsu 有 Org 手册和 Elisp 源码，TouchDeck 有 Rust/Wayland 实现和与 niri、fcitx5、librime 相连的设计笔记。很多文章在动笔之前，已经以代码、设计文档或 org-roam note 的形式存在。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;网站不应该迫使我为了发布再维护一套内容。但这也不意味着整个 &lt;code class=&quot;org-verbatim&quot;&gt;~/org&lt;/code&gt; 都应该公开。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;目前 &lt;code class=&quot;org-verbatim&quot;&gt;personal-site/content/&lt;/code&gt; 是明确的发布边界：知识库负责思考和积累，content 负责选择哪些内容成为公开作品。以后可以加入更方便的选取、引用和 transclusion，但自动化不能取消这条安全边界。公开页面中隐藏的 drawer 和 comment 仍会保留在 &lt;code class=&quot;org-verbatim&quot;&gt;/source/...org&lt;/code&gt; 里，因此放进 content 的文件本身就必须适合公开。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;我更愿意把这个网站叫作 personal technical workbench。项目、长文、持续更新的 notes、backlinks、threads 和搜索，是同一批知识在不同阶段的视图。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;publishing-revisions&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#publishing-revisions&quot;&gt;页面身份、内容修订和构建结果分开计算&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;文章发布以后，正文、链接关系和网站外壳会以不同速度变化。只用一个文件哈希无法同时表达这些变化。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;&lt;code class=&quot;org-verbatim&quot;&gt;:ID:&lt;/code&gt; 的 UUID 是页面的稳定身份，&lt;code class=&quot;org-verbatim&quot;&gt;:EXPORT_FILE_NAME:&lt;/code&gt; 决定稳定 URL。&lt;code class=&quot;org-verbatim&quot;&gt;:LOAM_PUBLISHED_AT:&lt;/code&gt; 记录第一次公开的日期，修改正文时保持不变；只有读者值得注意的实质修改才设置 &lt;code class=&quot;org-verbatim&quot;&gt;:LOAM_UPDATED_AT:&lt;/code&gt;。错别字、CSS 和部署脚本的改动不必把文章标记为更新。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;构建侧使用三个 digest：
&lt;/p&gt;&lt;dl&gt;&lt;dt&gt;&lt;code class=&quot;org-verbatim&quot;&gt;sourceRevision&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;p class=&quot;org-paragraph&quot;&gt;公开 Org source 的 SHA-256。一文件一文章时，它覆盖完整文件；collection page 则只覆盖该页面拥有的 subtree。
&lt;/p&gt;&lt;/dd&gt;&lt;dt&gt;&lt;code class=&quot;org-verbatim&quot;&gt;contentRevision&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;p class=&quot;org-paragraph&quot;&gt;标题、公开 metadata 和渲染正文的内容版本。comment 等不进入公开内容的改动不会产生新的 editorial revision。
&lt;/p&gt;&lt;/dd&gt;&lt;dt&gt;&lt;code class=&quot;org-verbatim&quot;&gt;pageBuildDigest&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;&lt;p class=&quot;org-paragraph&quot;&gt;页面内容及其关系依赖的构建版本。正文没变，但另一篇文章新增了指向它的 backlink，这一项仍然会变化。
&lt;/p&gt;&lt;/dd&gt;&lt;/dl&gt;&lt;p class=&quot;org-paragraph&quot;&gt;SvelteKit 默认使用构建时间作为 app version。相同输入连续构建两次时，runtime chunk 名称和页面里的 bootstrap script 也可能变化。我改用 &lt;code class=&quot;org-verbatim&quot;&gt;src/&lt;/code&gt; 和 Svelte 配置计算稳定的 site-shell revision，并把搜索索引改成按需加载。在相同构建环境中，连续两次相同构建现在会生成字节一致的文章 HTML。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;每次 release 仍然是一代完整、不可变、可以原子回滚的静态文件。部署时用：
&lt;/p&gt;&lt;figure class=&quot;org-src-block&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-emacs-mode=&quot;sh-mode&quot; data-highlight-provider=&quot;emacs-font-lock&quot;&gt;rsync --checksum --link-dest=&amp;lt;previous-release&amp;gt; build/ &amp;lt;new-release&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;p class=&quot;org-paragraph&quot;&gt;没有变化的文件直接 hardlink 到上一代，只有变化的文件需要传输。删除旧 release 时，只要新 release 仍引用相同 inode，文件数据就不会消失。完整 generation 与跨代复用可以同时成立。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;公开的页面更新历史只跟踪 editorial revision。一个 jj change 可能同时修改网站代码和几篇文章，也可能只做重构。每次正式部署会合并上一代的 &lt;code class=&quot;org-verbatim&quot;&gt;revision-history.json&lt;/code&gt; 与当前 manifest；同一页面 UUID 的 &lt;code class=&quot;org-verbatim&quot;&gt;contentRevision&lt;/code&gt; 变化时，才追加一条公开修订记录。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;每个页面还有对应的 &lt;code class=&quot;org-verbatim&quot;&gt;/source/...org&lt;/code&gt;。文章页公开完整原始文件，collection page 公开原文件中的精确 subtree，不把 AST 重新导出成「看起来像 Org」的文本。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;Tag 则被投影成 thread。&lt;code class=&quot;org-verbatim&quot;&gt;/threads/emacs/&lt;/code&gt;、&lt;code class=&quot;org-verbatim&quot;&gt;/threads/wayland/&lt;/code&gt; 按 writing、project 和 field note 分组，related threads 来自 tag co-occurrence；搜索中输入 &lt;code class=&quot;org-verbatim&quot;&gt;#emacs&lt;/code&gt; 时也优先匹配 tag。这些页面仍然来自同一份 Org corpus，不需要另一套内容数据库。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;syntax-showcase-link&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#syntax-showcase-link&quot;&gt;语法展示留在单独的回归测试里&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;完整的语法展示有两百多行，覆盖链接、列表、special block、LaTeX、source block、表格、脚注和 drawer。全部塞进正文会把建站文章变成 renderer fixture。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这些内容现在放在单独的 &lt;span data-org-link-occurrence id=&quot;org-link-9-0-2-1&quot;&gt;&lt;/span&gt;&lt;a href=&quot;https://0wd0.com/notes/org-syntax-showcase/&quot;&gt;Org 语法陈列室&lt;/a&gt;。那个页面通过正常内容管线发布，同时充当真实的回归测试；这里则只保留与架构有关的例子。
&lt;/p&gt;&lt;/section&gt;&lt;section class=&quot;org-section&quot; data-org-type=&quot;headline&quot;&gt;&lt;h2 class=&quot;org-heading&quot; id=&quot;self-hosting-post&quot;&gt;&lt;a class=&quot;org-heading-anchor&quot; href=&quot;https://0wd0.com/posts/building-this-site/#self-hosting-post&quot;&gt;这篇文章就是一次端到端构建&lt;/a&gt;&lt;/h2&gt;&lt;p class=&quot;org-paragraph&quot;&gt;这篇文章的源文件是：
&lt;/p&gt;&lt;pre class=&quot;org-example&quot;&gt;content/posts/building-this-site.org
&lt;/pre&gt;&lt;p class=&quot;org-paragraph&quot;&gt;网页侧栏里的 &lt;code class=&quot;org-verbatim&quot;&gt;view Org&lt;/code&gt; 指向本次构建实际发布的 &lt;code class=&quot;org-verbatim&quot;&gt;/source/posts/building-this-site.org&lt;/code&gt;。这份源文件先由 Emacs 解析，经 ox-edn 编码成 EDN；Loam 为它分配 &lt;code class=&quot;org-verbatim&quot;&gt;/posts/building-this-site/&lt;/code&gt;，解析链接并生成正文；SvelteKit 最后把页面 prerender 成静态 HTML。
&lt;/p&gt;&lt;p class=&quot;org-paragraph&quot;&gt;本文直接使用自己的源文件走完整条管线，也成了这个网站的第一份端到端构建测试。
&lt;/p&gt;&lt;/section&gt;&lt;/article&gt;</content>
  </entry>
</feed>
