Vue原始碼該如何入手?

陳煜侖發表於2019-04-19

前言

這是一個對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客戶端上。platformVue.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的一些打包思路去理解它,如果對webpacknode包還不太瞭解的同學可以看看我之前寫的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 OnlyRuntime + Compiler

  • Runtime Only

在使用Runtime Only版本的Vue.js的時候,通常需要藉助如webpackvue-loader工具把.vue檔案編譯成JavaScript,因為是在編譯階段做的,所以它只包含執行時的Vue.js程式碼,因此程式碼體積也會更輕量。

  • Runtime + Compiler

如果沒有對程式碼做預編譯,但又使用了Vuetemplate屬性並傳入一個字串,則需要在客戶端編譯模板,所以需要帶有編譯器的版本,即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原始碼該如何入手?

相關參考:Vue.js原始碼全方位深入解析

相關文章