JavaScript Web 框架的 “新浪潮”

大家好,我是 ConardLi,今天跟大家一起聊聊 JavaScript 框架。

太過保守很難在 Javascript 生態系統中保持與時俱進。

對於那些剛進入這個行業的人來說,要在新的庫、框架、概念和有力的意見中關注正在發生的事情,很有挑戰性。

這是個很好的提醒,默認情況下,使用 “無聊” 的技術,你所熟悉的技術,並且成爲晚期採用者,通常是個不錯的選擇。

閒話少敘,本文將帶讀者瞭解 Javascript 中生態系統中的最新進展,通過研究過去在構建大規模 Web 應用時的痛點來了解當前的情況。

不要把注意力集中在快速增長的解決方案上,而是從潛在問題入手。每一種架構都會有不同的答案,並且會有不同的權衡。到本文結束時,我們會列出流行框架的高級模型,如 React、Svelte、Vue、Solid、Astro、Marko、Fresh、Next、Remix、Qwik,以及適合當今環境的 “元框架” 。

鑑往知來。讓我們回首來時路,再看看未來的趨勢。這次,我們將專注於大型項目中的問題,這些問題激發了其他方法和思維方式。

網頁簡史

Web 最初由靜態文檔鏈接在一起組成。那時候,人們可以提前準備一份文件,並把它放在電腦上。而現在最酷的就是,人人都可以訪問它,無需親臨其境。

不知從何時起,我們覺得,讓這些文件變成動態,會非常酷。於是我們有了像 CGI 這樣的技術,使我們能夠根據請求提供不同的內容。然後,我們有了像 Perl 這樣的表達式語言來編寫這些腳本。它對最初針對 Web 開發的 PHP 產生了影響。PHP 的創新之處在於將 HTML 直接連接到後端代碼。這使得以編程方式創建嵌入動態值的文件變得容易了。

Web 最重要的突破之一來自於此:

<html>
  <body>
    Hello ConardLi !
  </body>
</html>

具有易於嵌入的動態值:

<html>
  <body>
    Y2K? <?php echo time(); ?>
  </body>
</html>

框架時代拉開大幕

這些動態頁面很受歡迎。我們可以很輕鬆地對發送給用戶的內容進行定製,包括啓用會話的 cookies。在與數據庫交互的語言生態系統中,已經有了基於服務器的模板框架。通過這些框架,我們可以輕鬆地從靜態頁面開始,然後擴展到動態頁面。

Web 的發展一日千里,我們想要更多的互動體驗。爲了這個目的,我們使用了 Flash 這樣的瀏覽器插件。在其他方面,我們會在後端提供的 HTML 上 “撒上” Javascript 片段。

jQueryPrototype 這樣的工具出現了,它們隱藏了 Web API 的複雜度,消除了瀏覽器之間的差異。

光陰荏苒,科技公司的規模在不斷擴大,並且由於項目和開發團隊的增長,在模板中加入更多的業務邏輯是非常普遍的。

編寫的服務器代碼,將處理後的數據傳輸到服務器模板語言中。模板常常會演變成業務邏輯的 “混合體” 來訪問全局變量。由於像 SQL 注入這樣的攻擊已經司空見慣,因此安全問題也越來越突出。

最終,論文《Ajax:Web 應用的新方法》(Ajax: A New Approach to Web Applications)爲我們帶來了 Ajax 技術。現在你用 Ajax 技術可以做的新事情就是用異步方式更新頁面,而不再是以同步的方式來更新頁面。這種模式被第一批大型客戶端應用程序所推廣,如谷歌地圖和谷歌文檔。後來,我們開始看到 Web 分發對桌面風格的軟件的影響力。與在商店裏購買光盤的軟件相比,這是一個重大的進步。

JavaScript 壯大

Node.js 出現的時候,它所帶來的新特性,就是用與前端相同的語言來編寫你的後端。所有這些都是開發人員所熟悉的異步優先模式。這曾經令人無法抗拒,當然現在也是。隨着越來越多的企業上線,競爭優勢在於能否快速交付和迭代。

Node.js 的生態系統強調重複使用小型的單用途包,你可以利用現成的去完成任務。

前端與後端分離

我們更渴求能夠與桌面、移動設備相媲美的 Web。現在,我們已經有了一系列可重用的 “小部件” 庫和工具,如 jQuery UI、Dojo、Mootools、ExtJs 和 YUI 等。

