跳至主要內容

Sapper:邁向理想的 Web 應用框架

跨出下一步

給心急的人的快速入門:Sapper 文件以及起始範本

如果你要列出一個完美的 Node.js Web 應用框架的特點,你可能會想到以下幾點:

  1. 它應該要支援伺服器端渲染,以實現快速初始載入且沒有 SEO 的限制
  2. 作為一個必然結果,你的應用程式程式碼應該是通用的 —— 一次編寫,適用於伺服器用戶端
  3. 用戶端應用程式應該還原伺服器渲染的 HTML,將事件監聽器(等等)附加到現有元素,而不是重新渲染它們
  4. 導覽到後續頁面應該是瞬間完成的
  5. 應該要開箱即支援離線和其他漸進式 Web 應用程式的特性
  6. 應該只載入首頁所需的 JavaScript 和 CSS。這表示框架應該在路由層級進行自動程式碼分割,並支援動態 import(...) 以進行更細微的手動控制
  7. 效能不容妥協
  8. 一流的開發者體驗,具有熱模組重新載入和所有修飾
  9. 產生的程式碼庫應該易於理解和維護
  10. 應該可以理解和自訂系統的每個方面 —— 沒有鎖在框架中的 webpack 設定,並且盡可能少的隱藏「管線」
  11. 應該很容易在一個小時內學會整個框架,而不僅限於經驗豐富的開發人員

Next.js 接近這個理想。如果你還沒有接觸過它,我強烈建議你去瀏覽 learnnextjs.com 上的教學。Next 提出了一個很棒的想法:你應用程式的所有頁面都是 your-project/pages 目錄中的檔案,而每個檔案都只是一個 React 組件。

其他一切都源於該突破性的設計決策。找到負責特定頁面的程式碼很容易,因為你可以直接查看檔案系統,而不是玩「猜組件名稱」的遊戲。專案結構的爭論已成為過去。並且 SSR(伺服器端渲染)和程式碼分割的結合 —— React Router 團隊放棄了,並宣布「那些嘗試伺服器渲染、程式碼分割應用程式的人,祝你們好運」—— 這一點變得微不足道。

但它並不完美。儘管挑剔某些如此、如此優秀的東西的缺點可能有點不應該,但還是有一些缺點的

  • Next 使用稱為「路由遮罩」的東西來建立漂亮的 URL(例如 /blog/hello-world 而不是 /post?slug=hello-world)。這破壞了目錄結構對應於應用程式結構的保證,並迫使你維護在兩種形式之間轉換的設定
  • 你的所有路由都被假設為通用的「頁面」。但是,通常需要僅在伺服器上渲染的路由,例如 301 重新導向或為你的頁面提供資料的 API 端點,而 Next 對此沒有很好的解決方案。你可以將邏輯新增到你的 server.js 檔案中來處理這些情況,但它感覺與頁面採用的宣告式方法格格不入
  • 要使用用戶端路由器,連結不能是標準的 <a> 標籤。相反,你必須使用特定於框架的 <Link> 組件,例如這篇部落格文章的 markdown 內容中就無法使用

但真正的問題是,所有這些好處都是有代價的。最簡單的 Next 應用程式 —— 一個渲染一些靜態文字的單一「hello world」頁面 —— 包含 66kb 的 gzip 壓縮 JavaScript。解壓縮後,它是 204kb,對於行動裝置來說,這是一次性解析的非小量程式碼,而效能是決定使用者是否會留下來的關鍵因素。而這還是基準

我們可以做得更好!

編譯器即框架的典範轉移

Svelte 引入了一個激進的想法:如果你的 UI 框架根本不是框架,而是將你的組件轉換為獨立 JavaScript 模組的編譯器呢?我們可以運送高度優化的原生 JavaScript,而不是使用像 React 或 Vue 這樣的函式庫,它們對你的應用程式一無所知,因此必須是一種一體適用的解決方案。只需要你的應用程式所需的程式碼,而沒有基於虛擬 DOM 的解決方案的記憶體和效能開銷。

JavaScript 世界正在朝這個模型發展。由 Ionic 團隊開發、受 Svelte 啟發的框架 Stencil 會編譯為 Web Components。Glimmer 不會編譯為獨立的 JavaScript(其優缺點值得單獨寫一篇部落格文章),但該團隊正在圍繞將模板編譯為位元組碼進行一些有趣的研究。(React 正在參與其中,儘管他們目前的研究重點是最佳化你的 JSX 應用程式程式碼,這可以說更類似於 Angular、Ractive 和 Vue 多年來一直在執行的預先最佳化。)

