# Future of SeamJS

> This is the agent view of https://canmi.net/architecture/observation-to-lowering, generated 2026-10-14T11:54:46Z. The same page for humans is the address without `.md`.
> Every page on this site has its agent view at its own address with `.md` appended, so ask for that address directly.

> The text below is in its original language, Chinese (zh), as written.

You are here: [サボり記](https://canmi.net/homepage.md) › architecture › Future of SeamJS

## About this article

> From the published corpus, as of 2026-10-01T14:46:52Z.

| Field | Value |
| --- | --- |
| Title | Future of SeamJS |
| Subtitle | From magic to lowering. |
| Short title | Future of SeamJS |
| Short subtitle | From magic to lowering. |
| Description | SeamJS 将组件语言从 React 转向 Svelte，不再靠执行与 diff 猜测页面结构，而是直接读取公开的 template AST，将静态 markup、动态值与控制流 lowering 为 skeleton、slot 与协议节点，同时为明确的 escape hatch 保留 SSR fallback。 |
| Kind | TechArticle, in architecture |
| Author | Canmi <t@canmi.icu> |
| Language | Chinese (zh), the original |
| Published | 2026-08-22T22:17:34Z |
| Last changed | 2026-08-23T05:27:18Z |
| Length | 10870 words |
| Page | https://canmi.net/architecture/observation-to-lowering |
| This document | https://canmi.net/architecture/observation-to-lowering.md |

## Reads

> Live, counted 2026-10-14T11:54:46Z; held up to five minutes.

547

## Other languages

> Each is a translation of this article, served as text/html, under the title and subtitle its page shows.

| Language | Title | Subtitle | Page |
| --- | --- | --- | --- |
| German (de-DE) | Die Zukunft von SeamJS | Von Magie zum Lowering. | https://canmi.net/architecture/observation-to-lowering?lang=de |
| English (en-US) | Future of SeamJS | From magic to lowering. | https://canmi.net/architecture/observation-to-lowering?lang=en |
| Spanish (es-ES) | El futuro de SeamJS | De magia a lowering. | https://canmi.net/architecture/observation-to-lowering?lang=es |
| French (fr-FR) | L'avenir de SeamJS | De la magie au lowering. | https://canmi.net/architecture/observation-to-lowering?lang=fr |
| Japanese (ja-JP) | SeamJS の今後 | 魔法から lowering へ。 | https://canmi.net/architecture/observation-to-lowering?lang=ja |
| Korean (ko-KR) | SeamJS의 미래 | 마법에서 lowering으로. | https://canmi.net/architecture/observation-to-lowering?lang=ko |
| Chinese (China) (zh-CN) | SeamJS 的未来 | 从魔法到 lowering。 | https://canmi.net/architecture/observation-to-lowering?lang=zh |
| Chinese (Taiwan) (zh-TW) | SeamJS 的未來 | 從魔法到 lowering。 | https://canmi.net/architecture/observation-to-lowering?lang=tw |

## Summary

> Written by a model (anthropic) from the article, as the page shows it.

SeamJS 的 V1 把 React 编译器当黑盒，靠反复 render + diff 去猜页面结构，遇到 price > 10 这类谓词就只能让用户手写 mock，笛卡尔积穷举也随之失效。这篇记录把 UI stack 换成 Svelte 的决定过程：逐一衡量 React 与 TanStack Start、Vue 3 与 Nuxt、SolidJS 的生态困境，也划清与 Astro islands、Qwik resume、Marko 6 在 request-time 是否仍跑 renderer 上的分歧，并解释 markup AST、runes、<style> 与 CSS-in-JS 的取舍，最后给出 CTR 边界的判定标准与 roadmap。

## Contents

- [请求时模型?](https://canmi.net/architecture/observation-to-lowering#request-time-model)
- [伪 SSR](https://canmi.net/architecture/observation-to-lowering#beyond-ssr)
- [Adopt](https://canmi.net/architecture/observation-to-lowering#adopt-without-replay)
- [同图?](https://canmi.net/architecture/observation-to-lowering#living-component-graph)
- [平衡点](https://canmi.net/architecture/observation-to-lowering#svelte-balance)
- [Black box](https://canmi.net/architecture/observation-to-lowering#react-black-box)
- [Visible AST](https://canmi.net/architecture/observation-to-lowering#visible-template-ast)
- [Lowering](https://canmi.net/architecture/observation-to-lowering#ast-lowering)
- [Invert](https://canmi.net/architecture/observation-to-lowering#known-by-default)
- [Slot & Branch](https://canmi.net/architecture/observation-to-lowering#slots-and-branches)
- [Escape](https://canmi.net/architecture/observation-to-lowering#explicit-escapes)
- [CTR](https://canmi.net/architecture/observation-to-lowering#ctr-boundary)
- [CSS](https://canmi.net/architecture/observation-to-lowering#static-css)
- [Ownership](https://canmi.net/architecture/observation-to-lowering#component-ownership)
- [Same protocol](https://canmi.net/architecture/observation-to-lowering#same-protocol)
- [Runes](https://canmi.net/architecture/observation-to-lowering#runes-semantics)
- [Data origin](https://canmi.net/architecture/observation-to-lowering#data-origin-and-purity)
- [What will Ship?](https://canmi.net/architecture/observation-to-lowering#what-seamjs-ships)

## Article

今天已经是我 move 到 US 来的整整一个月了；落地 NC 日常安顿好后，周围的地区和比较近的景点也基本上逛完了，那么人就闲下来了。但是恰恰好我就不是什么闲人，人一旦闲下来就会给自己找点事情干 ~~捡起博客不就算一件嘛~~，但是不巧 US 这边物流不是很方便，所以板子暂时画不了，那就只能回来写点软件水水了，顺便把几个月前丢掉的坑捡起来填上?

不过在开始吐槽别人之前，可能还是得先补一点 **seam & SeamJS** 相关的知识(x 这到底是个什么东西 ~~毕竟上一篇隔了好几个月，大概率你们已经忘干净了，Github 上的 README.md 窝又暂时懒得改~~

[Rendering as a Protocol](https://canmi.net/architecture/compile-time-rendering?lang=mw) — UI should be described, not executed.

那么简单说 Seam[^1] 首先是一套协议，而不是又一个 SSR runtime，意味着 build-time 编出一份带 `slot` 的 **HTML skeleton**，runtime-time / request-time 只往这些 `slot` 里塞值，`if` / `each` / `match` 这些也一样是写死在 skeleton 里的协议节点。所以请求进来的时候没有任何一个 UI 组件被执行过，服务端要做的只有"看图填空"这一件事，那么它自然也就不必是 Node 或者 Bun 了，**Rust** / **Go** 之类的后端填一份 HTML 不是也可以嘛，能有什么难的呢?! ~~但是其实后面也没有完全干掉 JS Runtime, 我的意思是可以不要但是前提是你能接受部分限制~~ 但是无论如何，总体性能提升绝对是指数级别的，因为达到大多数 **SSR** 效果不再要求 **runtime-time / request-time** 跑 **UI render** 了...

### 请求时模型?[^2]

大概从今年下半年开始，我把本站完全从 `TanStack Start` **React** 完全 `port` 为了 `SvelteKit` **Svelte** 全部迁移完之后，我发现它确实很好：`ssr` 的性能开销差不多降了一个数量级别，水合开销非常小，跑起来很舒服，框架编译的哲学也非常棒、非常轻量。但是 `Kit` 的部分总有一些让我觉得很不尽人意的地方。但是这其中大部分可以通过插件解决~~毕竟是 Vite 拼的~~ ，但是总是又一些部分是不能动的；但是这大概率也是我的问题，因为 seam 想要改的地方可能和任何一个存在的 `meta` 框架都有本质上冲突，~~本质上是我在挑战他们的 执行模型(每请求跑 render 函数)、数据边界(load 和 UI 糊在一起)、后端能力(绑死 JS runtime)~~.

### 伪 SSR[^3]

这里不用想肯定有人问：这不就是 `Astro` / `Qwik` / `Marko` 嘛，雀食 seam 在语言形状上 `Marko` 最近，默认少 JS 上 `Astro` 最近，不 `replay hydrate` 上 `Qwik` 最接近；但是呢 他们的 `request-time` 仍是在跑 UI 程序 `island` 各自 `render`、`Qwik` 那次 `SSR`、`Marko` 编译出来的 JS 模板函数，都还是每请求执行一块 `renderer`.

那 `{#each}` 一千条本质上我也还是在拼 `HTML`，所以我不是不生成文档[^4]，而是区别在于我拼的是已经自包含的协议，而不是重新生成 组件 `Tree`，`Seam` 要的是 `AST` → `IR` → 带 `slot` 的 `HTML`，请求时只做协议解释、加上动态数据区域的 `injection`，故不必再跑 **UI render** 的那部分函数了，所以任意后端都能填已经 `residual` 化那最后一层 也就解决了 ***vender lock in JS***[^5] 的问题。所以其实我和上述已有内容的大方向并不在一个线上(x)

### Adopt

Seam 会水合，但不是传统那种，也不是 Qwik 那种。传统水合是客户端再 `render` 一遍对拍 `DOM`；Qwik 是 `SSR` 出 `HTML` + 组件状态，客户端 `resume`，不 `replay`; 但是 Seam 的 **request-time** 就连 **UI renderer** 其实都不用跑：水合骨架、首屏、`hydrate` 数据来自同一次 `injection`. 客户端拿到的是已经对的 `DOM` + 同一份数据，按编译期 `IR` 把事件和 **client state** 挂上去 `adopt`，不是 `replay`，也自然不是 `resume` 一棵组件树。

正因如此「两边各 render 一次再对拍」这类 `mismatch`[^6] 已经从默认变成了非法状态。窝并不是宣称实现已经 100%，是这类偏差在模型里不合法：出现了就是我 `compiler` / `injector` 实现中的 `bug` 可以被 `fix`，典型的举例就是 (~~骨架没过 HTML parser、转义对不上、IR 和骨架不同步~~)。

当然 `skeleton` 自己得是 `parse-stable`[^7] 的，`table` 里乱塞 `comment` 那种算实现没处理好，别算到窝模型头上。**Build-time** 算的是水合映射（谁静态、谁是 `slot`、绑什么），不是把数据提前算死，但却固定死了这个 `slot` 的数据类型。数据值本身仍然是 **request-time** 可以任意换的；只不过 `HTML` 和 `payload` 必须是同一份。

***

### 同图?

`Astro` 虽然首屏也是文档，后面也能 `ClientRouter`，但差别是它的动态部分仍是 `islands` 各自 `render`，**ClientRouter** 本质更像 `document morph`，而不是水合后同一张还活着的组件图。所以 Astro 的技术栈上就不适合做这个，它本身没错，只是不是设计给你这样用的，你当然可以说有 **View Transitions API**[^8] 可以用，~~那这样我就要问你了 View Transitions API 可以当饭吃嘛，能让你跨页面套 Motion 动画[^9]嘛~~... 扯远了点；

反正水合前谁都动不了，首帧[^10] JS 起来之前的动画没有本来就不是任何人的错；`Seam` 也只是保证 `injection` 对、`document` 对、`client` 入口对，这里真正的约束在水合之后：`client` 导航是把数据打进还活着的那张图来做 ~~(layout 持久)~~，而不是再走一遍 `injection` 换 `document`. 总之 **Seam 水合后接管的是同一张静态组件图**。

这里其实还有 Marko 6 已经很接近我的想法了 (模板 **AST**、编译期细粒度、**resume** 不 **replay**)；但是和 `Marko` 比起来在技术上还能划开的线主要就两条：一个是 `request-time` 还在跑 JS 编译产物、整条链绑死 JS runtime；~~另一个就是作者用的语言是 Marko 不是 Svelte~~，我相信大部分人在聊前端的时候肯定 26年只会把 `React`, `Vue`, `Svelte`, `Solid` 考虑为比较主流的 **UI Stack** 吧，如果我不说很多人甚至可能连 `Marko` 是什么都不知道，那么建立在 `Svelte` 上我其实不算是「*复制一份原理再加个 backend*」，我倾向于解释为借用一个有人写、有人用、有人不断修 bug 不断打磨的那套 UI 出来用，都是大家熟悉的内容。

另外就是 `Qwik` 的「不水合」省的是客户端 replay，但是没有省掉 服务端的那次 SSR[^11]\(?) 所以很遗憾，而目前没有任何一个框架能满足我，或者给我足够的 API 让我有做加法的可能，这也是为什么几个月前我会去做 SeamJS 这个东东；那么今天其实也就是来重新规划一下它的 `roadmap`.

现在的我非常认同「***Svelte: for building, not frameworking.***[^12]」

#### React

先说 `React` 吧，抛开被 **Vercel** `Next.js` 商业绑定和 它有自己的 `roadmap` 不谈，首先它 **CVE** 满天飞，~~光这点就注定了你每时每刻都要盯着 **Social Media** 看有没有新的漏洞被 **report & release**~~. 其他家的 **Router** 基本处于不可用状态特别是 **Remix**[^13]，但 26年 有一个非常特别的选项，那就是 **TanStack** **Start**，我很早就用 **TanStack Router** 了，对此可以给出~~除了命名风格和数据结构做的不是很美观[^14]~~，但是从功能性上来说这是我唯一认可的选项。新是新了点，但是它解决了 **Next.js 16** 的 `next-server` 进程至今都没有解决的冷启动页面首次访问速度问题，**Next.js** 其实一直莫名其妙会有这个卡顿问题，不是服务器性能不够，实际上我会归因为要么是 启动 **Load** 没有加载完成，那么就是 `runtime` 还在编译某些内容。~~而且这个结构注定了不太适合 **Cloudflare Worker** 那种多地实例 最近启动的原则~~，只有 **Vercel** 那种传统 **Node 长进程**[^15]不会吃到这个问题(?) ~~有点扯远了，关于 Next.js 以后可以单独写一整片来吐槽~~，但是 **TanStack**[^16] 真的是我认为已经足够成熟到可以商业使用了，至少我有给某项目的销售站用上了。但这仅仅是唯二的选项(x)

#### Vue

Vue 的话我个人从 Vue 2 迁到 Vue 3 的时候，对语法等机制就不太认同了，我心中的 Vue 永远是 Vue2。不可否认的是 Vue 3 有很多很棒的 `feature`，但对比 Vue 2，它的核心对我来说感觉变了；而且变得很大。另外从商业角度上来考量 `Vue` 越来越被 meta 层彻底绑死、被生态控制？处境感觉和 `Svelte` 非常相似，都是开源，但是真的基本上很难有第三者站出来给你做第二个实现，这也是为什么 **TanStack Start** 加入的洗牌给 **Next.js** 的打击真的很大，必须表扬 [Tanner Linsley](https://x.com/tannerlinsley). 另外还有一点也是 `Vue` 的一个不可忽略的问题，早年的 `Vue` 确实是一种革新；但是任何 **UI Stack** 在生态起来后都会遇见生态问题，~~也就是为了兼容性而出现一定的妥协?~~ 特别是这个问题在 `React` 上真的非常明显，现在的 `Vue` 也不例外，开始出现这种内容[^17]的时候一般我就会考虑为 **Slop** 了，~~但是也不能说它不好~~，因为当 Nuxt 成为下一个 "企业级" 的框架后，首要的考量已经不再是技术正确不正确，优雅不优雅，而是某些向后兼容 + 稳定性(x

所以 [Evan You](https://github.com/yyx990803) 其实并没有做错什么，甚至乎可以说有人必须站出来当这个 ~~大坏人~~ 而他恰好就是最合适的人选，只是做了最合适的事而已...

#### Solid

**SolidJS** 框架很好、核心也足够稳定，~~但没人愿意给它写生态~~。`Solid` 其实就是早年的 `Rust`，但是 `Rust` 已经挺过来了，`Rust` 在编程领域确实也有类似生态少什么都要自己造轮子的处境，但 `Rust` 工具链能顺滑的让你把轮子造出来、让最小可用单元跑起来，造轮子的难度其实并不是很大？但是很不幸 **SolidJS** 暂时还没有挺过这个阶段，但是就遇到了 **AI 寒冬**；尤其是会在 26年后变得非常困难，Solid 2.0 核心已经很好了也很稳定，但是外围没滚起来，想法非常对，只是可惜生不逢时；就算有少数几个外围，那也只存在于某一两个零散的方面 ~~(比如 TanStack 就有 Solid 实验 Router)~~ 拼不起完整生态。这让它在当前以及未来都处于一个尴尬的位置：没有完整生态，在如今 Agent 爆炸的时代；雪球还没滚起来那么 Agent 就不会优先选择它，如果 AI 都不会优先偏好他意味着更少的机会得到贡献，尤其是当 [Linus Torvalds](https://github.com/torvalds) 都开始 Vibe 的年代，感觉 Solid 很遗憾，想法很好、技术很正确、实现很优雅，但是真的是很可惜它生不逢时...

「***或许曾经的我不会认为这是一个问题，什么都自己写也无妨，但是很遗憾现在的我不这么认为了***」

***

### 平衡点

那么在这条路上，恰恰踩在最完美平衡点上的其实只有 **Svelte** 了。它拥有比 **SolidJS** 更好的生态 ~~(更好的出生年代)~~，性能也没有差多少，甚至有时候受益于它的 **Compiler**，它很多时候甚至比 **SolidJS** 更快更优?!   当然这里肯定会有人说页面数量上升到一定量级或者用户浏览得越多的时候 Svelte 的资源开销[^18]对比 SolidJS 会越来越大？网络请求会越来越多？ Well, 理论上这是事实，~~但是我觉得得结合真实网页来看，99.9% 的个人网站或内容网站，访客基本上都不会停留太久，逗留很久甚至可以考虑为是非常**罕见**的情况~~。至少在我这个站上，目前来说深度连点极少，所以「翻很多页 = Svelte 更亏」对我不构成问题。但这不是选 Svelte 的理由；主要核心还是 **markup AST**.

另外就是 Dashboard 在这种会把页面点开的东西，这条统计帮不上忙，但是毕竟 Dashboard 的用户对比真的内容受众用户还是少的嘛不是。所以「浏览极多页面导致开销变大」我认为本质上是个**伪命题**；至少现在是，~~也许以后我会有不一样的看法~~，但是在现在这个快节奏的信息时代，我很难不承认这类用户极少；即便有人因为感兴趣而深度探索并转化为长期粉丝，这种探索大概率也只发生在第一次访问，后续他们也只是来看看新发的文章而已；可以说从统计学回报来看，这种极端情况并不成正比。因此我更愿意相信：**大部分场景下 Svelte 能带来更好的综合性能**。

> [quadrant: UI stack fit]
> Frameworks are grouped by ecosystem reach and compile-time leverage without implying precise scores.
> - Compiler / Niche: Solid
> - Compiler / Niche: Marko
> - Compiler / Mainstream: Svelte
> - Runtime / Mainstream: React
> - Runtime / Mainstream: Vue

***

**Svelte** 对上述 3个框架除了处境和位置比较合适之外，其实还很贴近写裸 `HTML` 的感觉；甚至早年 Svelte 3 刚普及、`.svelte` 扩展名刚定下来的时候；官方的 VSC 插件出来之前都是推荐这么做的，以至于现在你还能在 [Svelte 的插件页面](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) 找到这句话:

> If you added `"files.associations": {"*.svelte": "html" }` to your VSCode settings, ***remove it***.

另外就是 **Svelte** 还 Offer 了很多好处给我，首先就是不把 UI 推向 `JSX` + 一堆 `JS function`[^19]，以及 `<style>` 是编译期产物、而不是 `runtime` 前者让结构留在 `markup AST` 里；后者让 CSS 能直接进入 IR 而 Tailwind 刚好两边都能贴（

***

### Black box

就像上一篇写过的，`Seam` 首先是一套协议，不是又一个 **SSR runtime**. **Build-time** 产出带 `slot` 的 `HTML skeleton`，**request-time** 只做 `injection`；`if` / `each` / `match` 也是协议节点[^20]

只可惜之前我 UI 选了 React 开始动刀，很不幸的是在 React 上面想实现捕捉页面结构并产生变体的话非常困难，改编译器的话工作量不亚于重写一个...

```mermaid
flowchart LR
	accTitle: From black box to visible structure
	accDescr: React requires render and diff guesses before producing protocol nodes, while Svelte exposes a markup AST for direct lowering
	react["React black box"] --> guess["Render + diff"]
	guess --> structure["Structure guess"]
	guess -.-> escape["Mocks / escape"]
	structure --> protocol["Protocol nodes"]
	svelte["Visible Svelte AST"] --> lowering["Direct lowering"]
	lowering --> protocol
```

不改的话还可以把 React 编译器当成一个黑盒，然后真的执行一堆 `render` + `diff` 猜结构出来用，`nullable` / `enum` 这种有限决策还能穷举，一旦碰到 `price > 10` 这种谓词，类型值空间根本乘不完。~~V1 里遇到这种情况只会让你手动给 mock~~，比如 `price < 10` / `= 10` / `> 10` 切三刀;

但是问题也就出在这里(?) 要实现这一精确的切 3刀就会要求你知道切点在 10，但 React 编译器对我来说是不透明的，我们并不知道条件是什么，于是 TypeSafe[^21] 的代价变成用户在做编译器的活。

理论上这个不该丢给用户，不然结果就是 `escape` 满天飞[^22]，CTR[^23] 覆盖的还是那点 `nullable` / `enum`，结构发现等于没做，于是不知道还有多大的意义了。

但是这次也不是说要改协议本身，我原来说的 `skeleton`、`slot`、`injection` 我觉得其实可以保留，后端也还是可以不跑 UI 代码，只是要改的是协议前面那一层，关于回答页面怎么被编成这些节点的问题。
那么这里就不得不感谢来自 `Svelte` 给我最大的礼物—— `markup`

因为 **markup** 的结构根本不用我来猜！！！

***

### Visible AST

只凭「markup 是真的 markup」这一点，页面结构就绝对有机会成为编译器能读的东西，而不是一堆 runtime JS 执行出来的组件树。咱就是说好好的 UI 为什么一定要能任意执行绘制呢？我并不是否认这个观点， 但是我想说的是 至少 97%+ 的时间 任意执行绘制是用不上的，具体可以[**参考这里**](https://canmi.net/architecture/compile-time-rendering?lang=mw#ctr-x-ssr)

举个反例，React 里面的

```typescript
function Card({ user }) {
  return user
    ? <div className="card">
        <Avatar user={user} />
        <span>{user.name}</span>
      </div>
    : null
}
```

**本质上还是执行一个 JavaScript function**，看看它返回什么，哪怕编译器非常聪明，它面对的基本模型仍然是：`JS execution` → `JSX expression` → `element tree`  而 Svelte 里面就很不一样

```svelte
{#if user}
  <div class="card">
    <Avatar {user} />
    <span>{user.name}</span>
  </div>
{/if}
```

它给编译器的直接就是

```text
Component
├── IfBlock
│   └── Element div
│       ├── Component Avatar
│       └── Element span
│           └── Expression user.name
```

这对普通 **Kit** 应用来说已经很好，但是 **Seam 这种想把 `server rendering` 拆解成 `compile-time skeleton` + `CTR` + `SSR fallback` 的东西**来说，意义就真的大了不止一点。

因为这棵树同时给我了两层信息：首先就是 **值动态** → `slot` 但是这个 V1 里早就可以实现了，更重要的其实是后者给我了 **结构动态** → **协议控制流节点**，与其去猜 React 编译器的黑盒，Svelte 的编译器就是暴露出来了正规前端给我读呢，比如 `{user.name}` 是前者 `{#if user}` 才是后者。

```mermaid
flowchart LR
	accTitle: Two layers of dynamic structure
	accDescr: A visible Svelte AST lowers value expressions into runtime slots and structural blocks into protocol control flow over branch space
	ast["Visible Svelte AST"] -- "{user.name}" --> value["Value dynamic"]
	value --> slot["Slot"]
	slot --> injection["Runtime injection"]
	ast -- "{#if user}" --> structure["Structure dynamic"]
	structure --> control["Control-flow node"]
	control --> branch["Branch space"]
```

另外就是旧协议里的 `if` / `each` / `match` 仍然有效，但是关于之前[旧文](https://canmi.net/architecture/compile-time-rendering?lang=mw#maybe-ctr)里那套笛卡尔积我觉得要限定一下，成立的条件其实是分支空间，而不是值空间；`nullable` / `enum` / `bool` 这种有限决策，组合是有限的，`compile-time` 付费穷举在数学上成立；但 `JTD` 的 `string` / `number` / `timestamp` 本身不可枚举，`price > 10`、`inventory < 5`、`items.length === 0` 这类谓词切出来的支，单靠类型值笛卡尔发现不了；`sentinel` 填一个数进去永远走同一侧。

如果真要把它们收成有限决策，本来就得先 `derive` 成 `bool` / `enum`，那步不是 JTD 白送的(x) 所以 V1 在 `nullable` / `enum` 上 `sound`，对任意字段值空间不是；真的工程问题更是那些个块是 `diff` 猜出来的，`IR` 解释不了自己。

### Lowering

但是在 Svelte 时代这些节点是可以从 AST 直接生成的，这样做的原因并不是为了换栈省那几 ms，更多的是为了让控制流从观测[^24]变成 `lowering`——结构发现交给 **AST**，有限决策用协议节点，不必再乘出 `N` 份 `HTML`；类型去约束 `payload`，不去发现树。那么最后在 **Svelte** 里 values **在这个** **Layer** 上看起来就是下面这样子的 ↓

```text
STATIC
<div class="card">

DYNAMIC
{user.name}

STATIC
</div>
```

这类 DYNAMIC 很多时候也不需要 React 那种级别的「真 · rendering[^25]」，保持 slot 就够。

更细一点，Svelte 还可以把单个 element 拆成这样

```text
structure: static
attributes: static
text node #0: dynamic
```

这才是 `Seam` 最需要也最喜欢的东西。`React` 里想知道 `attributes` 是不是 `static`，基本上只能暴力穷举：所有可能输入跑一遍，`attribute` 没变就是 `static`，变了就给它一个 `slot`.

结构分支更惨，连「这里有没有这块 DOM」都得靠渲染两次再 `diff`, 而 `Svelte` 的 `IfBlock` 把后一件事从**可观测**变成了**可生成**，这才是质变。

~~*但是其实这样说也不完全对，或者太过于简单了*~~，更详细一点来说的话，单个 `element` 还可以继续拆；但 `attributes` 不一定是一上来就 `static`，具体还得看你怎么写的

```text
class="card"             → attributes: static
class:active={on}        → name static, value slot
{...rest}                → opaque, escape hatch
text node {user.name}    → slot
```

比如这里第一行进 skeleton 没有争议，但是第二行才是 Svelte 真正好用的地方：`class:` / `style:` 把 `attr` 名字留在 **AST** 里，值单独变 `slot`，不必把整个 `element` 打成动态。第三行和 **React** 的 `{...props}` 没有本质差别，Seam 就当明确 `hatch`，不用再假装分析它了，**React** 里字面量 `className="card"` 其实也不用穷举；要穷举的是「换一组 props 之后这个 attr 会不会变」。Svelte 恰好少掉的就是这最后一种猜测——只要名还在树上，那就不用渲染两次才知道了 🫠

反正就是 React 会逼 Seam 把很多东西重新当成 *runtime problem*, 比如 ↓

```typescript
const Wrapper = cond ? A : B;

return Wrapper({
  children: foo.map(renderItem)
});
```

亦或者

```typescript
return foo && bar
  ? getLayout()(data)
  : something();
```

当然现代 React Compiler 能分析其中不少东西，但是魔改它的话，意味着最终要一直维护它，这种花费可以说是超级多的，不论是精力还是时间... 如果改 Svelte 的话，这边我就不打算 fork compiler 了,理论上 `svelte/compiler` 已经把 **AST** 露出来了。

Seam 要做的是读这棵树、然后 gen 自己的 IR；官方那套 DOM / SSR codegen 其实都可以留着当 fallback，理论上只需要维护的是 IR lowering，不是整个 Svelte，所以大方向一致，冲突会少——但一致的是「*结构在编译期可见*」，不是**执行**模型；正因如此 **Svelte** / **Kit** 的 **SSR** 仍然是每个请求跑一遍生成的 `render` 函数；但是我更希望在我的 **Seam** 里，`request-time` 不跑这个函数，只解释协议（`slot` + `if` / `each` / `match`）

在 Svelte 的选择上其实还有另外一点考量就是 Next.js 和 React 其实已经完全[^26]被 Vercel 所控制为 Market 框架的前提下，如果 Seam **承诺兼容 React semantics** 那么我的 architecture 最终就必须允许 `arbitrary JavaScript` → `determine tree structure`.

于是 Seam 的 IR 就必须非常保守(x)   最后大概率就变成了能静态分析就优化，不能分析就 SSR，这样虽然也是混合了 CTR + SSR 的页面，但是问题就出在 “不能分析” 的这个地方上，问题是 React 世界里有太多 “不能确定”的地方了...

如果不改 React 编译器，那么就只能继续留在和 V1 一样的处境上，把 Compiler 当作黑盒，瞎猜它的产物，那样可以说非常脆弱，并且不优雅，还很容易 Edge Case 遍地飞。

### Invert

所以如果我现在说 Seam 的 component language 就是 Svelte，那么 compiler 甚至都可以反过来做，首先默认 `structure known` + 局部 `expression dynamic` + 明确 `escape hatch` 再进 `runtime` 实在无法处理最后再 SSR fallback.

```svelte
<script>
  let { product } = $props();
</script>

<article class="product">
  <h1>{product.name}</h1>
  <div class="price">${product.price}</div>
  <button>Buy</button>
</article>
```

Seam 完全可以把它理解成类似下面的样子

```html
<article class="product">
  <h1><!-- slot 0 --></h1>
  <div class="price">$<!-- slot 1 --></div>
  <button>Buy</button>
</article>
```

那么理论上 `slot0` = `product.name`, `slot1` = `product.price`. 这就是之前我就在做的 residualization, 核心思想就是整页不用进 SSR，只算真正 dynamic 的 residue.

***

### Slot & Branch

但是从 React switch 到 Svelte 真正有收益的其实是下一层；~~也就是结构变化的时候也不必再靠渲染两次去猜~~，因为结构在编译期就已经确定好了，直接生成对应的 IR 即可

```svelte
<article class="product">
  <h1>{product.name}</h1>
  <div class="price">${product.price}</div>
  {#if product.available}
    <button>Buy</button>
  {:else}
    <p>Sold out</p>
  {/if}
</article>
```

```html
<article class="product">
  <h1><!-- slot 0 --></h1>
  <div class="price">$<!-- slot 1 --></div>
  <!--seam:if:product.available-->
    <button>Buy</button>
  <!--seam:else-->
    <p>Sold out</p>
  <!--seam:endif-->
</article>
```

值还是 `slot`，分支则是旧协议里的 `if` block. 但是在 **React** 版要多次 `diff` 才拿得到这块；可是在 **Svelte** 里这里是可以直接从 `IfBlock` 直接推导生成的。

***

### Escape

这里当然不是说 JSX 做不到。只是真的写起来的话没几个人会把结构留在分支树上容易看得见的地方，基本上都是写成这下面这样子(x)

```typescript
const price = formatPrice(product);
const body = product.available
  ? getAvailableView(product)
  : getSoldOutView(product);

return <Layout>{body}</Layout>;
```

而且还有一点很重要，就算你能写成这样，只要 `React` 的**大部队**不写成这样，训练的 **LLM** 不写成这样，**那么迁移成本，后期开发成本绝对是指数级增长的**。

但是 `Svelte` 这里就又不一样了

`UI structure` ≈ `template AST`，`JavaScript` ≈ `values` + `behavior` 在这已经是日常了

甚至也是 `Svelte` 官方最鼓励的做法。这个区别对于 **Seam** 抽 **IR** 是结构性的。但是也请不要理解为 **Svelte** 没有动态结构，实际上它也有对应的逃逸口，而且是主要语法之一

```svelte
{@render children()}
<svelte:component this={layout} />
<svelte:element this={tag}>
{@html html}
```

`Wrapper = cond ? A : B` 在这里并没有凭空消失，而是从「任意 JS 表达式」收成了这几个节点，对于 Seam 来说就很简单了，只需要遵守默认走 **AST lowering**；静态看得见 hole, 那里面就是谁的；最常见的就是 `{@render}` 当 `compose`（`layout`、`Card` 包 `children` 都是这种类型），`<svelte:component>` / `<svelte:element>` / 动态 `{@render}` 才是明确的 `hatch`，会进 `runtime` 或 `SSR fallback`；另外 `{@html}` 的话可以直接沿用我直接设计的 `raw HTML slot`.

所以实际上优势并不是「**结构永远已知**」，是「**未知结构有参考列表**」(x

React 里未知是整个 JS；Svelte 里未知是上面这几个 Escape 的开口，仅此而已。用上后 CTR 需要分析的内容范围绝对会明显减少

### CTR

可以 Svelte 的不意味着一定可以 CTR，但是我这里有一个参考标准 ↓

```text
literal markup / literal attr / <style>
  → skeleton

{data.path} / class:foo={b} / {#if path} / {#each list}
  → CTR (slot or protocol if/each)
    {#if expr}: path → protocol if
    price > 10 → derive a finite payload field, then if
    protocol never evals raw Svelte expressions

pure expr over server data
  → CTR derive (generated JS; else QuickJS(rs) / Node(ts))

<Foo {x} /> static ref
  → compose Foo IR (inline if CTR; slots wire up)

{@render} statically visible callee (snippet / page / layout)
  → same compose, not hatch
    (Svelte 5 layout / Card-with-children)

$state / $effect / event
  → client

<svelte:component> / <svelte:element> / dynamic {@render}
{...rest} / {#await} / opaque JS
  → SSR fallback

{@html}
  → raw HTML slot (v1 protocol)
```

```mermaid
flowchart LR
	accTitle: CTR boundary
	accDescr: Visible Svelte structure becomes a skeleton, protocol slot, or client runtime path
	A["Svelte AST"] --> B{"Structure visible?"}
	B -- "No" --> F["Escape"]
	B -- "Yes" --> C{"Value origin?"}
	C -- "Build" --> D["Skeleton"]
	C -- "Request" --> E["Slot / protocol"]
	C -- "Client" --> G["Client runtime"]
```

这样静态引用才 `compose`； 仍走下面的 `hatch`, 可以得出 CTR 的单位是静态组件图，而不是单个文件。大部分都是默认 `structure known` 少部分明确 `escape hatch` 平时几乎遇不到 `SSR fallback` 其他不在表上的，一律当 `opaque` 处理好了根本不要猜嘛。`CTR` 只是这个子集上的 `execution model`，就算我切到 Svelte 也并不打算担保整个 Svelte 的范围[^27]呢(x)

### CSS

另外一个巨大收益其实是 CSS，在 Svelte 中可以写做为

```svelte
<div class="card">...</div>
<style>
.card { border-radius: 8px; }
</style>
```

这里 Seam 编译一个 component 的时候，同时已经拥有 `markup dependency` + `script dependency` + `style dependency`, 而且 style 是 **静态 artifact**. 那么就可以非常激进地判断出

```mermaid
flowchart LR
	accTitle: Route CSS delivery
	accDescr: A route discovers reachable components and CSS before choosing bundle, inline, or preload delivery
	route["Route"] --> components["Reachable components"]
	components --> css["Reachable CSS"]
	css --> bundle["Bundle"]
	css --> inline["Inline"]
	css --> preload["Preload"]
```

***

甚至乎更近一步把流程做成这样子 ↓

```mermaid
flowchart LR
	accTitle: Critical CSS delivery
	accDescr: Above-fold components produce critical CSS while lazy islands defer their CSS
	above["Above-fold components"] --> critical["Critical CSS"]
	critical --> islands["Lazy islands"]
	islands --> deferred["Deferred CSS"]
```

以上流程是完全不需要 JS runtime 参与的

#### No CSS-in-JS

而 CSS-in-JS 我也不接，那倒不是 React 逼着我要兼容下面这样的表达

```typescript
const Button = styled.button`
  color: ${p => p.primary ? "red" : "black"};
`;
```

OR

```typescript
<div css={theme => ({
  color: theme.colors.primary
})}>
```

只是上述结构其实在 V1 中就已经可以使用 Tailwind 绕开了， CSS-in-JS 不接的主要原因其实是一旦接上了 CSS 会变回 JS execution result，然后 style runtime、registry、SSR collection、hashing、hydration consistency、theme context、insertion、ordering、dedup 全都得请回来，普通框架可以吞下；但是 Seam 她不一样啊！我要消灭的就是这类 server / runtime work，应该是完全没必要为了生态礼貌把刚赶走的东西请回来吧（~~完全没有道理呢~~

再说 CSS-in-JS 出现的时代解决的问题是没有 Tailwind CSS & Motion 干的事情，如果现在 TailwindCSS & Motion 已经足够解决甚至更好更优雅的解决了，那么何必要开倒车呢，就像我前面所说的 「任何东西在生态起来后都会出现妥协[^28]，~~很少的出现 breaking 重构来革新~~」那么既然 Seam 还没有起来生态，那么为何不**激进到底**呢？

但是这里用 "激进" 我不知道对不对，可能对的一方面是对于传统框架来说，我干丢掉 Server Side 那种重 JS Runtime (Node OR Bun), 不对也可能是比方说 CSS-in-JS 如果彻底丢掉的话，其实也没啥问题，~~我更愿意考虑它为时代的眼泪了~~。曾经最知名的 CSS-in-JS 的 lib 们 [Stitches](https://github.com/stitchesjs/stitches/discussions/1149) 2023 年 6 月官方宣布 no longer actively maintained, [styled-components](https://styled-components.com/blog/celebrating-a-decade-of-styled-components) 2025 年 3 月也进入了休眠状态，[Emotion](https://github.com/emotion-js/emotion/discussions/2827) 基本上也是这样子的。

***

那么又不得不表扬一下 Svelte 的 `<style>` 了，基本上 `CSS` → `compile-time artifact` 在 Svelte 里面已经是毫无争议的了 ~~(至少我认为是这样，如果你不认同那就是你对)~~

只要不是 `JS` → `execute` → `generate CSS` → `collect` → `serialize` → `hydrate`, 那么差距就会很大，更重要的是也许 CSS dependency 可以直接进入 Seam IR 🤔

例如 component compilation:

```text
ComponentIR {
    skeleton,
    dynamic_slots,
    css,
    client_behavior,
    server_dependencies
}
```

注意到这不是另起一套渲染模型，在 V1 里 `skeleton` + `dynamic_slots` 编完就是旧协议那份 `HTML` 了，只不过 `skeleton` + `dynamic_slots` 怎么来的对我更加透明了。

我们假设像下面这样处理 ↓

```svelte
<script>
  let { name } = $props();
</script>
<div class="user">Hello {name}</div>
<style>
.user { font-weight: 500; }
</style>
```

那么最终肯定会达到

```text
ComponentIR
  HTML:   <div class="user svelte-x">Hello SLOT(0)</div>
  CTR:    slot(0) = props.name
  CSS:    .user.svelte-x { ... }
  Client: none
  SSR:    none
```

但是注意以上你看到的仅仅是叶子，而且是比较理想的切片；没有子组件、没有结构分支、没有 client. 页面级 `skeleton` 是静态组件图 compose 出来的；子树一旦有 `$state` 或 `hatch`，IR 里的 Client / SSR 就不会是 `none`.

但这已经非常接近我想让 Seam 达到的理想状态，也就是一个 Svelte component 不再对应“一个需要执行的 renderer”, 而是变成一个 Svelte 5 Compiler 能拆开的资源；实现了这个对我来说就已经是非常大的 conceptual improvement.

### Ownership

另外就是 “不推 global state” 对 Seam 的意义也比 DX 更深，我相信你肯定在 Next.js 项目里面或者任何 React 全栈框架里面已经见过了下面这一坨玩意 ↓

```typescript
<ThemeProvider>
  <AuthProvider>
    <QueryClientProvider>
      <I18nProvider>
        <RouterProvider>
          <App />
        </RouterProvider>
      </I18nProvider>
    </QueryClientProvider>
  </AuthProvider>
</ThemeProvider>
```

当然这个不是没有解决方案，这种"金字塔"式的 Provider 嵌套其实还可以写成 Compose Providers. 大概就是变成下面那个样子。

具体就是把原来一堆嵌套的 Provider 丢数组，然后定义一个组合函数，最后再吃这一层即可。~~但是这个对我来说其实就是一种自欺欺人的行为~~，本质上只是"藏起来"了, 又不是真的消除了。也就是从视觉上的嵌套变成了逻辑上的嵌套而已，某种意义上的 "眼不见为净" 纯纯的自我安慰

```typescript
const AppProviders = composeProviders([
  ThemeProvider,
  AuthProvider,
  QueryClientProvider,
  I18nProvider,
  RouterProvider,
]);

function composeProviders(providers: React.FC<{ children: React.ReactNode }>[]) {
  return ({ children }: { children: React.ReactNode }) =>
    providers.reduceRight(
      (acc, Provider) => <Provider>{acc}</Provider>,
      children
    );
}

function Root() {
  return (
    <AppProviders>
      <App />
    </AppProviders>
  );
}
```

React 项目最后真的非常容易或者说 100% 变成这样，那么就会产生一个 Seam 很讨厌的问题，也就是 「component 的输入到底是什么?」 表面上可以说是 `<ProductCard product={product}/>` 好像只依赖 `product` 本身？可是实际上呢

```text
depends on:
product
theme
locale
queryClient
router
auth
featureFlags
...
```

于是 component dependency graph 又将会变成隐式的，对于 traditional SSR 没太大问题，大不了就是「把整棵 React tree 跑一遍」嘛，但是 Seam 最后想问的还是 ProductCard 能不能独立 [CTR](https://canmi.net/architecture/compile-time-rendering?lang=mw#maybe-ctr) 那么这个时候就会真的非常麻烦。甚至可以说要改掉整个 React 编译器的行为 和 用户的 DX & 习惯，那么何必呢...

用 Next.js 的人基本上已经不在乎性能[^29]了，我可以很负责的说这句话；他们更多的是再为 Vercel 配套设施买单，如果你用 Next.js 不 hosted on Vercel 那真是一点好处都不吃 😡

#### Local ownership

反观 Svelte，它会是非常容易形成局部 ownership 的；

```svelte
<script>
  let { product } = $props();

  let quantity = $state(1);
</script>
```

从 Svelte / Seam Compiler 的角度来看这非常舒服

毕竟 「**input**: `product` + **local client state**: `quantity`」Ownership 真的非常清楚了

```text
product.name     → server CTR
quantity         → client
button onclick   → client
rest             → static
```

但是这只是默认，其实也不是保证; 因为 `getContext('theme')` 和 `.svelte.ts` 里的 `rune module` 仍是隐式输入，另外就是 `QueryClient` 也不会凭空消失。我觉得 Seam 的态度会和前面的 hatch 名单一样：看见就标进 IR（`server` 依赖或 `client` 所有权），分析不了就当 `opaque`，还是不要假装「Svelte 没有 Provider 这回事」比较好呢。~~但是也不一定，也许我稍微在研究一下可能是可以找到 Provider 的处理方案的~~

React 时代里 Seam 最大的问题是 IR 走了 `Component` → `execute renderer` → `get output`, 那么我最后所谓的 `seam compiler` 实际上变成了某种意义上的提前 `SSR` 或者完全可以说做是

「***universal SSR orchestration layer***」

***

### Same protocol

所以从现在开始可以说 **UI 就是 Svelte** 但 `seam` 协议本身还是协议，还是 `slot`、`injection` 为主，~~*JTD 到底合不合适还有待我进一步考虑*~~；从执行决策上来看现在就是已经是完全分层来的，已经 `residual` 化的槽，`Rust` / `Go` / `TS` server 只需要瞎填就行完全不需要理解组件代码，真正的零 JS；与此同时如果组件里还有纯派生，那么可以考虑加入一个 `generated JS` / `QuickJS runtime`，*(注意这里是在跑数据，不是在跑 UI)*

只有 `hatch` 的才会 `SSR fallback`, 那么后端在不需要真 SSR 的情况下是可以不跑 UI 代码的，自然也就不存在绑死在 `Node` OR `Bun` 上，但是呢 (Go Server 计划停止维护，~~因为个人尽力不足 如果有好心人有时间的话可以捡起来长期维护，并不是架构不支持后端无关性了~~)

换句话说，我只砍掉了 UI 我谁都接这种费力不太好的事情(?) 不变的部分还是可以作为「Rendering Protocol」后端不限 JS/TS

### Runes

还有一个很重要的点就是 Svelte 5 的 runes 对 Seam 非常友好，虽然 Svelte 5 开始变得更 JS 一点

```javascript
let count = $state(0);
let doubled = $derived(count * 2);
```

但此 JS 非彼 JS[^30]，和 `useState` / `useMemo` / `useContext` / `useEffect` 的差别是：**runes** 仍是 `compiler-recognized semantics` 所以 Seam 能知道 `$state` 是 **client** 所有，`$derived` 是派生，`$effect` 是 **client runtime**; 这和 `const foo = someLibraryHook()` 不在一个量级。

***

### Data origin

这里有一个很重要的点是「认得出来」其实不等于「能 CTR」，尤其不要看到 `$derived` 就默认它进 `skeleton`，其实上面这个 `count * 2` 就是反例之一，因为依赖是 `$state`，所以这是 client 派生产物。可以看看能 CTR 的 `$derived` 其实长这样：

```javascript
let { product } = $props();
let price = $derived(formatPrice(product.price));
```

`product` 来自 `$props`，`formatPrice` 还得纯并且可见，接到前面那张表上就是这样子的

```text
$state / $effect / onclick  → client

$derived
  deps ⊆ server data, pure, visible → CTR derive
  deps include $state               → client
  impure or opaque callee           → QuickJS or SSR fallback

markup → follow the table above; runes are not a free CTR pass
```

***

这样画出来看可能更好理解(?)

> [diagram: 一个管道从“Svelte source”开始，流入“Svelte / Seam AST”（“visible + typed”），随后分成三条并行路径。“Structure”（“markup AST”）指向“Static HTML”，而“Values”（“data + derives”）指向“CTR slots”（“lowered data”），并且“Behavior”（“state + effects”）指向“Client JS”。 — https://canmi.net/architecture/observation-to-lowering]

反正就是 Runes 友好体现为所有权标在语法上，不是因为 `$derived` 自带 **CTR** 资格，判断的唯一标准其实还是 下面路径展示的数据从哪来、纯不纯、看不看得见(x)

> [diagram: 一个分支管道从“CTR candidate”和“value / derive”开始，流入“Lowering decision”，该阶段考虑“origin · purity · visibility”，并强调“never syntax alone”。随后，上分支对比一个“simple”案例，它是“pure + visible”，导致“generated JS”，而下分支是一个“complex”案例，它是“opaque / hatch”，导致“QuickJS / SSR”“fallback”。 — https://canmi.net/architecture/observation-to-lowering]

然后旁边再加上一条 `<style>` → `static CSS dependency graph`, 这样整个世界观会非常一致，HTML 就是 structure, CSS 是 style artifact, JS 一定是 computation / behavior。

而不是现代 React 很容易走到的 **All in JS**[^31].  或许对于 React 自己而言，这未必是坏事 但是很遗憾
***Everything is JavaScript** 恰好是最不利于我做 **aggressive** compile-time decomposition 的世界*

***

### What will Ship?[^32]

说了这么多那么最后真正会落地的内容有哪些呢(x) 首先毫无疑问的肯定是 UI Stack 换 Svelte, 并且可以期望做完后大概率 **Compiler** 可观察性明显提高，主要原因还是 Svelte **强迫**+鼓励 `structure` 留在 `markup AST` 里，而不是埋进 **arbitrary JS control flow** 这样会直接提高 `skeleton` / `CTR` 的可分析空间以及 `CSS` 从 `runtime concern` 重新变成 `build artifact`.

这意味着 Seam 可以有机会做 **component-level CSS dependency**、**critical CSS**、**tree shaking**、**lazy CSS**，而不用额外维护 `server` / `client` 的 `style runtime`.

还有就是 **Component ownership** 会彻底变得干净；少一点 `Provider` / `Context` / `global runtime dependency` 意味着我会有机会彻底回答 哪个值来自 **Server**，哪个地方可以 **CTR** 传递，谁要 `client boundary`（~~哪个状态属于 client~~）还有这个 `component` 是否真的需要 `hydrate`...

***

在这三者里面前两者是可以直接决定 CTR 到底只是一个 optimization，还是能真正成为 SeamJS 的主要 **execution model**[^33]，这也就是为什么对我来说抛弃 React 不一定真的是技术上的一种妥协，反过来也可以是结构上的一次 breaking.   总之窝还是那句话

> 任何东西在生态起来后都会出现妥协，~~很少的出现 breaking 重构来革新~~

那么既然我还没有真正起来，我当然有的是机会可以 breaking 而暂时不需要考虑迁移成本，我也真的希望有一天可以在无数 breaking 后找到真正属于我的位置。

***哪怕它最后只有我一个人用，这也是属于我的一次* 「Experiment 🧪」**

[^1]: 是一种渲染协议，SeamJS 是使用该协议的 meta 框架
[^2]: 此处指的是执行模型，不是数据模型
[^3]: 这里特指请求时不执行传统的 UI render 但是使用其他方式达到了 SSR 的效果
[^4]: 此处指的是 html doc 术语
[^5]: 是指逻辑上没有强依赖 JS runtime 但是没有其他语言实现的问题
[^6]: 是指在 SSR 应用中浏览器客户端首次加载时生成的 vDOM 或结构与服务器端传来的静态 HTML 内容不一致所导致的错误
[^7]: 意思是“解析稳定”或“可复现解析”
[^8]: 这是一种由现代浏览器提供的 API 用浏览器内置预设实现代替完全可自定义的 JS 动画
[^9]: 是 Framer Motion 提供的 JavaScript 动画库，常用于 React 但是也提供了 Vanilla JS
[^10]: 指的是用户打开网页后，浏览器在屏幕上绘制出的第一帧可用画面内容
[^11]: 指的是 Qwik 服务器生成带有序列化状态和事件引用的完全交互式 HTML，从而实现“可恢复性”而无需执行水合
[^12]: 该理念出自 Rich Harris 2016 《Framework without the frameworking》
[^13]: 个人主观看法，因人而异，事实上被官方和社区考虑为可用，但是我不同意
[^14]: 纯个人主观偏好，并非框架设计缺陷
[^15]: Vercel 并不是传统进程，它会休眠等到实际运行时会被唤醒 但是长进程的思想成立
[^16]: 这里特指目前的 TanStack Start 而不包含其他正在 RC Beta 的库，就比如早期 Router 连稳定都算不上谈何生产
[^17]: 这里可以举例为 Options API + Component Instance Proxy 兼容层，我认为这是早期 Vue 最漂亮的设计之一 至少在 2016 年这让框架看起来不像框架。但到了 TS + Composition API 时代成本明显，新项目不会再用了但又因为是早期成功设计无法真的彻底丢掉
[^18]: 是指 Svelte 哲学下的把框架打散进组件，但是不可跨组件复用框架资源而 Soild 等框架哲学下的框架 runtime，载入一次运行时后组件只需小成本的开销对比
[^19]: 只的是 React 那种不管跑什么业务逻辑都要渲染组件树的行为，典型思维代表就是：「反正业务数据获取和组件都是 JS 产物，每次请求跑一下不久好了嘛」React 19 里面虽然有 PPR 但是本质上还是这种思路
[^20]: 这里的“节点”指协议中描述页面结构和控制关系的结构单元，而不是网络节点 和 VPN 之类的东西八杆子打不着
[^21]: 这里指的是结构上的类型安全，和 TypeScript 里面的 typecheck 保证的函数代码 TypeSafe 没关系
[^22]: 因为用户很懒，编译器不能机械保证基本上就是脱裤子放屁
[^23]: (Compile-Time Rendering) 是早期 seam 首次发明的一种完全独立的渲染方式
[^24]: 是只 React 版中的黑盒问题，无法真正的切入编译器中的某个步骤，所以只能当成“黑盒”来观测
[^25]: 特指执行了完整的 UI render 的操作，即使修改的状态只有 data 而不是 UI 组件也会完整渲染
[^26]: 此“完全”并非真的绝对完全，但是从结果上来说大差不差
[^27]: 无人可以担保，任何语言都有奇葩但是合理的写法，我只能提供一个合理业务下的优解
[^28]: 不一定都是坏的，至少不一定是所有方面上。可能某些设计上认为的妥协可以是工程上的好处？
[^29]: 用 Next.js 有在乎性能的人，但是他们在乎的是业务逻辑性能而不是 farmwork level 的
[^30]: 这里的“更 JS”只是语法形态更接近 JavaScript，并不意味着它退化成了编译器无法理解的普通函数调用
[^31]: 所谓 All in JS，不是“都写在 JS 文件里”，而是越来越多东西都退化成了 arbitrary JavaScript computation，原本清晰的结构边界也随之消失
[^32]: 一词出自 Vercel, 这里只真正会落地的内容有哪些
[^33]: 指的是 Seam 在运行时组织并完成页面生成、数据注入和交互接管的基本执行方式

## Elsewhere on this site

- Homepage: https://canmi.net/homepage.md
- Index for LLMs: https://canmi.net/llms.txt
- Full text for LLMs: https://canmi.net/llms-full.txt
- Sitemap: https://canmi.net/sitemap.xml
- Atom feed: https://canmi.net/atom.xml

## Structured data

> The JSON-LD graph the page for humans carries.

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://canmi.net/#website",
      "name": "サボり記",
      "alternateName": "Canmi",
      "description": "Notes on the software and hardware I build.",
      "url": "https://canmi.net",
      "author": {
        "@type": "Person",
        "@id": "https://canmi.net/about#person",
        "name": "Canmi",
        "url": "https://canmi.net"
      },
      "publisher": {
        "@type": "Person",
        "@id": "https://canmi.net/about#person",
        "name": "Canmi",
        "url": "https://canmi.net"
      }
    },
    {
      "@type": "Person",
      "@id": "https://canmi.net/about#person",
      "name": "Canmi",
      "alternateName": [
        "Canmi Wu",
        "canmi21",
        "canmi",
        "canmimua"
      ],
      "jobTitle": "Systems Engineer",
      "url": "https://canmi.net",
      "email": "mailto:t@canmi.icu",
      "image": "https://avatars.githubusercontent.com/u/72544151",
      "identifier": {
        "@type": "PropertyValue",
        "propertyID": "GitHub user ID",
        "value": "72544151"
      },
      "sameAs": [
        "https://github.com/canmi21",
        "https://twitter.com/canmi21",
        "https://nya.one/@canmi",
        "https://bsky.app/profile/canmi.net",
        "https://t.me/canmi21"
      ]
    },
    {
      "@type": "TechArticle",
      "@id": "https://canmi.net/architecture/observation-to-lowering#article",
      "headline": "Future of SeamJS",
      "alternativeHeadline": "From magic to lowering.",
      "description": "SeamJS 将组件语言从 React 转向 Svelte，不再靠执行与 diff 猜测页面结构，而是直接读取公开的 template AST，将静态 markup、动态值与控制流 lowering 为 skeleton、slot 与协议节点，同时为明确的 escape hatch 保留 SSR fallback。",
      "abstract": "SeamJS 的 V1 把 React 编译器当黑盒，靠反复 render + diff 去猜页面结构，遇到 price > 10 这类谓词就只能让用户手写 mock，笛卡尔积穷举也随之失效。这篇记录把 UI stack 换成 Svelte 的决定过程：逐一衡量 React 与 TanStack Start、Vue 3 与 Nuxt、SolidJS 的生态困境，也划清与 Astro islands、Qwik resume、Marko 6 在 request-time 是否仍跑 renderer 上的分歧，并解释 markup AST、runes、<style> 与 CSS-in-JS 的取舍，最后给出 CTR 边界的判定标准与 roadmap。",
      "image": "https://cdn.monoflake.com/object/6d6a5940fd970e46c93628952a10b06f.png",
      "datePublished": "2026-08-22T22:17:34Z",
      "dateModified": "2026-08-23T05:27:18Z",
      "inLanguage": "zh",
      "url": "https://canmi.net/architecture/observation-to-lowering",
      "mainEntityOfPage": "https://canmi.net/architecture/observation-to-lowering",
      "articleSection": "architecture",
      "wordCount": 10870,
      "author": {
        "@type": "Person",
        "@id": "https://canmi.net/about#person",
        "name": "Canmi",
        "url": "https://canmi.net"
      },
      "publisher": {
        "@type": "Person",
        "@id": "https://canmi.net/about#person",
        "name": "Canmi",
        "url": "https://canmi.net"
      },
      "isPartOf": {
        "@id": "https://canmi.net/#website"
      },
      "workTranslation": [
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=de#article"
        },
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=en#article"
        },
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=es#article"
        },
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=fr#article"
        },
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=ja#article"
        },
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=ko#article"
        },
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=zh#article"
        },
        {
          "@id": "https://canmi.net/architecture/observation-to-lowering?lang=tw#article"
        }
      ]
    }
  ]
}
```