我們對這些小玩意兒的關注程度與日俱增,並且在前端的工作也越來越多。這往往導致了前端和後端的模板重複。像 BackboneKnockout 以及許多其他的框架出現了。它們通過 MVC、MVVM 等架構爲前端增加了關注點的分離,並且,架構可以兼容我們收集到的所有小部件和 JQuery 插件。添加結構有助於擴展所有這些前端代碼。並且可以加速從後端傳送模板。

我們仍然編寫微調的 DOM 操作來更新頁面並保持組件的同步。這個問題非同小可,而且與數據同步相關的錯誤也很常見。

在谷歌的支持下,Angular 登場了。它通過增強 HTML 的動態性,促進了生產力的提高。它配備了雙向數據綁定,以及一個受電子表格啓發的反應性系統。這些聲明式的雙向綁定消除了許多必須更新的模板。這是好事,可以讓我們的工作效率更高。

隨着規模的擴大,跟蹤變化越來越困難,常常會造成性能下降。更新的週期會發生,並佔據主線程(今天像 Svelte 這樣的庫可以在降低其缺陷的情況下保持雙向綁定)。除了移動設備的興起之外,這些提高生產力的框架也加速了前端和後端的分離。這爲探索強調這種解耦的不同架構鋪平了道路。

這是 JAMstack 理念的一個主要部分,強調提前預生成 HTML,並從 CDN 提供服務。在當時,這是對提供靜態文檔服務的一種倒退。但現在,我們有了基於 git 的工作流,有了強大的 CDN 基礎設施,有了可以與獨立 API 互動的解耦前端,就無需依靠遠在天邊的中央服務器。與運營服務器相比,將靜態資產放置到 CDN 上要便宜很多。

今天,像 Gatsby、Next 和很多的其他工具都利用了這些想法。

React 崛起

快步流星地進入大科技時代。我們正試圖追風逐電,一改故轍。對於那些進入這個行業的人來說,Javascript 很大,而構建一個由獨立後端支持的解耦 SPA 已經成爲現實。

FacebookReact 的誕生面臨着幾個挑戰。

  1. 數據頻繁變化時的一致性:保持許多小部件之間的同步,仍然是一項重大的挑戰。由於數據流缺乏可預測性,這在規模上是個問題。

  2. 組織上的擴展:優先考慮進入市場的時間和速度。對於新開發人員來說,能否快速上手,並且富有成效,這一點至關重要。

React 誕生了,你能做得很酷的新事情就是聲明性地編寫前端代碼。

前端關注點的分離是著名的反思,以前的 MVC 框架無法擴展。人們並不喜歡從模板向 Javascript 驅動的 JSX 過渡。但是我們大多數人都接受了。

組件模型允許解耦獨立的前端團隊,他們可以更容易地在獨立組件上並行工作。作爲一個架構,它允許組件的分層。從共享的原語到構成頁面根目錄的 “有機體”。單向的數據流使數據流更易於理解、跟蹤和調試。這就提高了之前難以企及的可預見性。虛擬 DOM 就是我們可以編寫函數,返回用戶界面的說明,讓 React 去解決這些難點。這樣可以避免在數據頻繁變化時出現的一致性問題,並且使得模板的組成更加人性化。

規模化的 React 已達到 CPU 和網絡的極限

React 非常流行,已經成爲了業界的標準,即使是那些不想要其特性的網站來說也是如此。在規模的遠端,我們開始看到一些限制。

CPU 遭遇很大阻力

DOMReact 模型的一個問題。瀏覽器並不是爲了在連續的渲染週期中不斷創建和銷燬 DOM 節點而構建的。就像任何可以通過引入一個新的間接級別來解決的問題一樣,React 把它抽象到了虛擬 DOM 後面。

人們只有在 100 毫秒以內感知到反饋,纔會感到流暢。而在做像滾動頁面這樣的事情時則要低得多。在與單線程環境相結合的情況下,這種優化已經成爲高度交互式應用的新瓶頸。當虛擬 DOM 和真實 DOM 之間發生協調時,大型交互式應用程序會對用戶的輸入失去響應。像 “長任務” 這樣的術語開始出現了。

這導致了 React 在 2017 年被重新編寫,爲併發模式奠定了基礎。

運行時成本增加

與此同時,更快的移動意味着傳輸更多的代碼。瀏覽器在運行大量 Javascript 時,啓動速度慢就成爲一個問題。我們開始注意到所有隱含的運行時成本,不僅是 HTML 和虛擬 DOM,還有我們編寫 CSS 的方式。

組件模型簡化了我們在 CSS 方面的經驗。我們可以將樣式與組件放在一起,這提高了可刪除性。對於那些以前不敢刪除 CSS 代碼的人來說,這是一個非常好的屬性。我們一直在處理的級聯和所有的特殊性問題都被 JavaScript 庫中的 CSS 抽象化了。

