Vue架構設計
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版本
複製程式碼
什麼時候我們需要使用編譯器?
編譯器: 把template變異為Render函式。
// 用到了template就需要編譯器
new Vue({
template: '<div></div>'
})
// 如果本身就是Render函式不需要編譯器
new Vue({
render (h) {
return h('div', this.hi)
}
})
複製程式碼
我們如果使用vue-loader, 那麼*.vue檔案模板會在構建時候預編譯成JS, 所以打包完成的檔案實際上不需要編譯器的, 只需要引入執行時版本(體積小)即可。
如果確實需要使用完整版只需要在打包工具中配置一個別名。
// webpack
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
}
},
複製程式碼
關於開發環境與生產環境
我們知道Vue有很多打包後的版本
它們都依賴於都process.env.NODE_ENV環境變數, 根據其值來決定選擇什麼模式。 所以我們可以在打包工具中配置這些環境變數。
在webpack中配置環境變數
var webpack = require('webpack');
module.exports = {
...,
plugins: [
// 配置全域性變數的外掛
new webpack.DefinePlugin({
'NODE_ENV': JSON.stringify('production')
})
]
};
複製程式碼
建構函式的入口
一步步找到Vue的建構函式入口。
執行npm run dev
通過檢視package.json檔案下的scripts命令。
"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掛載函式](https://juejin.im/post/5c8531995188251bbf2edf82)。
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) { 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複製程式碼