前言
這是一個對Vue.js
原始碼解析的系列,會持續更新,歡迎關注;話不多說,下面我們就從怎麼讀Vue.js
原始碼開始。
一. 原始碼目錄
首先我們先看看Vue.js
原始碼的專案結構:Vue.js原始碼GitHub
我們先了解一下src
這個目錄的各模組分工:
src
├── compiler # 編譯相關
├── core # 核心程式碼
├── platforms # 不同平臺的支援
├── server # 服務端渲染
├── sfc # .vue 檔案解析
├── shared # 共享程式碼
複製程式碼
1. compiler
compiler
模組包含Vue.js
了所有編譯相關的程式碼。它包括把模板解析成AST
語法樹,AST
語法樹優化,程式碼生成等功能。
2. core
core
目錄包含了Vue.js
的核心程式碼,包括內建元件、全域性 API 封裝,Vue 例項化、觀察者、虛擬 DOM、工具函式等。
3. platform
Vue.js
是一個跨平臺的MVVM
框架,它可以跑在 web
上,也可以配合weex
跑在native
客戶端上。platform
是Vue.js
的入口,會分別打包成執行在 web
上和weex
上的Vue.js
。
4. server
這是與服務端渲染相關的部分,這部分程式碼是跑在服務端的Node.js
。
5. sfc
通常我們開發Vue.js
都會藉助webpack
構建,然後通過.vue
單檔案來編寫元件,這個模組的功能就是將.vue
檔案內容解析成一個 JavaScript
的物件。
6. shared
這裡是Vue.js
定義的一些共享工具方法,會供以上模組所共享。
大概瞭解了以上模組功能後,我們就知道了對於web
端的原始碼,我們主要分析的就是core
模組。
二. 原始碼的構建入口
想想平常我們使用vue
的時候是通過npm
來安裝使用的,那說明Vue.js
其實就是一個
node
包,但它是基於Rollup
構建的,但我們也可以用webpack
的一些打包思路去理解它,如果對webpack
和node
包還不太瞭解的同學可以看看我之前寫的webpack4.x最詳細入門講解和不會發布node包?進來看看
簡單理解的話就是Vue.js
通過構建工具將其打包,這個包會匯出一個Vue
建構函式供我們使用。
所以我們從Vue.js
原始碼中的package.json
檔案入手,因為其包含了打包的一些配置記錄,主要了解兩個地方:
"module": "dist/vue.runtime.esm.js"
這個配置可以理解為出口或者入口,理解為出口時就是指它會匯出一個Vue
建構函式供我們使用;理解為入口的話就從這個vue.runtime.esm.js
開始整合Vue.js
做需要的程式碼。
"scripts": {"build": "node scripts/build.js"}
可以理解為打包入口,會通過build.js
找到Vue.js
所需要的依賴程式碼,然後對其進行打包,所有我們可以從這裡入手,去scripts/build.js
路徑下的build.js
檔案中看看:
// scripts/build.js
let builds = require('./config').getAllBuilds()
...
build(builds)
複製程式碼
// 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 CommonJS build (CommonJS)
'web-full-cjs': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.js'),
format: 'cjs',
alias: { he: './entity-decoder' },
banner
},
// Runtime only (ES Modules). Used by bundlers that support ES Modules,
// e.g. Rollup & Webpack 2
'web-runtime-esm': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.esm.js'),
format: 'es',
banner
},
// Runtime+compiler CommonJS build (ES Modules)
'web-full-esm': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.js'),
format: 'es',
alias: { he: './entity-decoder' },
banner
},
// runtime-only build (Browser)
'web-runtime-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.js'),
format: 'umd',
env: 'development',
banner
},
// ...
}
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
複製程式碼
以上邏輯其實就是從配置檔案config.js
中讀取配置,再對構建配置做過濾,進而根據不同配置構建出不同用途的Vue.js
,其中就有web
使用的兩個版本:Runtime Only
和 Runtime + Compiler
:
Runtime Only
在使用Runtime Only
版本的Vue.js
的時候,通常需要藉助如webpack
的vue-loader
工具把.vue
檔案編譯成JavaScript
,因為是在編譯階段做的,所以它只包含執行時的Vue.js
程式碼,因此程式碼體積也會更輕量。
Runtime + Compiler
如果沒有對程式碼做預編譯,但又使用了Vue
的template
屬性並傳入一個字串,則需要在客戶端編譯模板,所以需要帶有編譯器的版本,即Runtime + Compiler
。
因為後續系列我們會將到編譯模組,所有我們就從帶編譯器的版本入手,即以上入口是entry: resolve('web/entry-runtime-with-compiler.js'),
的檔案,根據原始碼的路徑解析我們得到最終的檔案路徑是src/platforms/web/entry-runtime-with-compiler.js
。
// src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'
// ...
export default Vue
複製程式碼
可以看到這個檔案不是定義Vue
建構函式的地方,也是從其他檔案引入,然後再加工匯出,那我們從./runtime/index
這個檔案繼續找:
// src/platforms/web/runtime/index
import Vue from 'core/index'
// ...
export default Vue
複製程式碼
依舊如此……,繼續往上找,最終經過幾次查詢,在src/core/instance/index.js
中可以看到Vue
的真身:
// src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
// 判斷是否是開發環境且必須是new呼叫
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// new一個例項時會呼叫_init方法,該方法在下面的initMixin(Vue)中有定義
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
複製程式碼
所以到此我們終於看到了Vue
的廬山真面目,可以看到Vue
是一個建構函式,且經過了一系列的Mixin
,進而在Vue
的原型上擴充方法。
最後
通過以上梳理,我們大概瞭解到了我們平時使用的Vue
是怎麼來的,後續系列會繼續對原始碼進行梳理,如果對你的有幫助的話歡迎來波關注!
相關參考:Vue.js原始碼全方位深入解析