Vue源碼: 構造函數入口

Vue架構設計

屏幕快照 2019-05-02 下午7 13 45

Vue目錄設計

├── scripts ------------------------------- 構建相關的腳本/配置文件
│   ├── git-hooks ------------------------- 存放git鉤子的目錄
│   ├── alias.js -------------------------- 別名配置
│   ├── config.js ------------------------- 生成rollup配置的文件
│   ├── build.js -------------------------- 對 config.js 中全部的rollup配置進行構建
│   ├── ci.sh ----------------------------- 持續集成運行的腳本
│   ├── release.sh ------------------------ 用於自動發佈新版本的腳本
├── dist ---------------------------------- 構建後文件的輸出目錄
├── examples ------------------------------ 存放一些使用Vue開發的應用案例
├── flow ---------------------------------- 類型聲明,使用開源項目 [Flow](https://flowtype.org/)
├── packages ------------------------------ 存放獨立發佈的包的目錄
├── test ---------------------------------- 包含全部測試文件
├── src ----------------------------------- 源碼
│   ├── compiler -------------------------- 編譯器代碼的存放目錄,將 template 編譯爲 render 函數
│   ├── core ------------------------------ 存放通用的,與平臺無關的代碼
│   │   ├── observer ---------------------- 響應系統,包含數據觀測的核心代碼
│   │   ├── vdom -------------------------- 包含虛擬DOM建立(creation)和打補丁(patching)的代碼
│   │   ├── instance ---------------------- 包含Vue構造函數設計相關的代碼
│   │   ├── global-api -------------------- 包含給Vue構造函數掛載全局方法(靜態方法)或屬性的代碼
│   │   ├── components -------------------- 包含抽象出來的通用組件
│   ├── server ---------------------------- 包含服務端渲染(server-side rendering)的相關代碼
│   ├── platforms ------------------------- 包含平臺特有的相關代碼,不一樣平臺的不一樣構建的入口文件也在這裏
│   │   ├── web --------------------------- web平臺
│   │   │   ├── entry-runtime.js ---------- 運行時構建的入口,不包含模板(template)到render函數的編譯器,因此不支持 `template` 選項,咱們使用vue默認導出的就是這個運行時的版本。你們使用的時候要注意
│   │   │   ├── entry-runtime-with-compiler.js -- 獨立構建版本的入口,它在 entry-runtime 的基礎上添加了模板(template)到render函數的編譯器
│   │   │   ├── entry-compiler.js --------- vue-template-compiler 包的入口文件
│   │   │   ├── entry-server-renderer.js -- vue-server-renderer 包的入口文件
│   │   │   ├── entry-server-basic-renderer.js -- 輸出 packages/vue-server-renderer/basic.js 文件
│   │   ├── weex -------------------------- 混合應用
│   ├── sfc ------------------------------- 包含單文件組件(.vue文件)的解析邏輯,用於vue-template-compiler包
│   ├── shared ---------------------------- 包含整個代碼庫通用的代碼
├── package.json -------------------------- 不解釋
├── yarn.lock ----------------------------- yarn 鎖定文件
├── .editorconfig ------------------------- 針對編輯器的編碼風格配置文件
├── .flowconfig --------------------------- flow 的配置文件
├── .babelrc ------------------------------ babel 配置文件
├── .eslintrc ----------------------------- eslint 配置文件
├── .eslintignore ------------------------- eslint 忽略配置
├── .gitignore ---------------------------- git 忽略配置

複製代碼

Vue.js構建版本

完整版: 構建後文件包括編譯器+運行時
編譯器: 負責把模板字符串變異爲JS的Render函數
運行時: 負責建立Vue.js實例, 渲染視圖, 使用虛擬DOM算法從新渲染
UMD: 支持經過script標籤在瀏覽器引入
CJS: 用來支持一些低版本打包工具, 由於它們package.json文件的main字段只包含運行時的CJS版本
ESM: 用來支持現代打包工具, 這些打包工具package.json的module字段只包含運行時候的ESM版本
複製代碼
屏幕快照 2019-05-02 下午5 34 44

何時咱們須要使用編譯器?

編譯器: 把template變異爲Render函數。vue

// 用到了template就須要編譯器
new Vue({
   template: '<div></div>'
})

// 若是自己就是Render函數不須要編譯器
new Vue({
   render (h) {
      return h('div', this.hi)
  }
})
複製代碼

咱們若是使用vue-loader, 那麼*.vue文件模板會在構建時候預編譯成JS, 因此打包完成的文件實際上不須要編譯器的, 只須要引入運行時版本(體積小)便可。webpack

若是確實須要使用完整版只須要在打包工具中配置一個別名。git

// webpack

resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
        }
    },
