vue源码构建

本次学习版本是 vue 2.6.11

首先下载vue源码 **https://github.com/vuejs/vue.git** 然后优先看根目录下的 package.json

scripts 字段里面有一堆配置

"scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
    "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
    "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
    "dev:test": "karma start test/unit/karma.dev.config.js",
    "dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
    "dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
    "dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework",
    "dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
    "dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
    "build": "node scripts/build.js",
    "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
    "build:weex": "npm run build -- weex",
    "test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex",
    "test:unit": "karma start test/unit/karma.unit.config.js",
    "test:cover": "karma start test/unit/karma.cover.config.js",
    "test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js",
    "test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.js",
    "test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.js",
    "test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2",
    "test:types": "tsc -p ./types/test/tsconfig.json",
    "lint": "eslint src scripts test",
    "flow": "flow check",
    "sauce": "karma start test/unit/karma.sauce.config.js",
    "bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js",
    "release": "bash scripts/release.sh",
    "release:weex": "bash scripts/release-weex.sh",
    "release:note": "node scripts/gen-release-note.js",
    "commit": "git-cz"
  },

主要分析我们日常开发熟悉的 "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev" 这一段,这里用到了 rollup (https://github.com/rollup/rollup) 构建,这里暂时不管,只知道是一个类似 webpack 的打包工具。

往后查找 scripts/config.js 以及 TARGET为 web-full-dev 的内容

大概所略为

const version = process.env.VERSION || require('../package.json').version
const banner =
  '/*!\\n' +
  ` * Vue.js v${version}\\n` +
  ` * (c) 2014-${new Date().getFullYear()} Evan You\\n` +
  ' * Released under the MIT License.\\n' +
  ' */'

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) {
  const opts = builds[name]
  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)
      }
    }
  }
    // 中间被我删掉一些
    ...
  return config
}

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

到最下面,我们看到它调用了genConfig(process.env.TARGET) ,getConfig用于生成rollup的配置文件。builds是一个对象,获取它的process.env.TARGET值,在package.json中,我们看到dev中有TARGET:web-full-dev参数,即上面我留下的那一段配置。这样入口文件我们就找到了,也就是根目录下 src\platforms\web\entry-runtime-with-compiler.js

找到 entry-runtime-with-compiler.js 就看到

import Vue from './runtime/index' 

Vue 是从 './runtime/index' 文件引入,然后在改文件进行加工 原型上挂载 $mount 以及 添加 compile 方法。

打开 './runtime/index'

第一行就是 import Vue from 'core/index' 所以该文件的vue也还是从另一个地方的 vue 引进来而进行拓展。该文件 引入 patch 以及 对vue原型关注了 $mount 所以该文件是比较重要,以后会分析

接下来找到 'core/index' 文件

第一行也是从其他文件引入 import Vue from './instance/index' 是真有点深

找到 ./instance/index 文件,终于看到了光明。

整个过程是

src/platforms/web/entry-runtime-with-compiler.js
->
src/platforms/web/runtime/index.js
->
src/core/index.js
->
src/core/instance/index.js

找到 src/core/instance/index.js 终于找到定义 Vue 对象的所在之处,它是一个构造函数

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

首先判断如果不是生产环境,且不是通过 new 构造出来的实例对象话,就在控制台打印 warn ,来看下这个 warn 定义

是在 src\core\util\debug.js 文件里面,

export let generateComponentTrace = (noop: any) // work around flow check

warn = (msg, vm) => {

    const trace = vm ? generateComponentTrace(vm) : ''
        // config.warnHandler 默认是 null 
    if (config.warnHandler) {
      config.warnHandler.call(null, msg, vm, trace)
    } else if (hasConsole && (!config.silent)) {
      console.error(`[Vue warn]: ${msg}${trace}`)
    }
  }

接下来调用 this._init(options) ,接着 就对 Vue.prototype 原型上绑定了一些实例,后面看到源码有总结慢慢进行补充

