本系列爲慕課網《Vue.js 源碼全方位深刻解析》課程的學習筆記css
Flow 是 facebook 出品的 JavaScript 靜態類型檢查工具。Vue.js 的源碼利用了 Flow 作了靜態類型檢查。vue
JavaScript 是動態類型語言,但因爲是弱類型,每每在編譯階段很難發現這些隱患,在運行的時候出現各類各樣的BUG。
類型檢查是當前動態類型語言的發展趨勢,能夠在編譯期儘早發現(由類型錯誤引發的)bug,越早在上游發現對項目的成本控制越有益。node
Vue.js 在作 2.0 重構的時候,在 ES2015 的基礎上,除了 ESLint 保證代碼風格以外,也引入了 Flow 作靜態類型檢查。之因此選擇 Flow,主要是由於 Babel 和 ESLint 都有對應的 Flow 插件以支持語法,能夠徹底沿用現有的構建配置,很是小成本的改動就能夠擁有靜態類型檢查的能力。webpack
npm install -g flow-bin
一般類型檢查分紅 2 種方式:web
先上代碼vue-cli
/*@flow*/`` function split(str) { return str.split(' ') } split(11)
Flow 檢查上述代碼後會報錯,由於函數 split 期待的參數是字符串,而咱們輸入了數字。npm
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:4:14 Cannot call str.split because property split is missing in Number [1]. 1│ /*@flow*/`` 2│ 3│ function split(str) { 4│ return str.split(' ') 5│ } 6│ [1] 7│ split(11) Found 1 error
類型推斷的狀況下如下代碼並不會報錯json
/*@flow*/ function add(x, y){ return x + y } add('Hello', 11)
這每每就是隱患所在,flow的正確寫法應該是segmentfault
/*@flow*/ function add(x: number, y: number): number { return x + y } add('Hello', 11)
關於flow的具體用法參見官網 flowg官網連接api
Flow 提出了一個 libdef 的概念,能夠用來識別這些第三方庫或者是自定義類型,而 Vue.js 也利用了這一特性。
在 Vue.js 的主目錄下有 .flowconfig 文件, 它是 Flow 的配置文件,這其中的 [libs] 部分用來描述包含指定庫定義的目錄,默認是名爲 flow-typed 的目錄。這裏 [libs] 配置的是 flow,表示指定的庫定義都在 flow 文件夾內。咱們打開這個目錄,會發現文件以下:
flow ├── compiler.js # 編譯相關 ├── component.js # 組件數據結構 ├── global-api.js # Global API 結構 ├── modules.js # 第三方庫定義 ├── options.js # 選項相關 ├── ssr.js # 服務端渲染相關 ├── vnode.js # 虛擬 node 相關
這裏的庫定義對咱們在後面的源碼閱讀時會有必定的幫助。
Vue.js 的源碼都在 src 目錄下,其目錄結構以下
src ├── compiler # 編譯相關 ├── core # 核心代碼 ├── platforms # 不一樣平臺的支持 ├── server # 服務端渲染 ├── sfc # .vue 文件解析 ├── shared # 共享代碼
compiler 目錄包含 Vue.js 全部編譯相關的代碼。它包括把模板解析成 ast 語法樹,ast 語法樹優化,代碼生成等功能。
編譯的工做能夠在構建時作(藉助 webpack、vue-loader 等輔助插件);也能夠在運行時作,使用包含構建功能的 Vue.js。咱們每每在開發的時候使用運行時編譯,離線每每是用於發佈。
關於ast語法樹的介紹可見 一看就懂的JS抽象語法樹
core 目錄包含了 Vue.js 的核心代碼,包括內置組件、全局 API 封裝,Vue 實例化、觀察者、虛擬 DOM、工具函數等等。
Vue.js 是一個跨平臺的 MVVM 框架,它能夠跑在 web 上,也能夠配合 weex 跑在 natvie 客戶端上。platform 是 Vue.js 的入口,2 個目錄表明 2 個主要入口,分別打包成運行在 web 上和 weex 上的 Vue.js。
server
Vue.js 2.0 支持了服務端渲染,全部服務端渲染相關的邏輯都在這個目錄下。
服務端渲染主要的工做是把組件渲染爲服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最後將靜態標記"混合"爲客戶端上徹底交互的應用程序。
一般咱們開發 Vue.js 都會藉助 webpack 構建, 而後經過 .vue 單文件的編寫組件。
這個目錄下的代碼邏輯會把 .vue 文件內容解析成一個 JavaScript 的對象。
Vue.js 會定義一些工具方法,這裏定義的工具方法都是會被瀏覽器端的 Vue.js 和服務端的 Vue.js 所共享的。
Vue.js 源碼是基於 Rollup 構建的,它的構建相關配置都在 scripts 目錄下。
ps:roolup 每每用於純js的庫的構建,webpack功能更爲強大一些,能夠把css,圖片這些也一塊兒打到包裏。這裏Roolup顯然更適合一點。
一般一個基於 NPM 託管的項目都會有一個 package.json 文件,它是對項目的描述文件,它的內容其實是一個標準的 JSON 對象。
咱們一般會配置 script 字段做爲 NPM 的執行腳本,Vue.js 源碼構建的腳本以下:
{ "script": { "build": "node scripts/build.js", "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", "build:weex": "npm run build --weex" } }
當在命令行運行 npm run build 的時候,實際上就會執行 node scripts/build.js
咱們對於構建過程分析是基於源碼的,先打開構建的入口 JS 文件,在 scripts/build.js 中:
let builds = require('./config').getAllBuilds() // filter builds via command line arg if (process.argv[2]) { const filters = process.argv[2].split(',') builds = builds.filter(b => { return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1) }) } else { // filter out weex builds by default builds = builds.filter(b => { return b.output.file.indexOf('weex') === -1 }) } build(builds)
這段代碼邏輯很是簡單,先從配置文件讀取配置,再經過命令行參數對構建配置作過濾,這樣就能夠構建出不一樣用途的 Vue.js 了。接下來咱們看一下配置文件,在 scripts/config.js 中
const builds = { // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.common.js'), format: 'cjs', banner }, //... // Runtime+compiler development build (Browser) 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner }, // Runtime+compiler production build (Browser) 'web-full-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.min.js'), format: 'umd', env: 'production', alias: { he: './entity-decoder' }, banner }, // ... }
對於單個配置,它是遵循 Rollup 的構建規則的。其中 entry 屬性表示構建的入口 JS 文件地址,dest 屬性表示構建後的 JS 文件地址。format 屬性表示構建的格式,cjs 表示構建出來的文件遵循 CommonJS 規範,es 表示構建出來的文件遵循 ES Module 規範。 umd 表示構建出來的文件遵循 UMD 規範。
ps: cjs,es,umd是什麼
以上面的web-runtime-cjs 配置爲例,它的 entry 是
resolve('web/entry-runtime.js'),先來看一下 resolve 函數的定義。
源碼目錄:scripts/config.js
const aliases = require('./alias') const resolve = p => { const base = p.split('/')[0] if (aliases[base]) { return path.resolve(aliases[base], p.slice(base.length + 1)) } else { return path.resolve(__dirname, '../', p) } }
11
這裏的 resolve 函數實現很是簡單,它先把 resolve 函數傳入的參數 p 經過 / 作了分割成數組,而後取數組第一個元素設置爲 base。在咱們這個例子中,參數 p 是 web/entry-runtime.js,那麼 base 則爲 web。base 並非實際的路徑,它的真實路徑藉助了別名的配置,咱們來看一下別名配置的代碼,
在 scripts/alias 中:
const path = require('path')
module.exports = { vue: path.resolve(__dirname, '../src/platforms/web/entry-runtime-with-compiler'), compiler: path.resolve(__dirname, '../src/compiler'), core: path.resolve(__dirname, '../src/core'), shared: path.resolve(__dirname, '../src/shared'), web: path.resolve(__dirname, '../src/platforms/web'), weex: path.resolve(__dirname, '../src/platforms/weex'), server: path.resolve(__dirname, '../src/server'), entries: path.resolve(__dirname, '../src/entries'), sfc: path.resolve(__dirname, '../src/sfc') }
這裏 web 對應的真實的路徑是 path.resolve(__dirname, '../src/platforms/web'),這個路徑就找到了 Vue.js 源碼的 web 目錄。而後 resolve 函數經過 path.resolve(aliases[base], p.slice(base.length + 1)) 找到了最終路徑,它就是 Vue.js 源碼 web 目錄下的 entry-runtime.js。所以,web-runtime-cjs 配置對應的入口文件就找到了。
它通過 Rollup 的構建打包後,最終會在 dist 目錄下生成 vue.runtime.common.js。
一般咱們利用 vue-cli 去初始化咱們的 Vue.js 項目的時候會詢問咱們用 Runtime Only 版本的仍是 Runtime+Compiler 版本。下面咱們來對比這兩個版本。
• Runtime Only
咱們在使用 Runtime Only 版本的 Vue.js 的時候,一般須要藉助如 webpack 的 vue-loader 工具把 .vue 文件編譯成 JavaScript,由於是在編譯階段作的,因此它只包含運行時的 Vue.js 代碼,所以代碼體積也會更輕量。
• Runtime+Compiler
咱們若是沒有對代碼作預編譯,但又使用了 Vue 的 template 屬性並傳入一個字符串,則須要在客戶端編譯模板,以下所示:
// 須要編譯器的版本
new Vue({ template: '<div>{{ hi }}</div>' }) // 這種狀況不須要 new Vue({ render (h) { return h('div', this.hi) } })
由於在 Vue.js 2.0 中,最終渲染都是經過 render 函數,若是寫 template 屬性,則須要編譯成 render 函數,那麼這個編譯過程會發生運行時,因此須要帶有編譯器的版本。很顯然,這個編譯過程對性能會有必定損耗,因此一般咱們更推薦使用 Runtime-Only 的 Vue.js。