複製代碼

關於開發環境與生產環境

咱們知道Vue有不少打包後的版本web

屏幕快照 2019-05-02 下午6 30 40

它們都依賴於都process.env.NODE_ENV環境變量, 根據其值來決定選擇什麼模式。 因此咱們能夠在打包工具中配置這些環境變量。算法

在webpack中配置環境變量npm

var webpack = require('webpack');

module.exports = {
   ...,

    plugins: [
        // 配置全局變量的插件
        new webpack.DefinePlugin({
           'NODE_ENV': JSON.stringify('production')
        })
    ]
};

複製代碼

構造函數的入口

一步步找到Vue的構造函數入口。json

執行npm run dev

經過查看package.json文件下的scripts命令。api

"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
複製代碼

scripts/config.js爲打開的對應配置文件, process.env.TARGET爲web-full-dev。 在scripts/config.js找到對應的配置對象瀏覽器

const builds = {
    // 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
    },
}

複製代碼

固然主要生成配置對象是這段代碼緩存

function genConfig (name) {
  // opts爲builds裏面對應key的基礎配置對象
  const opts = builds[name]
  // config是真正要返回的配置對象
  const config = {
    input: opts.entry,
    external: opts.external,
    plugins: [
      flow(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    },
    onwarn: (msg, warn) => {
      if (!/Circular/.test(msg)) {
        warn(msg)
      }
    }
  }

  // built-in vars
  const vars = {
    __WEEX__: !!opts.weex,
    __WEEX_VERSION__: weexVersion,
    __VERSION__: version
  }
  // feature flags
  Object.keys(featureFlags).forEach(key => {
    vars[`process.env.${key}`] = featureFlags[key]
  })
  // build-specific env
  // 根據不一樣的process.env.NODE_ENV加載不一樣的打包後版本
  if (opts.env) {
    vars['process.env.NODE_ENV'] = JSON.stringify(opts.env)
  }
  config.plugins.push(replace(vars))

  if (opts.transpile !== false) {
    config.plugins.push(buble())
  }

  Object.defineProperty(config, '_name', {
    enumerable: false,
    value: name
  })

  return config
}

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

複製代碼

找到打包入口文件

根據配置對象的entry字段:

entry: resolve('web/entry-runtime-with-compiler.js')

複製代碼

以及resolve函數

const aliases = require('./alias')
const resolve = p => {
  // web/ weex /server
  const base = p.split('/')[0]
  if (aliases[base]) {
    // 拼接完整的入口文件
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}
複製代碼

aliases.js文件

const path = require('path')

const resolve = p => path.resolve(__dirname, '../', p)

module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
}
複製代碼

找到真正的入口文件爲: vue-dev/src/platforms/web/entry-runtime-with-compiler.js。

在entry-runtime-with-compiler.js文件中發現

import Vue from './runtime/index'
複製代碼

其實這裏主要作的是掛載$mount()方法, 能夠看我以前寫的文章mount掛載函數

OK回到繼續回到咱們以前話題, 在vue-dev/src/platforms/web/runtime/index.js下發現這裏還不是真正的Vue構造函數

import Vue from './instance/index'
複製代碼

不過也立刻接近了, 繼續查找vue-dev/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'

// Vue構造函數
function Vue (options) {
  // 提示必須使用new Vue()
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 執行初始化操做, 通常_前綴方法都是內部方法
  // __init()方法是initMixin裏綁定的
  this._init(options)
}

// 在Vue原型上掛載方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
複製代碼

initMixin()

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    // 緩存this
    const vm: Component = this
    // a uid
    vm._uid = uid++

    // 這裏只要是開啓config.performance進行性能調試時候一些組件埋點
    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    // 標識一個對象是 Vue 實例, 避免再次被observed
    vm._isVue = true
    // merge options
    // options是new Vue(options)配置對象
    // _isComponent是一個內部屬性, 用於建立組件
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      // 定義實例屬性$options: 用於當前 Vue 實例的初始化選項
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    // 定義一個內部屬性_self
    vm._self = vm
    // 執行各類初始化操做
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 執行掛載操做
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
複製代碼

參考閱讀

深刻淺出vue.js

相關文章
相關標籤/搜索