這些第一波的庫往往伴有隱含的運行時成本。我們需要等到組件被渲染後,再將這些樣式注入到頁面中,這就造成了 JavaScript 包中的樣式問題。從規模上來說,糟糕的性能往往是千夫所指,而我們也注意到了這些成本。這導致 JavaScript 庫中出現了新的 CSS,它通過使用智能預編譯器來提取樣式表,這些庫專注於沒有運行時的開銷。

效率低下的網絡和渲染受阻的組件

當瀏覽器渲染 HTML 時,像 CSS 或腳本這樣的渲染障礙資源會阻止 HTML 的其他部分顯示出來。在一個組件的層次結構中,父組件往往會成爲子組件的渲染障礙。

在實踐中,許多組件依賴於數據庫的數據和 CDN 的代碼(通過代碼分割)。這經常會造成瀑布式的網絡請求阻塞。在渲染之後,組件會獲取數據,解鎖異步子組件。接着,它們將會獲取它們所需的數據,並重復這一過程。經常可以看到 “下拉列表的地獄” 或累積佈局偏移,這些變化是在加載 UI 時出現在屏幕上的。

React 後來發佈了 Suspense,以使頁面的加載階段更加順暢。但是,默認情況下,這並不能防止持續的網絡瀑布問題。Suspense 支持 “在獲取數據時渲染” 的模式。

Facebook 如何解決這些問題

我們將繼續繞行,瞭解 React 的一些權衡如何在規模上得到緩解。這將有助於構建新框架中的模式。

React 中,虛擬 DOM 的運行時成本是無法避免的。併發模式是一個解決問題的方法,它可以讓你在高度互動的體驗中保持對事情做出響應。

JavaScript 中的 CSS 領域,使用了一個名爲 Stylex 的內部庫。當成千上萬的組件被渲染時,這可以維持人性化的開發人員體驗,而無需運行時的成本。

FacebookRelay 來避免順序性的網絡瀑布問題。對於一個給定的入口點,靜態分析可以精確地確定要加載的代碼和數據。這就意味着代碼和數據都可以在一個優化的 graphQL 查詢中並行加載。

這比初始加載和 SPA 轉換的順序網絡瀑布要快得多。

其中一個基本問題就是傳遞 JavaScript,這些 JavaScript 與具體的用戶無關。

如果有 A/B 測試,特性標記的經歷,以及針對特定類型和羣組的用戶的代碼時,那就很困難了。還有語言和地區設置。當代碼有許多分支時,靜態依賴關係圖不能看到在實踐中爲特定用戶羣一起使用的模塊。

Facebook 使用了一個由人工智能驅動的動態包系統。這利用其緊密的客戶 - 服務器集成,在運行時根據請求計算出最佳的依賴圖。這與一個根據優先級分階段加載包的框架相結合。

生態系統的其他部分呢?

Facebook 擁有複雜的基礎設施和多年來構建的內部庫。如果你是一家大型科技公司,你可以投入大量的資金和資源來優化這些大規模的權衡。

這爲前端產品開發人員創造了一個成功的深淵,可以讓他們在完成任務的同時保持性能。

我們中的大多數人都不會像 Facebook 那樣的規模上構建一套應用。然而,對於許多大型企業來說,性能是個話題。我們可以從這些模式中學習,例如:儘可能多地獲取數據,並行化網絡,以及使用內聯需求等等。

大型科技公司經常在內部推出自己的應用框架。在不同的用戶資源庫中,遺留了大量的解決方案。這導致了許多 Javascript 生態系統疲勞和框架倦怠。

JavaScript 的世界:羣龍無首

還跟我們在一起?我們正處於 SPA 的時代。這就是目前從事這一行的人所面臨的現狀。

React 是無可爭議的冠軍,然而,我們看到了大規模的取捨。

React 提供了一個層。它將其他必要的層留給了生態系統,在路由、狀態管理、數據獲取等各個重要方面造成了混亂,每個層都有自己的概念和 API。

不可變與可變,帶有類的 OOP 與函數式的 OOP,爭論和庫都如火如荼。

如今,很多開發人員都被不確定的事情所困擾,他們不知道應該怎麼去做,也不知道該怎麼去構建。

起來,起來,React 替代品!

組件是有黏性的。但運行時成本、Javascript 驅動的 JSX 以及複雜性都有待討論。很多不是來自大型科技公司的草根替代方案,已經獲得了廣泛的認同。讓我們對這些方案做一個總論:

