[譯]Vue3的設計過程

今日凌晨三點半左右,尤雨溪在他的微博上發表了一篇文章。
當然大佬是在另一個時區,咱們這的凌晨對應的應該是那個疫情最嚴重的時區的下午。

原文鏈接:https://increment.com/frontend/making-vue-3/

重構新版Vue.js的經驗教訓

在過去的一年中,Vue團隊一直在研究Vue.js的下一個主要版本,我們希望能在2020年的上半年發佈該版本。(在撰寫本文時,這項工作仍在進行中)。 Vue的主要版本於2018年底形成,當時Vue 2的代碼庫已有兩年半的歷史了。在通用軟件的生命週期中聽起來可能並不長,但在此期間,前端環境發生了巨大變化。

有兩個主要的考慮因素使我們開發了Vue的新主要版本(並重寫了它):首先,主流瀏覽器普遍提供了新的JavaScript語言功能。其次,隨着時間的推移,當前代碼庫中的設計和體系結構問題已經逐漸暴露了出來。

爲什麼要重構

利用新的語言功能

隨着ES6的標準化,JavaScript(正式稱爲ECMAScript,縮寫爲ES)獲得了重大改進,主流瀏覽器終於開始爲這些新功能提供不錯的支持。特別是一些新特性爲我們提供了極大提高Vue功能的機會。

其中最值得注意的是Proxy,它允許框架攔截對象上的操作。Vue的核心功能是能夠監聽對用戶定義狀態所做的更改並以數據驅動更新DOM的能力。Vue 2通過使用getter和setter替換狀態對象上的屬性來實現這種能力。切換到代理將使我們消除Vue的現有限制,例如無法檢測到新的屬性添加並提供更好的性能。

但是,代理是語言自身的功能,不能在舊版瀏覽器中完全polyfill。爲了利用它,我們知道我們必須調整框架的瀏覽器支持範圍,這是一個重大突破,只能在新的主要版本中發佈。

解決架構問題

要在當前的代碼庫中解決這些問題,將需要進行大量風險較大的重構,這幾乎等同於完全重寫了一遍 。
在維護Vue 2的過程中,由於現有架構的侷限性,我們積累了許多難以解決的問題。例如,模板編譯器的編寫方式令源映射的支持非常困難。同樣,雖然Vue 2從技術上允許構建針對非DOM平臺的更高級別的渲染器,但我們必須派生代碼庫並複製大量代碼才能實現這一點。要在當前的代碼庫中解決這些問題,將需要進行大量風險較大的重構,這幾乎等同於完全重寫 。

同時,我們以各種模塊的內部與浮動代碼之間隱式耦合的形式積累了隱患,而浮動代碼似乎並不屬於任何地方。這使得孤立地理解代碼庫的一部分變得更加困難,並且我們注意到,貢獻者很少會對重要的更改充滿信心。重構將使我們有機會根據這些注意事項來重新考慮代碼組織。

初始原型階段

我們於2018年底開始對Vue 3進行原型設計,其初步目標是驗證這些問題的解決方案。在此階段,我們主要爲進一步發展奠定堅實的基礎。

切換到TypeScript

Vue 2最初是用純JS編寫的。在原型開發階段之後不久,我們意識到類型系統對於這種規模的項目將非常有幫助。類型檢查極大地減少了在重構期間引入意外錯誤的機會,並有助於貢獻者更自信的進行重要的更改。我們通過Facebook的Flow類型檢查,因爲它可以逐步添加到現有的JS項目。Flow在一定程度上有所幫助,但是我們並沒有從中獲得太多收益。特別是不斷變化的需求使升級變得很痛苦。與TypeScript和Visual Studio Code的深度集成相比,Flow對集成開發環境的支持也不理想。

我們還注意到,用戶越來越多地同時使用Vue和TypeScript。爲了支持它們的用例,我們必須與使用不同類型系統分開創作和維護TypeScript聲明。切換到TypeScript將使我們能夠自動生成聲明文件,從而減輕了維護負擔。

解耦內部封裝

我們還採用了monorepo,其中的框架由內部軟件包組成,每個內部軟件包都具有自己的單獨API,類型定義和測試。我們希望使這些模塊之間的依賴關係更加明確,從而使開發人員更容易閱讀,理解並進行所有更改。這是我們努力降低項目貢獻壁壘並提高其長期可維護性的關鍵。

設置RFC流程