// _init
initMixin(Vue)  
// $set、$delete、$watch
stateMixin(Vue)
// $on、$once、$off、$emit
eventsMixin(Vue)
// _update、$forceUpdate、$destroy
lifecycleMixin(Vue)
// $nextTick、_render、以及多个内部调用的方法
renderMixin(Vue)

接着我们按照查找的倒叙看回 src/core/index.js

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

这个文件也可以一目了然,首先调用了 initGlobalAPI(Vue) , initGlobalAPI 方法引自src\core\global-api\index.js

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)

    // ASSET_TYPES  ->  'component','directive','filter'

  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  // Vue.options.components.KeepAlive
  extend(Vue.options.components, builtInComponents)

    // Vue.use
  initUse(Vue)
    // Vue.mixin
  initMixin(Vue)
    // Vue.extend
  initExtend(Vue)
    // Vue.component、Vue.directive、Vue.filter
  initAssetRegisters(Vue)
}

从上面代码可以看出,它是对Vue对象添加了一些静态方法和属性

/src/core/index.js文件中,还添加了一个Vue.prototype.$isServer属性,用于判断是不是服务端渲染,以及 $ssrContext (待补充) 还有 FunctionalRenderContext 用来 SSR 运行时帮助安装, 还有一个就是Vue.version。

接着就是 src/platforms/web/runtime/index.js

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
  setTimeout(() => {
    if (config.devtools) {
      if (devtools) {
        devtools.emit('init', Vue)
      } else if (
        process.env.NODE_ENV !== 'production' &&
        process.env.NODE_ENV !== 'test'
      ) {
        console[console.info ? 'info' : 'log'](
          'Download the Vue Devtools extension for a better development experience:\\n' +
          '<https://github.com/vuejs/vue-devtools>'
        )
      }
    }
    if (process.env.NODE_ENV !== 'production' &&
      process.env.NODE_ENV !== 'test' &&
      config.productionTip !== false &&
      typeof console !== 'undefined'
    ) {
      console[console.info ? 'info' : 'log'](
        `You are running Vue in development mode.\\n` +
        `Make sure to turn on production mode when deploying for production.\\n` +
        `See more tips at <https://vuejs.org/guide/deployment.html`>
      )
    }
  }, 0)
}

export default Vue

我们先看看 src/platforms/ 里面有 web 和 weex 文件夹,所以源码里把两个平台不同的内容单独提取出来了,这种目录思维非常值得学习借鉴。

我们这里只谈 web ,首先,在Vue.config上添加了几个平台相关的方法,扩展了Vue.options.directives(model和show)和Vue.options.components(Transition和TransitionGroup)。在Vue.prototype上添加了__patch__(虚拟dom相关)和$mount(挂载元素)。

最后是/src/entries/web-runtime-with-compiler.js,该文件主要干了两件事,一个是定义了一个方法Vue.prototype.$mount,另一个是将compileToFunctions挂在到Vue.compile上。

总结

entry-runtime-with-compiler.js

一个是定义了一个方法Vue.prototype.$mount,另一个是将compileToFunctions挂在到Vue.compile上

src/platforms/web/runtime/index.js

首先,在Vue.config上添加了几个平台相关的方法,扩展了Vue.options.directives(model和show)和Vue.options.components(Transition和TransitionGroup)。在Vue.prototype上添加了__patch__(虚拟dom相关)和$mount(挂载元素)。

src/core/index.js

添加了一些静态方法和属性

Vue.use、 Vue.mixin、 Vue.extend、[ Vue.component、Vue.directive、Vue.filter ] Vue.options.components.KeepAlive

还添加了一个Vue.prototype.$isServer属性,用于判断是不是服务端渲染,以及 $ssrContext (待补充) 还有 FunctionalRenderContext 用来 SSR 运行时帮助安装, 还有一个就是Vue.version。

src/core/instance/index.js

vue 构造函数,以及一些静态方法和实例

_init initMixin(Vue)
$set$delete$watch stateMixin(Vue)
$on$once$off$emit eventsMixin(Vue)
_update、$forceUpdate$destroy lifecycleMixin(Vue)
$nextTick、_render、以及多个内部调用的方法 renderMixin(Vue)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343