Vue

當人們在評估遷移到 Angular 2React 時,Vue 填補了入門門檻低的空白。你不必爲複雜的 webpack 配置而擔心。你可以從 CDN 上下載並開始使用對許多開發人員來說很直觀的模板來構建組件。

核心團隊可以使用路由和樣式等核心組件,減少決策疲勞。它還通過對模板進行靜態分析,緩解了 React 調和算法的某些方面,以實現優化,加快運行時。這被稱爲編譯器通知的虛擬 DOM。

Svelte

Svelte 開創了預編譯方法的先河,消除了我們在運行時看到的複雜性和開銷。

我們的想法是要有一個可以自行編譯的框架,並簡化輸出最小的普通 JavaScript。所有這些都是基於聲明式組件和熟悉的可變 JavaScript 風格來保持現代的創作體驗。Svelte 完全避免了使用虛擬 DOM,因此不會受到編寫 JavaScript 的不可變風格的約束,這種風格可以用來做更新狀態之類的事情。對於許多人來說,這是一個更簡單、更理智地在 Web 上構建東西的模型。

Solid

Solid 有一個直接的和可預測的反應性模型,其靈感來自 Knockout。像 React 一樣,它也避免了使用模板來簡化函數的可組合性。

React 採取的是不斷重新渲染世界的方法。Solid 只渲染一次,並在不增加虛擬 DOM 開支的情況下,使用精簡的反應性系統進行細粒度的更新。Solid 看起來就像我們許多 React 開發人員想要使用鉤子的新代碼那樣。它的 API 也許更人性化,並且在許多方面非常順利,例如鉤子的依賴數組,其重點是細粒度的反應性和可組合的原語。

交流互鑑

對於每個框架,還有許多可說的。每個人都會在自己的基本模式和喜好上作出不同的權衡。

在現實中,進化往往是由人類的意志決定的。嘗試不同的解決方案來解決當前的痛點,每個框架都從彼此中學習。其中一個重要的主題就是精簡和簡化。把事情從運行時移到編譯時是這些主題之一,它激發了 “React forget”,這是一個有望能夠消除記憶化需求的特性。它們的共同點是解決了文件的交互部分。正如我們所看到的,這是一個具有挑戰性的方面,要以一種容易擴展的方式來解決。

同時,我們看到了純客戶端渲染的權衡。當加載一個頁面時,那個空白的白屏需要更長的時間。在移動設備和網絡上,這真是一場災難。對於很多網站來說,網頁打開速度更快,且性能不降低,成爲一個主要的競爭優勢。

我們邁出了這一步,正在探索通過首先在服務器上渲染內容來加快渲染速度的方法(後來才發現這是一種權衡)。這個最初的倒退引發了許多 “元” 框架和 HTML 優先前端框架的新浪潮。

新一代的 JavaScript Web 框架

我們不會停止探索。我們所有探索的終點就是我們開始的地方。也是第一次知道這個地方。

PHP 的啓發,Next 開始簡化創建靜態頁面推送到 CDN 的過程。它還解決了在 React 應用程序中使用 SSR 的棘手問題。

它還提供了一些關於使用基於文件的路由來構建應用程序的意見,這很受歡迎。還有其他一些不錯的特點。從那時起,又有一波 “元” 框架被創建。對於 Vue,我們在 Nuxt 中有一個類似的框架。SvelteSveltekit,以及即將推出的 SolidStart

這些都是服務器優先,旨在整合 Web 框架的所有部分和人體工程學。這並不僅僅是人們長久以來所關心的互動元素。

對話的出發點是改進用戶的經驗和開發人員的經驗,而非一種交換。

MPA 的反擊

多頁面架構從服務器上提供 HTML,其中導航是全頁面刷新。快速啓動對於很多站點來說都是至關重要的,尤其是那些沒有登錄的站點。它直接關係到諸如搜索排名和跳出率之類的事情。對於許多互動性低的網站和應用程序來說,使用像 React 這樣的客戶端渲染庫,就過於誇張了。

對許多人來說,這意味着翻轉腳本。做到 HTML 優先而不是 Javascript 優先,MPA 優於 SPA,並默認爲零 Javascript

Marko、Astro、Fresh、RocketEnhance 等框架都採用了這種方法。與一些元框架相比,路由器停留在服務器上,而不是讓客戶端的路由器在第一次加載後接管。在 Javascript 生態系統中,這是對 Node.js 之後不久的基於服務器的模板製作的一種倒退。

