huixisheng / huixisheng.github.com

前端开发者说,记录前端的故事
http://huixisheng.github.io/
12 stars 3 forks source link

Vue源码分析-代码主要流程 #81

Open huixisheng opened 5 years ago

huixisheng commented 5 years ago

安装依赖

$ yarn
$ cd packages/vue-server-renderer && yarn
$ cd packages/weex-vue-framework && yarn
$ npm run dev:test

根据测试用例看代码

"dev:test": "karma start test/unit/karma.dev.config.js", 加载相关测试用例 test/unit/index.js

components
directives
filter
global-api
instance
options
transition
debug
error-handling
ref

以上可以对照 Api 文档 https://cn.vuejs.org/v2/api/

Vue 核心模块

compiler
observer
server-compoler
sfc
util
vdom

代码流程

库入口 src/platforms/web/entry-runtime-with-compiler 。对应文档的完整版

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'),
  entries: resolve('src/entries'),
  sfc: resolve('src/sfc')
}

object instanceof constructor

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。

src/platforms/web/entry-runtime-with-compiler.js
    import Vue from './runtime/index'
    // src/platforms/web/runtime/index.js
    const mount = Vue.prototype.$mount
    Vue.prototype.$mount
        // Compile Template
        compileToFunctions
        mount.call

src/platforms/web/runtime/index.js

    Vue.config.mustUseProp/isReservedTag/isReservedAttr/getTagNamespace/isUnknownElement
    Vue.options.directives
    Vue.options.components
    Vue.prototype.__patch__
    Vue.prototype.$mount
        mountComponent
            beforeMount
            updateComponent
            mounted
            beforeUpdate
            updated
            // 手动调用 $destroy
            callHook(vm, 'beforeDestroy')
            vm._watcher.teardown()
            callHook(vm, 'destroyed')

src/core/index.js
    import Vue from './instance/index'
    initGlobalAPI
        Vue.config
        // 不是公开的 API
        Vue.util.*
        Vue.set/delete
        Vue.nextTick
        Vue.observable // 2.6
        Vue.options.components、directives、filters
        Vue.use
        Vue.mixin
        Vue.extend
        Vue.components、directives、filters

    Vue.prototype.$isServer
    Vue.prototype.$ssrContext
    Vue.FunctionalRenderContext
    Vue.version

src/core/instance/index.js
    function Vue (options) {
        this._init(options)
    }

    initMixin(Vue)
        Vue.prototype.__init
            // 初始化组件的属性
            initInternalComponent
            // 数据绑定?
            initProxy

            vm._self = vm
            // 初始化生命周期
            initLifecycle(vm)
            initEvents(vm)
            initRender(vm)
            callHook(vm, 'beforeCreate')
            initInjections(vm) // resolve injections before data/props
                defineReactive // Define a reactive property on an Object.
            initState(vm)
                initProps
                initMethods
                initData
                initWatch
            initProvide(vm) // resolve provide after data/props
            callHook(vm, 'created')  

            vm.$mount(vm.$options.el)

    stateMixin(Vue)
        Vue.prototype.$data
        Vue.prototype.$props
        Vue.prototype.$set、$delete
        Vue.prototype.$watch

    eventsMixin(Vue)
        Vue.prototype.$on
        Vue.prototype.$once
        Vue.prototype.$off
        Vue.prototype.$emit

    lifecycleMixin(Vue)
        Vue.prototype._update
        Vue.prototype.$forceUpdate
        Vue.prototype.$destroy

    renderMixin(Vue)
        Vue.prototype.$nextTick
        Vue.prototype._render

VSCode 代码跳转

配置 jsconfig.json 方便代码跳转

重启 VSCode 才生效

{
  "compilerOptions": {
    "target": "es2017",
    "baseUrl": "./",
    "paths": {
      "vue/*": ["src/platforms/web/entry-runtime-with-compiler/*"],
      "compiler/*": ["src/compiler/*"],
      "core/*": ["src/core/*"],
      "shared/*": ["src/shared/*"],
      "web/*": ["src/platforms/web/*"],
      "weex/*": ["src/platforms/weex/*"],
      "server/*": ["src/server/*"],
      "entries/*": ["src/entries/*"],
      "sfc/*": ["src/sfc/*"],
    }
  },
  "exclude": ["node_modules", "dist"],
  "include": ["src/**/*"]
}