到2018年底,我們有了一個使用新的數據驅動視圖系統和虛擬DOM渲染器的工作原型。我們已經驗證了我們想要進行的內部體系結構改進,但是隻包含了面向公衆的API更改的草案。現在是將它們變成具體設計的時候了。

我們知道我們必須儘早而仔細地做到這一點。Vue的廣泛使用意味着突破性變化可能導致用戶大量遷移成本和潛在的生態系統碎片化。爲了確保用戶能夠提供有關重大更改的反饋,我們在2019年初採用了RFC(徵求意見)流程。每個RFC遵循一個模板,其中側重於動機,設計細節,權衡和採用策略。由於此過程是在GitHub倉庫中進行的,提案是作爲請求請求提交的,因此討論會在註釋中自然展開。

該RFC的過程已經證明了極大的幫助,作爲一個思想框架,它迫使我們要充分考慮潛在變化的方方面面,讓我們的社區參與設計過程,並提交深思熟慮出來的功能要求。

更快更小

性能對於前端框架至關重要。儘管Vue 2具有出色的性能,但通過嘗試新的渲染策略,重構提供了進一步發展的可能。

克服虛擬DOM的瓶頸

Vue有一個相當獨特的呈現策略:它提供類似於HTML的模板語法,但將模板編譯爲可返回虛擬DOM樹的呈現函數。該框架通過遞歸遍歷兩個虛擬DOM樹並比較每個節點上的每個屬性來確定實際DOM的哪些部分需要更新。由於現代JavaScript引擎執行了高級優化,因此這種有點野蠻的算法通常很快,但是仍然涉及許多不必要的CPU工作。當您查看包含大量靜態內容且只有少量動態綁定(整個虛擬DOM)的模板時,效率低下,尤其僅有一點改變卻仍然需要遞歸整個虛擬DOM樹,以瞭解發生了什麼變化。

幸運的是,模板編譯步驟使我們有機會對模板進行靜態分析並提取有關動態零件的信息。Vue 2通過跳過靜態子樹在某種程度上做到了這一點,但是由於過於簡單的編譯器體系結構,難以實施更高級的優化。在Vue 3中,我們使用適當的AST轉換管道重寫了編譯器,這使我們能夠以轉換插件的形式編寫編譯時優化。

有了新的體系結構,我們希望找到一種渲染策略,以儘可能減少開銷。一種選擇是放棄虛擬DOM並直接生成命令式DOM操作,但這將消除直接編寫虛擬DOM渲染功能的能力,我們發現這對高級用戶和庫作者非常有價值。另外,這將是一個巨大的突破性變化。

其次,最好的方法是消除不必要的虛擬DOM樹遍歷和屬性比較,這在更新過程中往往會帶來最大的性能開銷。爲了實現這一點,編譯器和運行時需要協同工作:編譯器分析模板並生成帶有優化提示的代碼,而運行時將拾取提示並在可能的情況下采用快速路徑。這裏有三個主要的優化工作:

首先,在樹的層面上,我們注意到,節點結構在沒有模板指令的時候是完全靜態的(例如,v-if和v-for)。如果我們將模板分爲動態的和靜態的“塊”,每個塊內的節點結構再次變得完全靜態。當我們更新一個塊內的節點時,我們不再需要遞歸遍歷樹,因爲我們可以在平面數組中跟蹤該塊內的動態綁定。通過將我們需要執行的樹遍歷量減少一個數量級,從而節約了虛擬DOM的大部分開銷。

其次,編譯器會主動檢測模板中的靜態節點,子樹甚至數據對象,並將其提升到生成代碼中的render函數之外。這樣可以避免在每個渲染上重新創建這些對象,從而大大提高了內存使用率並減少了垃圾回收的頻率。

第三,在元素級別,編譯器還會根據需要執行的更新類型爲具有動態綁定的每個元素生成一個優化標誌。例如,具有動態類綁定和許多靜態屬性的元素將收到一個標誌,指示僅需要進行類檢查。運行時將獲取這些提示並採用專用的快速路徑。

綜上所述,這些技術已顯著提高了我們的渲染更新,運行Vue 3有時甚至會比Vue 2快上個十倍。

極小的尺寸

框架的大小也會影響其性能。這是Web應用程序的重要關注點,因爲需要動態下載資源,並且在瀏覽器解析必要的JavaScript之前,該應用程序將是交互式的。對於單頁應用程序尤其如此。儘管Vue一直是相對輕量級的(Vue 2的運行時大小壓縮爲23 KB),但我們注意到了兩個問題:

