在過去的一年裏,Vue 團隊一直在研究 Vue.js 的下一個主要版本,咱們但願在 2020 年上半年發佈。這項工做在撰寫本文時還在進行中),關於 Vue 新的主要版本的想法是在 2018 年末造成的,當時 Vue 2 的代碼庫大約有兩年半的時間。在通用軟件的生命週期中,這聽起來可能並不長,但在這段時間內,前端的格局發生了翻天覆地的變化。vue
有兩個關鍵的考慮因素促使咱們對 Vue 進行了新的主要版本(和重寫)。第一,主流瀏覽器中新的 JavaScript 語言功能的廣泛存在。第二,當前代碼庫中暴露出來的設計和架構問題。算法
隨着 ES2015 的標準化,JavaScript(即 ECMAScript,縮寫爲 ES 的縮寫)獲得了重大改進,主流瀏覽器終於開始爲這些新增長的功能提供了像樣的支持。特別是一些新添加的功能爲咱們提供了極大的機會,使 Vue 的能力獲得了極大的提高。typescript
其中最值得一提的是 Proxy,它容許框架對對象進行攔截操做。Vue 的一個核心特性是可以監聽用戶定義狀態的變化,並對 DOM 進行反應式更新。Vue 2 經過用 getter 和 setter 替換狀態對象上的屬性,實現了這種反應性。切換到 Proxy 可讓咱們消除 Vue 現有的限制,好比沒法檢測到新的屬性添加,並提供更好的性能。後端
然而,Proxy 是一個原生語言的功能,在傳統的瀏覽器中沒法徹底複用。爲了利用它,咱們知道咱們必須調整框架的瀏覽器支持範圍--這是一個重大的突破性改變,只能在新的主要版本中提供。數組
在現有的代碼庫中修復這些問題,須要進行巨大的、危險的重構,幾乎至關於重寫。
在維護 Vue 2 的過程當中,因爲現有架構的限制,咱們已經積累了不少問題,這些問題很難解決。例如,模板編譯器的編寫方式使得適當的源碼映射支持變得很是具備挑戰性。此外,雖然 Vue 2 在技術上能夠構建針對非 DOM 平臺的更高級別的渲染器,但咱們不得不對代碼庫進行分叉,並重復了大量的代碼來實現這一點。在當前的代碼庫中修復這些問題,將須要進行巨大的、危險的重構,幾乎至關於重寫。瀏覽器
同時,咱們還以各類模塊內部的隱式耦合形式積累了技術債務,以及彷佛不屬於任何地方的浮動代碼。這使得咱們很難孤立地理解代碼庫中的某一部分,並且咱們注意到,貢獻者不多有信心進行一些可有可無的修改。重寫將給咱們提供了一個機會,讓咱們在考慮到這些問題的狀況下從新思考代碼組織。前端框架
咱們在 2018 年末開始了 Vue 3 的原型開發,初步目標是驗證這些問題的解決方案。在這個階段,咱們主要集中在爲進一步開發打下堅實的基礎上。架構
Vue 2 最初是用普通的 ES 編寫的。在原型設計階段後不久,咱們意識到類型系統對於這樣的項目來講是很是有幫助的。類型檢查大大減小了在重構過程當中引入意外 bug 的機會,並幫助貢獻者更有信心地進行簡單的修改。咱們採用了 Facebook 的 Flow 類型檢查器,由於它能夠逐步添加到現有的純 ES 項目中。Flow 在必定程度上起到了必定的幫助,但咱們並無如願以償地受益;特別是,不斷的破壞性修改讓升級成爲一種痛苦。與 TypeScript 與 Visual Studio Code 的深度集成相比,對集成開發環境的支持也並不理想。app
咱們還注意到,用戶愈來愈多地將 Vue 和 TypeScript 一塊兒使用。爲了支持他們的用例,咱們不得不將 TypeScript 聲明與源代碼分開編寫和維護,而源代碼使用的是不一樣的類型系統。轉換到 TypeScript 將使咱們可以自動生成聲明文件,減輕了維護負擔。
咱們還採用了一個單體化的設置,框架由內部包組成,每一個包都有本身的 API、類型定義和測試。咱們但願讓這些模塊之間的依賴關係更加明確,讓開發者更容易閱讀、理解和修改。這對於咱們努力下降項目的貢獻障礙和提升項目的長期可維護性是很是關鍵的。
到 2018 年年末,咱們已經有了一個工做原型,有了新的反應式系統和虛擬 DOM 渲染器。咱們已經驗證了咱們想作的內部架構改進,但只有面向公衆的 API 改動的粗略草稿。如今是時候把它們變成具體的設計了。
咱們知道咱們必須儘早、謹慎地完成這項工做。Vue 的普遍使用意味着破壞性的改變可能會致使用戶的大量遷移成本和潛在的生態系統碎片化。爲了確保用戶可以提供對打破性改動的反饋,咱們在 2019 年初採用了 RFC(徵求意見)流程。每一個 RFC 都遵循一個模板,其中的章節集中在動機、設計細節、權衡和採用策略等方面。因爲該流程是在 GitHub repo 中進行的,建議以拉動請求的形式提交,所以討論在評論中有機地展開。
事實證實,RFC 流程很是有幫助,它做爲一個思想框架,迫使咱們充分考慮到了潛在變革的全部方面,並容許咱們的社區參與到設計過程當中,提交深思熟慮的功能請求。
性能對於前端框架來講是相當重要的。儘管 Vue 2 擁有極具競爭力的性能,但經過實驗新的渲染策略,重寫提供了一個更進一步的機會。
Vue 有一個至關獨特的渲染策略。它提供了一個相似於 HTML 的模板語法,但將模板編譯成了返回虛擬 DOM 樹的渲染函數。該框架經過遞歸地走過兩個虛擬 DOM 樹,並比較每一個節點上的每個屬性,計算出實際 DOM 的哪些部分須要更新。因爲現代 JavaScript 引擎進行了高級優化,這種有點蠻力的算法通常來講是至關快的,可是更新仍然會涉及到不少沒必要要的 CPU 工做。當你看一個基本上是靜態內容和只有幾個動態綁定的模板時,效率低下的問題就特別明顯--整個虛擬 DOM 樹仍然須要遞歸地走一遍,以找出改變了什麼。
幸運的是,模板編譯步驟讓咱們有機會對模板進行靜態分析並提取動態部分的信息。Vue 2 經過跳過靜態子樹在必定程度上作到了這一點,但因爲編譯器架構過於簡單化,更高級的優化很難實現。在 Vue 3 中,咱們用適當的 AST transform pipeline 重寫了編譯器,這使得咱們能夠用變換插件的形式來進行編譯時的優化。
有了新的架構,咱們但願找到一種可以儘量消除開銷的渲染策略。一個選擇是拋棄虛擬 DOM,直接生成必要的 DOM 操做,但這將消除直接編寫虛擬 DOM 渲染函數的能力,咱們發現這對高級用戶和庫做者來講是很是有價值的。另外,這將是一個巨大的突破性改變。
接下來最好的辦法就是去掉沒必要要的虛擬 DOM 樹遍歷和屬性比較,這些每每在更新過程當中的性能開銷最大。爲了實現這個目標,編譯器和運行時須要協同工做。編譯器分析模板,並生成帶有優化提示的代碼,而運行時則接收這些提示並儘量地採起快速路徑。這裏有三個主要的優化工做。
首先,在樹級,咱們注意到,在沒有模板指令動態改變節點結構的狀況下,節點結構保持徹底靜態(例如,v-if 和 v-for)。若是咱們把一個模板劃分紅由這些結構指令分隔的嵌套 "塊",那麼每一個塊內的節點結構又變得徹底靜態。當咱們更新塊內的節點時,咱們再也不須要遞歸地遍歷塊內的樹形動態綁定,能夠在一個平面數組中跟蹤。這種優化避免了虛擬 DOM 的大部分開銷,減小了咱們須要執行的樹狀遍歷量,減小了一個數量級。
其次,編譯器會主動檢測模板中的靜態節點、子樹,甚至是數據對象,並在生成的代碼中把它們掛在渲染函數以外。這就避免了在每次渲染時從新建立這些對象,極大地提升了內存使用量,減小了垃圾回收的頻率。
第三,在元素層面,編譯器還會根據每一個具備動態綁定的元素須要執行的更新類型,爲其生成一個優化標誌。例如,一個具備動態類綁定和多個靜態屬性的元素將收到一個標誌,提示只須要進行類檢查。運行時將接收到這些提示並採起專用的快速路徑。
CPU 時間 即執行 JavaScript 計算所花費的時間,不包括瀏覽器 DOM 操做。
綜合起來,這些技術大大改善了咱們的渲染更新基準,Vue 3 有時只須要不到 Vue 2 的十分之一的 CPU 時間。
框架的大小也會影響到它的性能。這對於 Web 應用來講是一個獨特的問題,由於資產須要實時下載,在瀏覽器解析了必要的 JavaScript 以後,應用程序纔會進行交互。這對於單頁面應用來講尤爲如此。雖然 Vue 一直以來都是相對輕量級的--Vue 2 的運行時大小約爲 23 KB gzipped,但咱們注意到了兩個問題。
首先,不是每一個人都會使用該框架的全部功能。例如,一個從未使用過過渡功能的應用仍然要支付過渡相關代碼的下載和解析成本。
第二,隨着咱們添加新的功能,框架不斷地無限增加。當咱們考慮增長新功能的權衡時,這就賦予了捆綁大小不成比例的權重。所以,咱們傾向於只包含大多數用戶會使用的功能。
理想的狀況下,用戶應該可以在構建的時候,爲未使用的框架功能丟棄代碼,也就是所謂的 "動搖樹"--只爲他們使用的功能付費。這也可讓咱們在不增長其餘用戶的付費成本的狀況下,爲一部分用戶提供有用的功能。
在 Vue 3 中,咱們經過將大部分的全局 API 和內部幫助器轉移到 ES 模塊導出中來實現了這一點。這使得現代捆綁器可以靜態分析模塊的依賴性,並丟棄與未使用的導出相關的代碼。模板編譯器還生成了樹狀的友好代碼,只有在模板中實際使用某個功能的時候纔會導入幫助器。
框架中的一些部分永遠不能被樹形動搖,由於它們對於任何類型的 app 都是必不可少的。咱們把這些必不可少的部分的衡量標準稱爲基線大小。Vue 3 的基線大小約爲 10KB 左右,儘管增長了許多新功能,但它的基線大小還不到 Vue 2 的一半。
咱們還但願提升 Vue 的處理大規模應用的能力。咱們最初的 Vue 設計的重點是低准入門檻和溫和的學習曲線。但隨着 Vue 的應用愈來愈普遍,咱們更多的瞭解到了包含數百個模塊的項目的需求,而且由幾十個開發人員長期維護的項目。對於這些類型的項目來講,像 TypeScript 這樣的類型系統和乾淨利落地組織可重用代碼的能力是相當重要的,而 Vue 2 在這些方面的支持並不理想。
在設計 Vue 3 的早期階段,咱們試圖經過提供對使用類編寫組件的內置支持來改進 TypeScript 的集成。咱們所面臨的挑戰是,咱們須要的許多語言特性,如類字段和裝飾符等,在正式成爲 JavaScript 的一部分以前,仍然是建議--並且可能會發生變化。所涉及的複雜性和不肯定性讓咱們懷疑增長類 API 是否真的合理,由於它除了稍微好一點的 TypeScript 集成以外,並無提供其餘任何東西。
咱們決定研究其餘方式來攻擊縮放問題。受 React Hooks 的啓發,咱們想到了暴露出底層的反應性和組件生命週期 API,以實現一種更自由的方式來編寫組件邏輯,稱爲 Composition API。與其經過指定一長串選項來定義一個組件,Composition API 容許用戶像寫函數同樣自由地表達、編譯和重用有狀態的組件邏輯,同時提供了出色的 TypeScript 支持。
咱們對這個想法很是興奮。雖然 Composition API 是爲了解決特定類別的問題而設計的,但在技術上,只有在編寫組件時纔可使用它。在提案的第一稿中,咱們有點超前了,並暗示咱們可能會在將來的版本中用 Composition API 替換現有的 Options API。這致使了社區成員的巨大反擊,這給咱們上了寶貴的一課,讓咱們明白瞭如何清晰地傳達長期計劃和意圖,以及瞭解用戶的需求。在聽取了社區成員的反饋後,咱們對提案進行了完全的修改,明確了 Composition API 將是對 Options API 的補充和補充。修改後的提案獲得了更多的好評,並收到了不少建設性的建議。
開發者簡介的多樣性與用例的多樣性相對應。
在 Vue 的用戶羣中,超過 100 萬的開發者中,有隻懂 HTML/CSS 的初學者,也有從 jQuery 遷移過來的專業人士,有從其餘框架遷移過來的老手,有尋找前端解決方案的後端工程師,也有處理規模化軟件的軟件架構師。開發者配置文件的多樣性與用例的多樣性相對應。一些開發人員可能但願在傳統的應用程序上灑上交互性,而另外一些開發人員多是一次性的項目,週轉速度快,但維護問題有限;架構師可能須要處理大型的、多年期的項目,而且在項目的生命週期內,開發人員的團隊起伏不定。
Vue 的設計一直在不斷地塑造和了解這些需求,在各類權衡中尋求平衡。Vue 的口號是 "漸進式框架",這句話歸納了這個過程當中產生的分層 API 設計。初學者能夠經過 CDN 腳本、基於 HTML 的模板和直觀的 Options API 享受平穩的學習曲線,而專家們能夠經過全功能的CLI、渲染函數和 Composition API 來處理宏大的用例。
爲了實現咱們的願景,還有不少工做要作--最重要的是,更新支持庫、文檔和工具以確保順利遷移。咱們將在將來的幾個月裏努力工做,咱們火燒眉毛地想看看社區將用 Vue 3 創造出什麼。