相关知识点

关于 jasminekarmaChromeHeadless 参考 https://github.com/huixisheng/huixisheng.github.com/issues/80

require.context

// require all test files
const testsContext = require.context('./', true, /\.spec$/)
testsContext.keys().forEach(testsContext) 
ƒ webpackContext(req)
id: "./test/helpers sync recursive ^\.\/.*$"
keys: ƒ webpackContextKeys()
resolve: ƒ webpackContextResolve(req)
arguments: null
caller: null
length: 1
name: "webpackContext"
prototype: {constructor: ƒ}
__proto__: ƒ ()
[[FunctionLocation]]: .*$:19
[[Scopes]]: Scopes[2]

webpackContext(req) {
    var id = webpackContextResolve(req);
    return __webpack_require__(id);
}

src/unit/index.js 添加 debugger

var map = {
    "./classlist": "./test/helpers/classlist.js",
    "./classlist.js": "./test/helpers/classlist.js",
    "./test-object-option": "./test/helpers/test-object-option.js",
    "./test-object-option.js": "./test/helpers/test-object-option.js",
    "./to-equal": "./test/helpers/to-equal.js",
    "./to-equal.js": "./test/helpers/to-equal.js",
    "./to-have-been-warned": "./test/helpers/to-have-been-warned.js",
    "./to-have-been-warned.js": "./test/helpers/to-have-been-warned.js",
    "./trigger-event": "./test/helpers/trigger-event.js",
    "./trigger-event.js": "./test/helpers/trigger-event.js",
    "./vdom": "./test/helpers/vdom.js",
    "./vdom.js": "./test/helpers/vdom.js",
    "./wait-for-update": "./test/helpers/wait-for-update.js",
    "./wait-for-update.js": "./test/helpers/wait-for-update.js"
};