這一輪的 MPA 與前幾代不同。“Sprinkles” 是在一個基於組件的模型中編寫的,通常使用 island 模式。在前端和後端代碼中使用相同的語言。往往在同一個文件中共存。這就消除了在添加一些交互性時前端和後端構造不同的重複模板代碼的問題。

漸進增強的迴歸

RemixReact 生態系統中帶來了漸進增強的迴歸。

從技術角度來看,RemixReact Router 的編譯器,和其他新興的元框架一樣,是一個邊緣兼容運行時。它通過嵌套佈局和數據獲取 API,解決了 Facebook 通過 Relay 大規模解決的相同挑戰。

這允許早期的代碼和數據的並行獲取。這是用 Suspense 實現 “邊渲染邊獲取” 模式的一個良好前提條件。對漸進增強的強調意味着它的 API 基於 Web 標準,數據變異的故事基於 HTML 表單。

而不是通過連接事件處理程序來進行必要的獲取請求。你渲染表單,將數據提交給在服務器上處理它們的動作函數(通常在同一個文件中)。受到 PHP 的啓發。

Next 類似,應用程序可以縮小規模,像傳統的服務器渲染的 MPA 那樣在沒有 Javascript 的情況下工作,或者按每頁的規模擴展到交互式 React 應用程序。

Remix 還提供了許多 API 和模式,用於處理諸如樂觀的 UI 更新、靜態條件的處理以及優雅的退化之類的事情,這些都是你希望一個專注於終端用戶體驗的深思熟慮的框架所提供的。

混合的未來

不要與 Quic 協議相混淆。Qwik 這個框架是關於儘量減少不必要的 Javascript。雖然它的 API 看起來像 React,但它的方法與其他元框架不同,因爲它專注於水化過程。

就像你可以暫停一臺虛擬機並將其移動到不同的物理機上。Qwik 把這個想法帶到了服務器和瀏覽器之間發生的工作。它的 “可恢復” 水化的想法意味着你可以在服務器上啓動一些東西,然後在客戶端上恢復,而不需要任何重新工作。這與部分水化形成對比,後者在水化工作發生時進行移動,而 Qwik 則試圖在一開始就避免這樣做。

這是一套有趣的想法,它利用了服務器和客戶端緊密結合的力量,允許這種動態捆綁和服務。

這些概念開始模糊了 MPASPA 之間的界限,一個應用程序可以從 MPA 開始,動態地過渡到 SPA。有時(用更流行的話來說)被稱爲 “過渡性應用程序”。

邊緣渲染

同時,後端基礎設施和託管也在不斷改進。CDN 的邊緣使我們的 SPA 的靜態資產服務變得簡單而快速。現在將運行時和數據轉移到邊緣也變得可行了。這是在瀏覽器之外創建一個新的運行時層,但仍然儘可能地接近用戶。這使得將目前在瀏覽器中完成的許多事情移回服務器變得更加容易。同時在一定程度上減輕了這樣做所帶來的網絡延遲的取捨。

React 服務器組件這樣的想法正在探索將服務器組件的輸出從這一層流向瀏覽器的概念。像 Deno 和 Bun 這樣的新的 Javascript 運行時正在出現,以簡化和精簡 Javascript 生態系統,併爲這個邊緣運行時的新世界而構建,爲速度和快速啓動時間而優化。

這也導致了應用框架採用標準的網絡 API 來在這一層運行。隨着無服務器功能和流媒體架構被探索出來。

流(Streaming)是這裏的一個大主題。它允許提前刷新 HTML,因此瀏覽器可以在接收到它時逐步進行渲染。在後端同時獲取任何數據時,開始處理任何阻礙渲染的資源,如 CSS 和 JS。這有助於並行化許多其他順序往返行程。

總結

本文講了那麼多,但實際上只是觸及皮毛而已。對於本文中提到的最佳框架、架構或模式,以及我們沒有提到的無數其它框架、架構和模式,並沒有一個通用的答案。它始終是對特定指標的權衡。而要知道如何權衡,取決於你正在構建的東西、你的用戶是誰、他們的使用模式,以及圍繞關鍵用戶體驗的任何其他要求(如性能預算)的設定。

對於我們中的大多數人來說,真相在某個中間的地方。新一波框架和創新的偉大之處在於,它們提供了根據需要擴大和縮小規模的槓桿。對於那些進入這個行業的人和那些經驗豐富的人來說,投資於基本面總是一個不錯的選擇。

框架的演變慢慢地將原生 Web 推向了更遠的地方,消除了以前對框架的需求,並減輕了之前的取捨,使我們能夠越來越多地採用其原生特性。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/toI8RcNe_2CXum2KiTOF5g