Angular 是 Google 親兒子,React 是 Facebook 小正太,那咱為啥偏偏選擇了 Vue 下手,一句話,Vue 是咱見過的最對脾氣的 MVVM 框架。之前也使用過 knockout,angular,react 這些框架,但都沒有讓咱產生 follow 的沖動。直到見到 Vue,簡直是一見鍾情啊。
Vue 的官方文檔已經對 Vue 如何使用提供了最好的教程,建議 Vue 新手直接去官網學習,而不要在網上找些質量參差不齊的文檔去看,以免誤人子弟。中文版 和 英文版 文檔寫的都很地道,畢竟是國產,中文文檔真心贊。不誇張地說,中文文檔咱已經看了不下 5 遍了,每次都有收獲,第一遍看的很慢,邊看邊做,之后就非常快了,主要都是掃讀。英文的咱也不是看不了,只是速度問題,沒必要較這個勁。值得一提的是,Vue 每次版本更新官方文檔迅速都會跟進到最新,所以說 Vue 的官方文檔是學習 Vue 的不二之選。
本系列的目的不是介紹如何使用 Vue,而是希望把 Vue 的源碼實現思路簡單清晰地描繪出來,從而摸清一個 MVVM 框架是如何工作的,並從中學習封裝輪子(庫或框架)的各種實用技巧。文章中的不足和欠缺之處,請大家多多指教/抱拳。
Vue 的源碼目錄結構
如果直接去看 Vue 生成的代碼文件 Vue.js,代碼足足有上萬行。這樣去研究源碼肯定是行不通的,也是不明智的。從 Vue 項目的目錄結構入手是個不錯的選擇,這么大的項目,一個好的目錄結構對開發和維護的重要性不言而喻。
Vue 源碼目錄如下:
Vue 源碼目錄
各目錄和文件功能:
- compiler
此目錄存放編譯模板相關的代碼,用於將 html 模板編譯成 Vue 的 render 函數。 - runtime
此目錄存放 Vue 生命周期內使用的相關代碼,負責 Vue 實例的創建,視圖渲染和處理虛擬 DOM 等等一切編譯 html 模板之外的事情。 - server
這里存放 Vue 服務端渲染(SSR)需要使用的代碼。 - util
這里存放一些其他模塊共用的工具函數。 - entry-compiler.js
該文件是一個提供編譯 html 模板相關接口的模塊,通常用於為 Vue 編寫的構建插件,比如vue-loader
或vueify
。 - entry-runtime.js
用於構建僅包含運行時的文件,不具備編譯 html 模板功能。 - entry-runtime-with-compiler.js
用於構建同時包含編譯器和運行時的全功能文件。 - entry-server-basic-renderer.js 和 entry-server-renderer.js
用於構建服務端渲染可用的文件。
可見 Vue 按照功能塊對整個項目進行了目錄拆分,每個目錄負責一塊功能,接下來就可以從這些 entry 文件入手按照 模塊依賴 進行由淺及深,從整體到局部的深入剖析。
在繼續之前咱們可以先看一下由上面這些源代碼構建出來的可用文件是怎么樣的。
Vue 構建生成的目錄如下:
Vue 構建生成的目錄
各文件功能:
UMD | CommonJS | ES Module | |
---|---|---|---|
Full | vue.js | vue.common.js | vue.esm.js |
Runtime-only | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js |
表格中的術語解釋:
- Full:包含編譯器和運行時的全部功能。
- Runtime-only:僅包含運行時。
- UMD:可通過
<script>
標簽引入直接在瀏覽器中使用,Vue 會暴露一個全局變量window.Vue
。同時適配require.js
這種 AMD 系統的使用。 - CommonJS:適配
const Vue = require('vue')
這種 node 式的模塊系統。 - ES Module:適配
import Vue from 'vue'
這種 es6 提供的模塊系統。
這些被構建出來的文件才是咱們在實際項目中可以直接使用的文件。
是用 Full 還是用 Runtime-only ?
這個需要具體情況具體分析。如果你需要使用 Vue 提供的 html 模板功能,那么就使用 Full 版本。否則,最好用 Runtime-only 版本,因為它比 Full 版本的文件體積會小上 30% 左右。值得注意的是,*.vue
單文件組件會被 vue-loader 或 vueify 直接構建成 JavaScript,並沒有使用到 Vue 的編譯器,因此可使用 Runtime-only 版本。
預備知識
# Flow
Vue 使用了 Facebook 出品的 flow.js 作為靜態類型檢查工具,所以特意跑到 flow 的官網了解了一下(不然看到一些奇怪的寫法會一臉懵逼),發現其實很簡單,就是在變量或函數簽名的地方加上一些類型注解,而且都是可選項,加不加都行,主要是為了方便開發維護用的。
為什么選擇 FLow 而不是 TypeScript ?
Vue 是想找一個靜態類型檢查工具以便提高項目的開發效率和可維護性。Flow 和 TypeScript 都提供了靜態類型檢查功能,但 TypeScript 提供了更多的有用功能,靜態類型檢查只是 TypeScript 提供的諸多強大功能里並不起眼的一個。而 Flow 則不一樣,靜態類型檢查幾乎是它的全部,可以說是典型小而美的實現。可能是本着殺雞焉用牛刀,盡可能降低項目復雜度的想法,Vue 選擇了 Flow 而不是 TypeScript。無獨有偶,Vue 的構建工具選擇了 rollup 而不是 webpack,原因應該也是如出一轍。所以說最強的不一定是最好的,最合適的才是最好的。
# ES6
Vue 的源碼完全使用 ES6 編寫,使得代碼更清晰,更易維護。本系列的所有代碼片段亦全部使用 ES6,不熟悉 ES6 的同學是時候加強學習了,這可是 JavaScript 的未來。如果想系統了解一下 ES6 的話推薦阮一峰老師的 ECMAScript 6 入門 教程。
# Rollup
Vue 使用了 Rollup 作為最后的打包工具。並且使用了 Rollup 的 rollup-plugin-alias 插件,該插件可以為目錄取一個別名,使得在編寫 ES6 代碼 import 模塊時可以使用更短的路徑,而不用每次都小心翼翼地去拼相對路徑,非常方便。如果不了解這一點,在看到 import config from 'core/config'
這種語句時可能會很迷惑,同級目錄下並沒有 core
目錄啊,實際上 core
是 Vue 為其他目錄配置的別名。這個映射表可以在 build/alias.js
文件中找到,映射表中有一條 core: resolve('src/core')
,resolve('src/core')
會解析出 core
目錄的絕對路徑,這其實就是告訴 rollup 在解析 import config from 'core/config'
時從這個絕對目錄中去加載 config.js
。
先撿軟的捏
從源碼的目錄結構可以看出,Vue 的 runtime 模塊負責的事情很多,代碼量必然也很大,應該是塊難啃的骨頭。而 compiler 則只負責將 html 模板轉換為 Vue 的 render 函數,這一塊應該是水很淺的,因此從這塊入手先吃掉它一部分。
本篇完,將在下篇開始研究 Vue 的編譯器模塊。
本系列會以每周一篇的速度持續更新,喜歡的小伙伴記得點關注哦。