這篇文章可能不適合所有人閱讀。要理解它的內容,你至少得是一名準前端開發者或全端開發者,對 SSR、SSG、ISR 等概念有基本認識,並且至少使用過 Next.js。
前言
可能是我天生就對底層原理有著強烈的好奇心,再加上有時總執著於追問為什麼不能這樣做;我猜大概正因如此,我偶爾才能想出一些獨特的點子,而這就是其中之一。它早在 2025 年 11 月就冒了出來,當時我和身邊許多朋友聊過,但由於手頭專案的時間衝突,一直拖到 2026 年新年前後才真正動手做出一個版本。之後它又被擱置了——因為我跑去架站了。部落格雖然還沒有完全架好,但總算有了一個還算能記錄東西的地方,所以我想把這件事重新拿出來談一談。
首先聲明一點:我不是在說 SSR 不好。我只是覺得,從我看到的趨勢來說,太多人在濫用它,整個大方向都在隨波逐流地逃避一個問題。我也跟不少人討論過這件事,得到的回應大多不太確定;我也講過自己的構想,但對方總是擔心其中會存在各種問題和邊界情況,而且說實話,我也不太想每次都把整套思路從頭到尾完整地講一遍。所以今天索性把它詳細寫下來,試著把其中的一些疑問和技術原理講清楚。
被濫用的 SSR
早在我剛入門網頁前端開發的時候,第一個接觸到的全端框架應該就是 Next.js。我承認這是一個非常優秀的框架,儘管它有一些效能問題,但這些問題換來了實實在在的開發者體驗,這一點無可厚非。不過,隨著我逐漸深入理解前端的一些概念和知識,我發現它可能愈來愈不適合我了——因為我很喜歡寫 Rust,也很喜歡研究底層,還玩過嵌入式系統,所以對效能有一種很特別的執著。這份執著並不是要把什麼都推到極限,而是:明明已經有效能顯著更好、或許只需付出很少努力就能實現的方法,為什麼大家都不這樣做?所以每當用起 Next 時,我就會想:即使只是一個很簡單的頁面,大多數內容都是固定的,只有一個地方需要改變,為什麼整個頁面都必須重新渲染一次?為什麼?在我的理解中,即使後端不是用 Rust 寫的,即使不追求極致效能,就算程式跑在 JavaScript 直譯器上,真正改變的那一點資料也應該只產生相對較小的開銷;「渲染」應該能以很低的成本實現,而不是重新渲染整個頁面。
以至於後來,它已經成為了我心裡的一道坎,變成了我不得不思考的問題。但諷刺的是,當我看完前端這近 10 年的發展,以及 Vue、Svelte、Solid 和它們的框架 Next.js、TanStack Start、Remix、Nuxt、Sveltekit 和 Soild Start 後,我覺得似乎大家都走偏了。尤其是 React 的 RSC,這是一個錯誤,但它的存在其實也是有理由的;這一點以後有機會也許再拿出來詳細談談...
在我看來,RSC 和一眾 SSR 框架其實都在濫用 render 這件事。SSG 倒是做到了我想要的風格——頁面在建置時就生成好了,請求時不需要重新渲染——但它沒有真正的動態能力(x)。而 ISR 呢,看起來是在 SSG 和 SSR 之間找了個平衡,做到了一定的靈活性,但它本質上並沒有達到我的期望;我覺得 ISR 的出現其實就是為了彌補 SSR 效能差的問題,包括後續的各種 CDN 快取策略之類的操作,它們其實都算是一種 workaround,而不是真正優雅的解決方案。
當然,SSR 本身並不是一個錯誤;它是很好的技術,解決了許多真實情境中的問題。但問題在於,這些框架將 SSR 推到了幾乎絕對預設的位置,導致開發者實際使用 SSR 時,可能有 95% 的情境根本不需要它。頁面上的大部分內容都是固定的,真正動態的部分少之又少,但每次請求仍然要把整棵元件樹執行一遍。
不過近幾年 React 倒是有所改進,React 19.2 提供了 PPR,但它在執行階段仍有昂貴的 renderToString(),只能算是一種權宜之計,並非真正的解決方案。所以我就在想,既然大部分內容在建置時就已經確定了,為什麼不直接在編譯期完成渲染呢?
編譯期渲染?
把渲染放到編譯期聽起來是一件很荒謬的事,因為伺服器端渲染存在的核心理由就是,有些值和條件只有到了執行時的那個節點才能解析出來;你不知道它們是什麼,也就無法作出判斷。所以我想,大多數人第一時間否決我的想法也並非沒有道理,完全可以理解。
但其實我想到了一個稱得上精巧的 Pipeline 來實現這件事,這也是為什麼我需要把它稱為一種 Protocol。另外,我一直覺得全端框架混淆前後端邊界是一件很蠢的事(雖然不得不承認,近幾年 Next.js 在這方面的開發者體驗做得很好,讓很多初學者覺得寫一個全端應用程式是件很簡單的事,但實際上很多安全隱患也由此而來——扯遠了)。
所以,在元件裡取得資料也是一樣。我更傾向於一種界線明確的方式——元件就是純元件,資料取得應該被徹底抽離。而一旦你接受了這個前提,將純元件和資料分離,事情就變得有意思了:你會發現資料本身其實是可以分類的。
這裡也要感謝 TypeScript 給我的靈感,其實所謂的 "值" 本身在編譯期是什麼並不重要,重要的是它的型別;一個 slot 裡要填什麼內容,只要它不是那種 Open String (有無限種可能的字串),它就可以被包裹為有限可能性的型別。比如你在寫一個 dashboard,需要條件渲染某塊內容,無非就是 User 或 Admin 等等但是總歸是可以定義完的型別;真實世界中幾乎所有需要條件渲染、需要做邏輯判斷的地方,都可以被歸納為幾種確定的可能性。
那正好這些可能性可以用一個很好的辦法來描述,那就是 JTD!
JTD 規範
JTD(JSON 型別定義)由 RFC 8927 定義,共有八種綱要形式:空、參照、型別(布林值、字串、時間戳記及各種精度的數值型別)、列舉、元素、屬性、值和判別器;此外,任何綱要都可以標記為 nullable。JTD 的優點在於它能跨語言使用,JavaScript、Rust、Go 以及幾乎所有其他語言都能找到對應的型別映射。這讓它自然成為前端與後端之間的橋梁,而它採用的載體 JSON 原本就是前端與後端的最小公約數。現實世界中,95% 的網站應用程式無非就是顯示字串、數值欄位,或用布林值進行判斷,而這些全都涵蓋在這個範圍內。釐清這一點後,我們實際上就有了很大的操作空間。當然,JTD 並不完美,也存在 Markdown 這類例外,不過我們稍後會詳細討論;而這其實才是我真正認可 SSR 價值的地方。
哨兵值
先說簡單的:既然我們已經知道每個動態值的型別,就可以在編譯期用 renderToString() 執行一遍 React 元件,不過不用真實資料,而是用 Sentinel 模擬出來的資料。這究竟是什麼意思呢?
{ user: { name: "Alice", age: 30 } }假設你的資料長這樣,那麼我們可以把它替換成
{ user: { name: "%%SEAM:user.name%%", age: "%%SEAM:user.age%%" } }這些 %%SEAM:...%% 就是 Sentinel,它們占住了每一個動態值的位置。React 拿著這些 Sentinel 資料跑完 renderToString() 之後,輸出的 HTML 裡這些位置就被標記出來了。接下來建置管線會把這些哨兵轉換成 HTML 註解形式的 slot 標記
<!-- Sentinel -->
<span>%%SEAM:user.name%%</span>
<!-- Slot -->
<span><!--seam:user.name--></span>到這一步你可能已經看出來了這些 slot 就是 "有型別標記的插槽"。這個時候伺服器端執行環境要做的事情變得極其簡單,那就是拿到真實資料,往這些孔裡填值,純粹的字串替換。完全不需要 renderToString(),不需要 JavaScript 執行環境,不需要 vDOM。任何能解析 HTML 註解、能做字串替換的語言都能當後端,Rust、Go、TypeScript 都行,這就是為什麼它是一個協定而不是一個框架。
這個時候你可能會想到條件渲染怎麼辦?比如某個欄位為 null 的時候一整塊內容都不應該出現,但是其實這個也不需要執行期的 JavaScript 來判斷,反之是協定可以定義這個情況
<!--seam:if:user.avatar-->
<img src="<!--seam:user.avatar-->" />
<!--seam:endif:user.avatar-->條件渲染如此,列表渲染也一樣。
<!--seam:each:messages-->
<li><!--seam:$.text--></li>
<!--seam:endeach-->甚至模式匹配
<!--seam:match:status-->
<!--seam:when:active--><span class="green">Active</span>
<!--seam:when:disabled--><span class="red">Disabled</span>
<!--seam:endmatch-->這就是註解的藝術:這裡選擇註解,只是因為它恰好是合法的 HTML,僅此而已。那麼,這些條件區塊和迴圈區塊是如何在建置時確定並產生的呢?其實方法也相當巧妙:雖然沒有虛擬 DOM,但我們可以渲染兩次 HTML 並比較差異。以條件式渲染為例,第一次使用完整的哨兵資料渲染,第二次將某個 nullable 欄位設為 null 後再渲染一次;比較兩次輸出,消失的那段 HTML 就是由該欄位控制的條件區塊,直接用 <!--seam:if:...--> 包起來即可。陣列也是同理。
也許是 CTR?
那肯定又有人要擔心了:條件渲染的組合不會指數爆炸嗎?比如 3~5 個變數控制條件渲染,每個變數有 10 種可能,乘起來確實是一個看起來很大的數字。但實際上這個數字只是看起來大唬人而已嘛。首先,很多字串型別的值我們並不需要真的窮舉內容,我們只關心它有值還是沒值 nullable 這裡實際上就是 2 種;其次,真正需要做條件判斷的欄位,它的型別一定是可窮舉的,就比如(你不會拿一個 open string 來做 if 判斷吧?);再說了,就算真的要窮舉所有組合,在現代 CPU 上跑完可能也就幾 ms,而且這完全是編譯期的重活,就像 Rust 編譯一樣,編譯期付出比較重的代價,但執行期就變成了純粹的字串替換,這筆生意怎麼算都是划算的吧,更何況這裡沒有任何特別的魔法。但是這裡也可以埋一個小點:後面我其實引入了一個很小的嵌入式 JavaScript 執行環境來做一些複雜的推導,但這算是一種妥協,後面會解釋原因,以及理論上是可以不要的。
這就是我所說的 CTR(編譯期渲染)。至於限制,它其實和 SSR 的限制完全一樣:例如,<!--seam:path--> 插入文字時會自動進行 HTML 跳脫(&、<、> 等),如果需要插入原始 HTML,就必須明確使用 <!--seam:path:html-->;缺少的資料路徑在文字 slot 中會變成空字串,在屬性 slot 中則會略過注入;each 區塊拿到的如果不是陣列,也會直接略過。這些行為與 SSR 框架中的邊界情況並沒有本質區別,我只是把 SSR 的渲染步驟移到編譯期執行,僅此而已。CTR 會在建置時遍歷所有條件變數型別取值的笛卡兒積,使用任意自動產生且符合型別的模擬值,或由使用者指定的特殊模擬值(一般不需要手動提供產生所有 HTML 變體所需的模擬值,不過也可以提供;否則系統會自動選取符合相應型別的值)。每種組合都會渲染一次,再透過比較結果找出所有條件區塊和迴圈區塊的邊界,最終得到一個完整展開的 HTML 骨架。從數學上說,只要執行時傳入的資料符合這些型別定義,就一定能被正確注入這個骨架,因為所有可能的分支路徑都已經在編譯期被窮舉過了。
一致性?
那麼該如何確保一致性呢?答案就是 JTD 這份契約:前後端雖然彼此分離,但只要都遵循同一份 JTD 綱要,資料型別就能保持一致,不會出現不相符的問題。
但和其他框架相比,我還多了一項挑戰:既然後端已擺脫 JavaScript 執行環境的限制,就不必再與前端綁成一體;你完全可以用 Rust 或 Go 撰寫後端。在 TypeScript 全端框架中,前後端的一致性可以直接透過型別來保證;換成其他語言呢?其實我在這裡採用了程式碼生成:無論後端使用哪種語言撰寫,都以後端為準,將前端可用的變數和型別直接產生為 TypeScript,供前端匯入。這樣一來,雖然前後端的界線劃清了,但實際上仍可像 TypeScript 全端框架一樣放在同一個資料夾中,符合單一儲存庫的慣例,彼此可以直接呼叫,不必手寫應用程式介面或 gen OpenAPI 之類的東西。實際上,我使用私有路徑 /_seam/ 作為框架的預設端點(和 Nuxt 一樣可以設定),執行 JTD Typed-RPC 後便能解決資料傳輸與跨來源資源共用的問題。
CTR × SSR
接著是 raw HTML slot,這其實又回到了那 5% 的邊緣情況,例如 Markdown、Rich Text 之類的東西,編譯時根本不可能知道它們的值,而且用型別約束它們的成本非常高。當然,你也可以擴充協定,列出 Markdown 的所有語法,再於執行時進行解析,但那樣的工作量基本上就等於重寫一個 Markdown 渲染器了,不是嗎?而我們只約束最少的幾種型別,這和重寫整個渲染引擎的工作量完全不可同日而語。這也回到了我一開始提到的理念:以更低的成本、更優雅地解決問題。
所以好消息是,raw HTML slot 其實可以讓 CTR 和 SSR 共存。這意味著一個頁面的大部分使用者介面都可以用 CTR 來實作,幾乎沒有執行階段成本;然後最核心的那篇 Markdown 文章則用 SSR 來渲染。由於後端已經不受限制,你既可以用 TypeScript 匯入原本那套 SSR 渲染方法,也可以打開思路!既然後端用的是 Rust,當然也可以用 Rust 的 Markdown 編譯器來渲染。反正最後只要產生一個 HTML 字串,插入 raw slot 後就能顯示。使用 CTR 並不意味著不能使用 SSR,兩者完全可以共存。
PPR 的落差
這樣理解下來,CTR 本質上就是 PPR(Partial Prerendering) 最理想的情況,所有能快取的東西全部在編譯期渲染完了,零執行階段開銷,只有真正變化的那一點資料才有成本。那和 React 19.2 的 PPR 不同的是什麼呢?答案是我更徹底,更激進 PPR 就算在最理想的情況下,所有靜態部分都快取好了,只有一小段動態內容需要更新,但哪怕這個動態內容只是一個簡單的字串變了,React 還是得重新跑一遍 renderToReadableStream(),這個代價是不小的。
而 CTR 把這條界線劃得很清楚:字串、數字和布林值等簡單型別都直接進行字串替換;只有 Markdown 這種窮舉成本極高的複雜型別,才會進入真正的渲染流程。而且這裡的渲染已經不是傳統意義上的 SSR,它可以採用任何程式語言實作的渲染方式。
還有 RSC
那再來談談 RSC(React 伺服器元件)。之前說過 RSC 可能是一個錯誤,那是因為它模糊了太多前後端的界線,帶來了不少安全隱患,以及眾多 CVE。但其實這更多是 Next.js 和其他 SSR 方案的錯,使用者根本沒有機會不用,變成了想要動態內容就必須用 SSR,可實際上像我們這樣做字串替換也完全行得通。另外不得不承認,RSC 賦予了伺服器端一項很重要的能力,那就是渲染任意 React 元件,也就是執行任意程式碼。
不過,既然都要執行任意程式碼了,想用型別把它窮舉出來基本上不可能;就算可能,工作量也未必比寫一個 React 編譯器小。所以,RSC 的這種能力確實有其存在的理由。CTR 和 RSC 能不能共存?答案是可以,而且完全不衝突。但這是框架層面需要解決的問題,不屬於協定本身的範圍。我暫時還沒有實作,不過看起來可以借鑑 TanStack Start:只要多傳送一個 html 和一個 js 套件,理論上實作起來還是很容易的,至少工作量清晰可見。
再說回 raw HTML slot,它主要解決的是 Markdown、Rich Text 這類場景:渲染出來的 HTML 透過 dangerouslySetInnerHTML 注入,水合之後這塊區域不參與互動,是 "死的"。這部分內容應該盡量少,你想幫文章加邊框、加樣式,那些應該寫成 React 元件,而不是混在這段 HTML 裡。這種做法的缺點是水合之後它不能再改變,但它確實把這個前提下的 "SSR" 成本壓到非常低,接近於零成本。在真正需要 SSR 的場景裡,大概 60% 都屬於這種靜態 HTML 注入;剩下 40% 才需要 RSC 那種在伺服器端執行任意元件的能力。
UI 無關性
最後,還有相當誘人的協定無關性。本質上,我們只需要抓住 renderToString 這一個關鍵點,前面使用什麼 UI 框架其實與協定無關。那麼,這和 Astro 有什麼區別呢?不過請放心,我打造的肯定不是另一個 Astro。表面上看,我的概念與 Astro 的島嶼有些相似,但實際上兩者差異很大。
我並沒有在單一頁面中水合多個執行環境。嗯,其實我覺得真正需要這樣做的情境非常有限,不同技術堆疊之間的元件狀態通訊會變得非常昂貴。它更像是一種過渡方案:從一個技術堆疊遷移到另一個技術堆疊時,無法一次完成替換,所以先用它來過渡。其次,Astro 本質上是 MPA,而我們可以在水合前維持 MPA,水合後再變成 SPA,像 Next.js 一樣提供使用者端路由並實現跨頁面動畫——這是 Astro 夢寐以求的能力。
Astro 與 SSG
另外,Astro 的設計本來就只有在橫跨多個技術堆疊的情境中才真正有用。如果你只是為了速度而使用它,卻只用其中一個框架,例如只引入 React 而不引入 Vue,那我認為它所謂的 「速度快」 是個偽命題。首屏載入的確全是 HTML,但只要你需要任何互動,它就必須進行水合,而水合的代價就是下載整個 React 執行階段,這與我們的水合沒有任何本質差異。當然,之後我們也可以採用島嶼架構的概念,加入一個外殼路由器來實現跨使用者介面框架的單頁應用程式導覽,但那是後續路線圖上的事,至少我現在不急。
最後是與傳統 SSG 的比較,我做到了和 SSG 一樣的事,本質上就是把編譯期能確定的內容全部渲染好;但我們更加動態,因為那些簡單型別的 slot 值完全可以在執行期替換。你可以理解成,我們把 SSG 渲染成一種 MPA 的入口,水合前是 MPA,水合後變成 SPA,同時保留了「動態」能力。
水合錯誤?
最後就是我很討厭的 hydration mismatch,我相信你也不喜歡;但是歸根結底這其實就是 DOM 狀態不一致,而傳統框架比如 Next.js 的 App Router 試圖把整個應用都用 React 包裹起來,一旦使用者端有瀏覽器注入什麼標記,就有機率出現水合錯誤。我們和傳統 SSR 的限制其實是一樣的,但是我在你用 TS 的時候也包裹了一個叫做 __root 的水合 div,這樣讓水合區域不再覆蓋 metadata 區域,換來了更加耐用的水合,另外 React 19 內建了對 <title>、<meta>、<link> 等文件中繼資料標籤的支援。即使你水合的只是頁面中的某個 <div>(而不是整個 <html>),在元件裡直接渲染 <title>My Page</title>,React 也會自動把它提升 hoist 到 <head> 中去。
回到 水合不一致:既然我們在編譯期就已經知道每個 slot 的型別,便可以再加一道 CTR 等價性檢查,也就是用根據型別定義推導出的模擬資料填入完全展開的 HTML,再呼叫傳統的 renderToReadableStream() 執行一次,然後比較兩棵 DOM 樹在語意上是否等價,而不考慮格式化差異。兩者的格式可能略有不同,但只要 DOM 結構嚴格且完全等價,其實就可以徹底告別 水合不一致 了。為什麼傳統 SSR 做不到?因為它們直到執行期才進行這項工作,而 CTR 的結構決定了你必須在編譯期落實所有這些約束。當然,我們也提供了 any 這個逃生口,但和 TypeScript 一樣,一旦使用 any,你就必須主動承擔後果:CLI 會在編譯時警告你,作為開放字串逃生口的 any 可能導致不一致。
無伺服器
當然,無伺服器這種可能性也不能少。近幾年,無伺服器的體驗可以說已經非常好了,我的評價是「除了花錢,沒什麼缺點」。而 CTR 天生就很適合這個場景:我們在執行階段做的事情既輕量又少,放在無伺服器環境中會執行得非常快,回應時間與額外負擔都會顯著改善。剩下無法改善的,也就是渲染 Markdown 這類場景,但那完全應該從業務邏輯上最佳化,例如預先渲染並儲存 Markdown,這樣每次請求時就不必重新渲染,正如我現在這個網站所做的一樣。這是業務層面的問題,不是框架能解決的;但除此之外,那些簡單邏輯的額外負擔,框架可以幫你壓到幾乎為零。與傳統 SSR 相比,它們的額外負擔已經不在同一個數量級。到底有多小?大約就是數百微秒到 1 毫秒,這是傳統 SSR 想都不敢想的。
那如果後端使用其他語言呢?以 Cloudflare Workers 為例,其實許多無伺服器平台都支援 WASM,因此其他語言編譯成 WASM 二進位檔後,同樣可以用於後端。本質上,我只是把前端編譯成純靜態資源,類似使用者端渲染;但這批純靜態資源搭配一個私有橋接器 /_seam/,就能實現與真正的全端框架相同的動態能力,自然也完全相容於無伺服器平台。
Seam 與 SeamJS
所以你是不是已經被 Seam 和 SeamJS 的關係搞糊塗了?其實這兩者很簡單。Seam 是協定,它定義的是:怎麼用 Sentinel 標記動態位置、怎麼轉換成 slot 標記、條件區塊和迴圈區塊怎麼做 diff 偵測,以及執行期怎麼基於 AST 做資料注入。協定本身與語言無關,任何能解析 HTML 註解、能做字串取代的後端都能實作它。SeamJS 是框架,是基於這套協定做出來的一個具體實作:它把 Vite、TanStack Router、TanStack Query 這些現成的輪子在一起,再補上它們涵蓋不到的地方,例如我這套 skeleton 擷取、注入引擎、CLI 之類的東西。
但說實話,SeamJS 現在還是一個非常基礎的樣子。跑起來是沒什麼問題的,但真的拿它去寫專案的話還需要打磨很久,我也在更多地思考一些架構上的改革,比如資料傳輸通道的抽象,但我並不確定在後續版本中還會不會堅持這個方向?框架層面上,我肯定會做出一個 TypeScript 的全端框架,以及一個 Rust 作為後端的旗艦框架;至於 Go 相關的實作,會在後續版本中移除掉,我覺得自己的精力維護不過來了。
Rendering is a protocol, not a render-time computation.
不只是網頁框架
那這個東西做出來有什麼用呢?實際上它不只侷限於 Web。比如說我把 Transport 通道也給抽象掉之後,是可以後續移植到類似於 Electron 或者 Tauri 之類的 Desktop 環境的,只要把 HTTP 傳輸管線換成 IPC 通訊,跑的還是一樣的 Seam 協定,取而代之的就是那些 Electron 應用開屏的時候再也不用放 loading 了,很多東西能夠直接像 SSR 一樣本地渲染出來,這就是 CTR 的魔法!
沒有 JS 執行環境的代價
那為什麼 SeamJS 最後還是加入了 JS 執行環境呢?因為在落實這套方案時,我遇到了一個問題:CTR 的理想設計對首屏資料有著極其嚴格的約束。資料必須是一個能夠完全推導出的確定結構體,甚至不能包含任何計算邏輯,只能包含條件。這就非常嚴苛了。可以想見,從框架的開發體驗來說,習慣傳統 React 寫法的人肯定希望能在元件裡做一些計算,取得幾個值後直接使用。如果要嚴格遵循 CTR 的約束來達到 ready-to-display 狀態,整個過程會非常繁瑣,甚至可能需要把同一個元件寫兩遍。
但其實有解:由於 Web 本身的限制,前端只能執行 JavaScript,元件自然也只能執行 JS。因此,要解決這個問題,後端就必須具備執行 JS 的能力;TypeScript 全端專案可以直接利用自身的執行環境,而 Rust 只需嵌入 QuickJS 這樣極其輕量的 JS 執行環境即可。需要注意的是,這裡的 JavaScript 執行環境只實作了標準 JS 的一個子集,完全不同於 Bun 或 Node 那種帶有完整作業系統 API 的執行環境;它確實只用於推導資料。
這樣就可以在元件裡還是寫一些計算邏輯,這部分邏輯會在後端拿到資料之後跑一段小 JS,把它推導為 ready-to-display 的狀態,然後再發回給前端做首屏的水合。這樣 CTR 滿意了,拿到了嚴格結構的派生資料,使用者 DX 開心了,元件只寫一遍。當然如果你願意嚴格遵守純型別約束的話,後端確實可以做到 No JS Runtime,這個承諾其實還是成立的。但無論怎麼說,新增的這個很小的 JavaScript Runtime,它的效能開銷、記憶體佔用和體積都遠遠比 Node 之類的東西小得多,基本上就是 200-300KB 的事情,卻帶來了非常大的靈活性。
未來展望
講了這麼多,到底什麼時候才能 呢?大概還要很久很久。真的不是我想 ,而是這東西在現階段綁架了我太多太多。如果我只能拿它來做開發,那基本上就寫不了應用了,寫什麼都先被框架絆一腳,更何況我最近主要都在寫這個網站之類的東西。所以我想通了:不如先把網站做到一定規模,這樣才知道我的需求到底是什麼;以後再回頭寫 SeamJS,手上就有一份 TODO List 了,一個一個把功能實作好,它就能用了。後續還能再水一篇遷移和 Benchmark?好了,,理念就擺在這裡。先別管今天能不能用,至少思路上原型已經跑通了。晚安 💤