从入口开始
我们在这里只看 Runtime + Compiler 构建的入口
'src/platforms/web/entry-runtime-with-compiler.js'
import config from "core/config";
import { warn, cached } from "core/util/index";
import { mark, measure } from "core/util/perf";
import Vue from "./runtime/index";
import { query } from "./util/index";
import { compileToFunctions } from "./compiler/index";
import {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
} from "./util/compat";
const idToTemplate = cached((id) => {
const el = query(id);
return el && el.innerHTML;
});
const mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el);
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== "production" &&
warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
);
return this;
}
const options = this.$options;
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template;
if (template) {
if (typeof template === "string") {
if (template.charAt(0) === "#") {
template = idToTemplate(template);
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
);
}
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
if (process.env.NODE_ENV !== "production") {
warn("invalid template option:" + template, this);
}
return this;
}
} else if (el) {
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
mark("compile");
}
const { render, staticRenderFns } = compileToFunctions(
template,
{
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments,
},
this
);
options.render = render;
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
mark("compile end");
measure(`vue ${this._name} compile`, "compile", "compile end");
}
}
}
return mount.call(this, el, hydrating);
};
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML(el: Element): string {
if (el.outerHTML) {
return el.outerHTML;
} else {
const container = document.createElement("div");
container.appendChild(el.cloneNode(true));
return container.innerHTML;
}
}
Vue.compile = compileToFunctions;
export default Vue;
👇🏻
'./runtime/index'
import Vue from "core/index";
import config from "core/config";
import { extend, noop } from "shared/util";
import { mountComponent } from "core/instance/lifecycle";
import { devtools, inBrowser, isChrome } from "core/util/index";
import {
query,
mustUseProp,
isReservedTag,
isReservedAttr,
getTagNamespace,
isUnknownElement,
} from "web/util/index";
import { patch } from "./patch";
import platformDirectives from "./directives/index";
import platformComponents from "./components/index";
// 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" &&
isChrome
) {
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;
👇🏻
'core/index'
import Vue from "./instance/index";
import { initGlobalAPI } from "./global-api/index";
import { isServerRendering } from "core/util/env";
import { FunctionalRenderContext } from "core/vdom/create-functional-component";
// 初始化全局API
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__";
export default Vue;
👇🏻
'./instance/index'
Vue 的定义
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";
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);
}
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
export default Vue;
它实际上就是一个用 Function 实现的类,我们只能通过 new Vue 去实例化它。
为何 Vue 不用 ES6 的 Class 去实现呢?后面很多 xxxMixin 的函数调用,并把 Vue 当参数传入,它们的功能都是给 Vue 的 prototype 上扩展一些方法,Vue 按功能把这些扩展分散到多个模块中去实现,而不是在一个模块里实现所有,这种方式是用 Class 难以实现的。这么做的好处是非常方便代码的维护和管理,这种编程技巧也非常值得我们去学习。
initGlobalAPI
Vue 在初始化的过程中,除了给他的原型 prototype 上扩展方法,还会给 Vue 这个对象本身扩展全局的静态方法,定义在'./global-api/index'
import config from "../config";
import { initUse } from "./use";
import { initMixin } from "./mixin";
import { initExtend } from "./extend";
import { initAssetRegisters } from "./assets";
import { set, del } from "../observer/index";
import { ASSET_TYPES } from "shared/constants";
import builtInComponents from "../components/index";
import {
warn,
extend,
nextTick,
mergeOptions,
defineReactive,
} from "../util/index";
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;
Vue.options = Object.create(null);
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;
extend(Vue.options.components, builtInComponents);
initUse(Vue);
initMixin(Vue);
initExtend(Vue);
initAssetRegisters(Vue);
}
这就是 Vue 上扩展的一些方法,在官网中全局 API 都可以在这找到。要注意,Vue.util 暴露的方法不要依赖,因为它可能会发生变化。
总结
在此,我们了解了如何查看 Vue 的入口、初始化过程。它本质上是一个用 Function 实现的 Class,然后在它的原型 prototype 以及它本身都扩展了一系列的方法和属性。