function webpackContext(req) {
    var id = webpackContextResolve(req);
    return __webpack_require__(id);
}
function webpackContextResolve(req) {
    var id = map[req];
    if(!(id + 1)) { // check for number or string
        var e = new Error("Cannot find module '" + req + "'");
        e.code = 'MODULE_NOT_FOUND';
        throw e;
    }
    return id;
}
webpackContext.keys = function webpackContextKeys() {
    return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = "./test/helpers sync recursive ^\\.\\/.*$";
var map = {
    "./features/component/component-async.spec": "./test/unit/features/component/component-async.spec.js",
    "./features/component/component-keep-alive.spec": "./test/unit/features/component/component-keep-alive.spec.js",
    "./features/component/component-scoped-slot.spec": "./test/unit/features/component/component-scoped-slot.spec.js",
    "./features/component/component-slot.spec": "./test/unit/features/component/component-slot.spec.js",
    "./features/component/component.spec": "./test/unit/features/component/component.spec.js",
    "./features/debug.spec": "./test/unit/features/debug.spec.js",
    "./features/directives/bind.spec": "./test/unit/features/directives/bind.spec.js",
    "./features/directives/class.spec": "./test/unit/features/directives/class.spec.js",
    "./features/directives/cloak.spec": "./test/unit/features/directives/cloak.spec.js",
    "./features/directives/for.spec": "./test/unit/features/directives/for.spec.js",
    "./features/directives/html.spec": "./test/unit/features/directives/html.spec.js",
    "./features/directives/if.spec": "./test/unit/features/directives/if.spec.js",
    "./features/directives/model-checkbox.spec": "./test/unit/features/directives/model-checkbox.spec.js",
    "./features/directives/model-component.spec": "./test/unit/features/directives/model-component.spec.js",
    "./features/directives/model-dynamic.spec": "./test/unit/features/directives/model-dynamic.spec.js",
    "./features/directives/model-file.spec": "./test/unit/features/directives/model-file.spec.js",
    "./features/directives/model-parse.spec": "./test/unit/features/directives/model-parse.spec.js",
    "./features/directives/model-radio.spec": "./test/unit/features/directives/model-radio.spec.js",
    "./features/directives/model-select.spec": "./test/unit/features/directives/model-select.spec.js",
    "./features/directives/model-text.spec": "./test/unit/features/directives/model-text.spec.js",
    "./features/directives/on.spec": "./test/unit/features/directives/on.spec.js",
    "./features/directives/once.spec": "./test/unit/features/directives/once.spec.js",
    "./features/directives/pre.spec": "./test/unit/features/directives/pre.spec.js",
    "./features/directives/show.spec": "./test/unit/features/directives/show.spec.js",
    "./features/directives/static-style-parser.spec": "./test/unit/features/directives/static-style-parser.spec.js",
    "./features/directives/style.spec": "./test/unit/features/directives/style.spec.js",
    "./features/directives/text.spec": "./test/unit/features/directives/text.spec.js",
    "./features/error-handling.spec": "./test/unit/features/error-handling.spec.js",
    "./features/filter/filter.spec": "./test/unit/features/filter/filter.spec.js",
    "./features/global-api/assets.spec": "./test/unit/features/global-api/assets.spec.js",
    "./features/global-api/compile.spec": "./test/unit/features/global-api/compile.spec.js",
    "./features/global-api/config.spec": "./test/unit/features/global-api/config.spec.js",
    "./features/global-api/extend.spec": "./test/unit/features/global-api/extend.spec.js",
    "./features/global-api/mixin.spec": "./test/unit/features/global-api/mixin.spec.js",
    "./features/global-api/observable.spec": "./test/unit/features/global-api/observable.spec.js",
    "./features/global-api/set-delete.spec": "./test/unit/features/global-api/set-delete.spec.js",
    "./features/global-api/use.spec": "./test/unit/features/global-api/use.spec.js",
    "./features/instance/init.spec": "./test/unit/features/instance/init.spec.js",
    "./features/instance/methods-data.spec": "./test/unit/features/instance/methods-data.spec.js",
    "./features/instance/methods-events.spec": "./test/unit/features/instance/methods-events.spec.js",
    "./features/instance/methods-lifecycle.spec": "./test/unit/features/instance/methods-lifecycle.spec.js",
    "./features/instance/properties.spec": "./test/unit/features/instance/properties.spec.js",
    "./features/instance/render-proxy.spec": "./test/unit/features/instance/render-proxy.spec.js",
    "./features/options/_scopeId.spec": "./test/unit/features/options/_scopeId.spec.js",
    "./features/options/comments.spec": "./test/unit/features/options/comments.spec.js",
    "./features/options/components.spec": "./test/unit/features/options/components.spec.js",
    "./features/options/computed.spec": "./test/unit/features/options/computed.spec.js",
    "./features/options/data.spec": "./test/unit/features/options/data.spec.js",
    "./features/options/delimiters.spec": "./test/unit/features/options/delimiters.spec.js",
    "./features/options/directives.spec": "./test/unit/features/options/directives.spec.js",
    "./features/options/el.spec": "./test/unit/features/options/el.spec.js",
    "./features/options/errorCaptured.spec": "./test/unit/features/options/errorCaptured.spec.js",
    "./features/options/extends.spec": "./test/unit/features/options/extends.spec.js",
    "./features/options/functional.spec": "./test/unit/features/options/functional.spec.js",
    "./features/options/inheritAttrs.spec": "./test/unit/features/options/inheritAttrs.spec.js",
    "./features/options/inject.spec": "./test/unit/features/options/inject.spec.js",
    "./features/options/lifecycle.spec": "./test/unit/features/options/lifecycle.spec.js",
    "./features/options/methods.spec": "./test/unit/features/options/methods.spec.js",
    "./features/options/mixins.spec": "./test/unit/features/options/mixins.spec.js",
    "./features/options/name.spec": "./test/unit/features/options/name.spec.js",
    "./features/options/parent.spec": "./test/unit/features/options/parent.spec.js",
    "./features/options/props.spec": "./test/unit/features/options/props.spec.js",
    "./features/options/propsData.spec": "./test/unit/features/options/propsData.spec.js",
    "./features/options/render.spec": "./test/unit/features/options/render.spec.js",
    "./features/options/renderError.spec": "./test/unit/features/options/renderError.spec.js",
    "./features/options/template.spec": "./test/unit/features/options/template.spec.js",
    "./features/options/watch.spec": "./test/unit/features/options/watch.spec.js",
    "./features/ref.spec": "./test/unit/features/ref.spec.js",
    "./features/transition/transition-group.spec": "./test/unit/features/transition/transition-group.spec.js",
    "./features/transition/transition-mode.spec": "./test/unit/features/transition/transition-mode.spec.js",
    "./features/transition/transition.spec": "./test/unit/features/transition/transition.spec.js",
    "./modules/compiler/codeframe.spec": "./test/unit/modules/compiler/codeframe.spec.js",
    "./modules/compiler/codegen.spec": "./test/unit/modules/compiler/codegen.spec.js",
    "./modules/compiler/compiler-options.spec": "./test/unit/modules/compiler/compiler-options.spec.js",
    "./modules/compiler/optimizer.spec": "./test/unit/modules/compiler/optimizer.spec.js",
    "./modules/compiler/parser.spec": "./test/unit/modules/compiler/parser.spec.js",
    "./modules/observer/dep.spec": "./test/unit/modules/observer/dep.spec.js",
    "./modules/observer/observer.spec": "./test/unit/modules/observer/observer.spec.js",
    "./modules/observer/scheduler.spec": "./test/unit/modules/observer/scheduler.spec.js",
    "./modules/observer/watcher.spec": "./test/unit/modules/observer/watcher.spec.js",
    "./modules/server-compiler/compiler-options.spec": "./test/unit/modules/server-compiler/compiler-options.spec.js",
    "./modules/server-compiler/optimizer.spec": "./test/unit/modules/server-compiler/optimizer.spec.js",
    "./modules/sfc/sfc-parser.spec": "./test/unit/modules/sfc/sfc-parser.spec.js",
    "./modules/util/invoke-with-error-handling.spec": "./test/unit/modules/util/invoke-with-error-handling.spec.js",
    "./modules/util/next-tick.spec": "./test/unit/modules/util/next-tick.spec.js",
    "./modules/vdom/create-component.spec": "./test/unit/modules/vdom/create-component.spec.js",
    "./modules/vdom/create-element.spec": "./test/unit/modules/vdom/create-element.spec.js",
    "./modules/vdom/modules/attrs.spec": "./test/unit/modules/vdom/modules/attrs.spec.js",
    "./modules/vdom/modules/class.spec": "./test/unit/modules/vdom/modules/class.spec.js",
    "./modules/vdom/modules/directive.spec": "./test/unit/modules/vdom/modules/directive.spec.js",
    "./modules/vdom/modules/dom-props.spec": "./test/unit/modules/vdom/modules/dom-props.spec.js",
    "./modules/vdom/modules/events.spec": "./test/unit/modules/vdom/modules/events.spec.js",
    "./modules/vdom/modules/style.spec": "./test/unit/modules/vdom/modules/style.spec.js",
    "./modules/vdom/patch/children.spec": "./test/unit/modules/vdom/patch/children.spec.js",
    "./modules/vdom/patch/edge-cases.spec": "./test/unit/modules/vdom/patch/edge-cases.spec.js",
    "./modules/vdom/patch/element.spec": "./test/unit/modules/vdom/patch/element.spec.js",
    "./modules/vdom/patch/hooks.spec": "./test/unit/modules/vdom/patch/hooks.spec.js",
    "./modules/vdom/patch/hydration.spec": "./test/unit/modules/vdom/patch/hydration.spec.js"
};

function webpackContext(req) {
    var id = webpackContextResolve(req);
    return __webpack_require__(id);
}
function webpackContextResolve(req) {
    var id = map[req];
    if(!(id + 1)) { // check for number or string
        var e = new Error("Cannot find module '" + req + "'");
        e.code = 'MODULE_NOT_FOUND';
        throw e;
    }
    return id;
}
webpackContext.keys = function webpackContextKeys() {
    return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = "./test/unit sync recursive \\.spec$";
/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/        }
/******/    };
/******/
/******/    // define __esModule on exports
/******/    __webpack_require__.r = function(exports) {
/******/        if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/            Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/        }
/******/        Object.defineProperty(exports, '__esModule', { value: true });
/******/    };
/******/
/******/    // create a fake namespace object
/******/    // mode & 1: value is a module id, require it
/******/    // mode & 2: merge all properties of value into the ns
/******/    // mode & 4: return value when already ns object
/******/    // mode & 8|1: behave like require
/******/    __webpack_require__.t = function(value, mode) {
/******/        if(mode & 1) value = __webpack_require__(value);
/******/        if(mode & 8) return value;
/******/        if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/        var ns = Object.create(null);
/******/        __webpack_require__.r(ns);
/******/        Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/        if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/        return ns;
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "/_karma_webpack_/";
/******/
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = "./test/unit/index.js");
/******/ })
/************************************************************************/