首先,並不是每個人都使用框架的所有功能。例如,從未使用過渡組件的應用仍會下載與過渡相關的代碼和並且花時間去解析它。

其次,當我們添加新功能時,該框架會無限的變大。當我們考慮新功能添加的時候,不得不考慮到尺寸的問題。因此,我們傾向於框架僅包含大多數用戶會使用的功能。

理想情況下,用戶應該能夠在構建時刪除未使用的框架功能的代碼-也稱爲“Tree Shaking” -只打包他們使用的代碼。這也將使我們能夠發佈一部分用戶會覺得有用的功能,而不會增加其餘用戶的有效下載成本。

在Vue 3中,我們通過將大多數全局API和內部幫助程序移至ES模塊導出來實現了這一目標。這使現代打包工具可以靜態分析模塊依賴性並刪除與未使用的導出相關的代碼。模板編譯器還會生成Tree Shaking友好的代碼,如果該功能實際上在模板中使用,則該代碼僅導入該功能的幫助程序。

框架的某些部分永遠不會Tree Shaking,因爲它們對於任何類型的應用程序都是必不可少的。我們將這些必不可少的部分的度量標準稱爲基礎尺寸。儘管增加了許多新功能,但Vue 3的基準尺寸gzip後大約只有10KB ,甚至還不到Vue 2的一半。

滿足規模需求

我們還想提高Vue處理大型應用程序的能力。我們最初的Vue設計着重於溫和的學習曲線。但是隨着Vue越來越廣泛地被採用,我們瞭解了更多有關項目需求的信息,這些項目包含數百個模塊,並且隨着時間的流逝由數十名開發人員維護。對於這些類型的項目,像TypeScript這樣的類型系統以及可複用代碼的能力至關重要,而Vue 2在這些領域的支持並不理想。

在設計Vue 3的早期階段,我們嘗試通過提供對使用類編寫組件的內置支持來改善TypeScript集成。挑戰在於,我們需要使類可用的許多語言功能(如類字段和裝飾器)仍是提案,並且在正式成爲JavaScript一部分之前可能會發生變化。涉及到的複雜性和不確定性使我們懷疑添加Class API是否真的合理,因爲它除了提供更好的TypeScript集成之外沒有提供任何其他功能。

我們決定研究其他解決擴展問題的方法。受到React Hooks的啓發,我們考慮過公開較低級別的數據驅動視圖和組件生命週期API,以實現一種更自由形式的編寫組件邏輯的方式,稱爲Composition API。無需通過指定一長串選項來定義組件,Composition API允許用戶像編寫函數一樣自由地表達,編寫和重用有狀態組件邏輯,同時提供出色的TypeScript支持。

我們對這個想法感到非常興奮。儘管Composition API旨在解決特定類別的問題,但從技術上講,僅在編寫組件時纔可以使用它。在該提案的初稿中,我們暗示我們可能會在將來的版本中將現有的Options API替換爲Composition API。這導致社區成員的大量不滿,這爲我們上了寶貴的一課,可以使他們清楚地傳達長期計劃和意圖,以及瞭解用戶的需求。在聽取了我們社區的反饋之後,我們對提案進行了完全的重新設計,從而明確表明Composition API將是對Options的補充和補充API。收到修改後的提案更加積極,並收到了許多建設性的建議。

尋求平衡

在Vue超過一百萬的開發人員中,有隻會HTML和CSS的基礎知識的初學者,有從jQuery遷移的老程序員,還有從另一個框架遷移的前端,甚至還有正在尋找前端解決方案的後端工程師、以及大規模處理軟件的軟件架構師。一些開發人員可能希望在舊版應用程序上增加交互性,而另一些開發人員可能需要敏捷開發但維護需求有限的一次性項目。在項目的整個生命週期中,可能不得不處理大型的,多年期的項目和一個波動的開發團隊。

在我們尋求平衡各種折衷方案的同時,Vue的設計不斷受到這些需求的影響和啓發。Vue號稱“漸進式框架”,封裝了由此過程產生的分層API設計。初學者可以使用CDN腳本,基於HTML的模板和直觀的Options API來輕鬆學習,而高手可以使用功能齊全的CLI,渲染功能和Composition API來處理更復雜的項目。

要實現我們的願景,還有許多工作要做。最重要的是,更新周邊庫,文檔和工具以確保順利遷移。在接下來的幾個月中,我們將繼續努力,我們已經迫不及待的想看看社區將會通過Vue 3創造些什麼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章