Vue源码解析(一)- package到core

525 阅读2分钟

Vue源码解析(一)

所有的源码分析都应该是package.json开始

package.json解析

  "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 ",
...
}

我们可以看到频繁的出现一个关键词 scripts/config.js

scripts/config.js

根据传入的不同的 --environment Target: 获取不同的配置,简化的配置如下所示

const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.dev.js'),
    format: 'cjs',
    env: 'development',
    banner
  },
  ...
  // Runtime only ES modules build (for bundlers)
  'web-runtime-esm': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.esm.js'),
    format: 'es',
    banner
  },
  // Runtime+compiler ES modules build (for bundlers)
  '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+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
  },
  ...
  // Web server renderer (CommonJS).
  'web-server-renderer-dev': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.dev.js'),
    format: 'cjs',
    env: 'development',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  ...
  // Weex runtime framework (CommonJS).
  'weex-framework': {
    weex: true,
    entry: resolve('weex/entry-framework.js'),
    dest: resolve('packages/weex-vue-framework/index.js'),
    format: 'cjs'
  },
}

我们可以看到几个特征

  1. Vue支持CJS(CommonJS), ESM(ECMAScript Modedule), UMD 如果这三个特性不清楚了,可以单独去了解下
  2. Vue提供Weex的版本
  3. Vue提供runtime,compile,full三种类型的包,full = runtime + compile, compile顾名思义支持提前编译,也就是把我们平时写的<template></template>的vue文件进行拆分编译。

web/entry-runtime-with-compiler.js

对应的文件在src/platform/web下面

$mount函数

getOuterHTML的作用

Vue挂载的时候可以传入一个Element对象,如果没有template信息,这时候会把Element对象的innerHTML当做template。 [TODO:实际场景很少用到,不知道是不是内部用的比较多]

const { render, staticRenderFns } = compileToFunctions

template中的代码,编译成render函数,设计到编译原来,词法分析等,暂时不深入理解

[TODO: staticRenderFns 的作用]

整个web/entry-runtime-with-compiler.js文件的Vue对象来自于web/runtime/index.js我们继续深入了解

web/runtime/index.js

我们发现此文件中也有$mount方法,什么意思呢? 回头看上一个文件,发现

//先保留核心的mount
const mount = Vue.prototype.$mount;

//在调用核心mount
return mount.call(this, el, hydrating);

这其实是一个装饰器模式,目的让entry-runtime-with-compiler.js$mount支持compile模式。

而在此文件中

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

核心逻辑是mountComponent

mountComponent

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  //没有则返回一个空的VNode,VNode会在后面具体讲
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  //调用生命周期
  callHook(vm, 'beforeMount')

  //跟新Dom节点,这个内部逻辑比较多,可以先放一放
  let updateComponent
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    //...调试信息忽略
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }
  
  // Watcher 后续作为一个重点去看,大家可以先把这个理解为对Data的双向监听
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // 调用挂载函数,结束挂载
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

[TODO:transition 原理]

extend, noop, toObject函数

我们学一些里面用到的小工具。

//添加对象属性
export function extend (to: Object, _from: ?Object): Object {
  for (const key in _from) {
    to[key] = _from[key]
  }
  return to
}

//把数据属性合并到一个对象上
export function toObject (arr: Array<any>): Object {
  const res = {}
  for (let i = 0; i < arr.length; i++) {
    if (arr[i]) {
      extend(res, arr[i])
    }
  }
  return res
}

//空函数写法
export function noop (a?: any, b?: any, c?: any) {}

总结

到此位置,我们已经完成不同环境,不同平台的源码解剖,去除了Runtime,Compile,Weex等额外的干扰因素,下面我们将进入 Core 内部的学习。