Take time to read the source code for a little while every day, for no reason, I often use Vue, read should know more, and come to follow the volume for a while…
To read the source code, you have to start with the import file. To find the Vue source code import file, you have to start with the package.json file, because we usually use the NPM command to package and compile, so there must be a file to point to.
"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"
}
Copy the code
Hu ~ good guy, a come in and look at the thick and thick of this tuo command really scared. However, if you look at the first line of the dev command, you can see that it executes the following command:
rollup -w -c scripts/config.js --environment TARGET:web-full-dev
Copy the code
The command will look for the file scripts/config.js and web-full-dev, so find the corresponding file location and find the following code:
'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
}
Copy the code
Resolve (‘web/entry-runtime-with-compiler.js’) path to the file and find a problem:
Shock the entire UC news department, there is no Web directory! After calming down for a while, resolve appears to be the problem. Sure enough, we find that resolve is a custom method as follows:
const resolve = p= > {
const base = p.split('/') [0]
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1))}else {
return path.resolve(__dirname, '.. / ', p)
}
}
Copy the code
It turns out that the/before the path is an alias, defined in alias.js as follows:
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')}Copy the code
It’s clear that this entry ultimately points to: SRC /platforms/web/ entry-runtime-with-Compiler.js, so I can’t wait to open this file and find that it introduces a Vue file. Oh my god! Enter the main character:
.import Vue from './runtime/index'.Copy the code
Runtime /index: runtime/index
.import Vue from 'core/index'.Copy the code
They are all called core directories, which look like core directories.
.import Vue from './instance/index'.Copy the code
The final position was finally fixed under the instance/index file, because I finally found our protagonist:
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
Copy the code
As you can see from this file, two things are done:
- define
Vue
Object: inside to determine whether new is generated, and executed_init
This method; - the
Vue
Object passed to eachMinxin
In the method.
Now, I immediately have a question: where is this _init method defined and what is it for? If you want to know the answer, you have to go to the Mixin method. So, I start with the initMixin method and open the init.js file:
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options? :Object) {... }}Copy the code
As soon as I came in, I saw that the prototype chain of Vue was hanging in a regular way, but it was the _init method. Turn your mind to the _init method and start exploring:
- First of all, we found that we defined one
vm
Object to point to the current instance;
const vm: Component = this
Copy the code
- As if to make a performance record:
// a uid
vm._uid = uid++
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)
}
Copy the code
- Initialization parameters:
// a flag to avoid this being observed
vm._isVue = true
// merge options
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 {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
Copy the code
The first sentence vm._isVue = true is supposed to avoid being observed. Then, by judgment, the instance object is treated differently. I can’t see where the _isComponent property is mounted, so I’ll go straight to the following. Found the vm $options by mergeOptions parameters of this method to merge, in another resolveConstructorOptions mergeOptions for processing, then find the way to find:
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if(superOptions ! == cachedSuperOptions) {// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
Copy the code
The constructor returns options (where is this defined?). The constructor returns options (where is this defined?). Some objects have been mounted:
Day! The mergeOptions function has the following functions:
/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */
export function mergeOptions (
parent: Object,
child: Object, vm? : Component) :Object {
if(process.env.NODE_ENV ! = ='production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if(! child._base) {if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if(! hasOwn(parent, key)) { mergeField(key) } }function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
Copy the code
It turns out that this is a utility class, and the annotation translates to:
Combine the two option objects into a new object. Core utilities for instantiation and inheritance.
There are basically two things: one is to test parameters and make corresponding hints (many warnings we encountered in development can be found here), and the other is to merge parameters. It is too difficult to read, so I will not delve into it for the time being and go through the process first (forgive me).
Back in the init.js file, I find this code again:
/* istanbul ignore else */
if(process.env.NODE_ENV ! = ='production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
Copy the code
In a non-production environment, I did an initProxy operation, and clicked on this method.
config.keyCodes = new Proxy(config.keyCodes, {
set (target, key, value) {
if (isBuiltInModifier(key)) {
warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
return false
} else {
target[key] = value
return true}}})Copy the code
How can there be Proxy operation ah, this is not used for Vue3, but look at the submission time is before…
Anyway, this is a proxy operation, so let’s go back to the other flow. The rest of the code is found in the init.js file:
/ expose real 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)
}
Copy the code
I found myself doing three things:
- Initialize the life cycle, state, and so on (certainly complex, more on that later);
- Performance testing (used in conjunction with the previous one);
- Mount according to judgment
el
Object (this should be writtenel
Property without$mount
The reason is, essentially, through$mount
To mount).
Today, first to this, read the source code timeout, mainly my food, looking at the effort. To be continue…