如果我們將這個新模型作為起點會發生什麼?

介紹 Sapper

Sapper 就是這個問題的答案。Sapper 是一個類似 Next.js 的框架,旨在滿足本文頂部的 11 個標準,同時大幅減少發送到瀏覽器的程式碼量。它以與 Express 相容的中介軟體的形式實作,這表示它易於理解和自訂。

使用 React 和 Next 產生 204kb 的相同「hello world」應用程式,使用 Sapper 僅重 7kb。隨著我們探索最佳化可能性的空間,例如對於非互動式的頁面,除了處理用戶端路由的微小 Sapper 執行階段之外,不運送任何 JavaScript,這個數字在未來可能會進一步下降。

那更「真實世界」的範例呢?方便的是,RealWorld 專案挑戰框架開發一個 Medium 克隆的實作,為我們提供了一種找出答案的方法。Sapper 實作需要 39.6kb(壓縮後為 11.8kb)才能渲染一個互動式首頁。

整個應用程式的成本為 132.7kb(壓縮後為 39.9kb),這明顯小於參考 React/Redux 實作的 327kb(85.7kb),但即使它一樣大,也會因為程式碼分割而感覺更快。這是至關重要的一點。我們被告知需要分割應用程式的程式碼,但是如果你的應用程式使用像 React 或 Vue 這樣的傳統框架,那麼你的初始程式碼分割區塊的大小會有一個硬性下限 —— 框架本身,它很可能是你應用程式總大小的重要部分。使用 Svelte 方法,情況就不再是這樣了。

但大小只是故事的一部分。Svelte 應用程式的效能和記憶體效率也極高,並且該框架包含強大的功能,如果你選擇「最小」或「簡單」的 UI 函式庫,你將會犧牲這些功能。

權衡取捨

對於許多評估 Sapper 的開發人員來說,最大的缺點將是「但我喜歡 React,而且我已經知道如何使用它」,這很公平。

如果你是那一派,我邀請你至少嘗試其他框架。你可能會感到驚喜!Sapper RealWorld 實作總共有 1,201 行原始程式碼,而參考實作則為 2,377 行,因為你可以使用 Svelte 的範本語法非常簡潔地表達概念(只需五分鐘即可掌握)。你將獲得作用域 CSS,內建未使用的樣式移除和縮小,並且如果你願意,可以使用像 LESS 這樣的預處理器。你不再需要使用 Babel。SSR 非常快,因為它只是字串串連。而且我們最近推出了 svelte/store,一個微小的全域儲存,可使用零樣板同步你的組件階層中的狀態。最壞的情況是,你會感到被證實是對的!

但儘管如此,還是存在權衡。有些人病態地厭惡任何形式的「模板語言」,也許這適用於你。JSX 的支持者會用「它只是 JavaScript」的口號來打擊你,而這正是 React 最大的優勢,那就是它具有無限的靈活性。這種靈活性有它自己的一組權衡取捨,但我們在這裡不是要討論那些。

然後還有生態系統。特別是 React 周圍的世界 —— 開發工具、編輯器整合、輔助函式庫、教學、StackOverflow 回答,甚至就業機會 —— 是無與倫比的。雖然將「生態系統」作為選擇工具的主要原因是一種你被困在局部最大值的跡象,很容易被進步的浪潮所淹沒,但它仍然是支持在位者的主要理由。

路線圖

我們還沒有達到 1.0.0 版本,在我們達到目標之前,有些事情可能會改變。一旦我們這樣做了(很快!),就會有很多令人興奮的可能性。

我相信 Web 效能的下一個前沿是「整個應用程式最佳化」。目前,Svelte 的編譯器在組件層級運作,但理解這些組件之間邊界的編譯器可以產生更有效率的程式碼。React 團隊的 Prepack 研究 是基於類似的想法,而 Glimmer 團隊正在這個領域做一些有趣的工作。Svelte 和 Sapper 處於有利地位,可以利用這些想法。

說到 Glimmer,將組件編譯為位元組碼的想法是我們可能會在 2018 年竊取的想法。像 Sapper 這樣的框架可以根據你的應用程式的特性來確定使用哪種編譯模式。它甚至可以為初始路由提供 JavaScript 以實現最快的啟動時間,然後為後續路由惰性地提供位元組碼直譯器,從而實現啟動大小和應用程式總大小的最佳組合。

但是,在大多數情況下,我們希望 Sapper 的發展方向由其使用者決定。如果你是喜歡走在前沿的開發人員,並且想幫助塑造我們建構 Web 應用程式的未來,請加入我們在 GitHubDiscord 上的行列。