preface

Almost one in the past, from the practice to become a full member has in the past seven or eight months, the content of the work feeling more and more boring, a day to move the brick to let his body be emptied their thinking, go on like this, I feel I will waste, so that this article is to force yourself to think about, and to adjust their own state.

In addition, the new understanding of the source code is not necessarily correct, but also hope that you give directions.

The preparatory work

The address of the project: https://github.com/vuejs/vue.git

Version: 2.6.14

Project Structure:

A word:

As we know, the.vue file will become a JS object after being packaged by the Vue-loader of Webpack, and this object will have a render attribute from the template of the.vue file. That’s what compilers do.

In the package. The json:

We’ll see that vue is packaged with rollup, so we need to install rollup globally, then install dependencies, and finally run NPM run dev. [Note: I added –sourcemap in dev so that debug can see where in the source file later]

{
  "name": "vue"."version": "2.6.14"."main": "dist/vue.runtime.common.js"."module": "dist/vue.runtime.esm.js"."sideEffects": false."scripts": {
    "dev": "rollup -w -c scripts/config.js --sourcemap --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". }},Copy the code

As you can see, NPM run dev executes the scripts/config.js file and specifies the web-full-dev parameter.

scripts/config.js

const aliases = require('./alias')
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)
  }
}
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
  },
  ...
]
Copy the code

alias.js

const path = require('path')

const resolve = p= > path.resolve(__dirname, '.. / ', p)

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

The web/entry-runtime-with-compiler.js file is obviously the entry file, but looking back at our directory, it doesn’t seem to have the Web folder either. Note that resolve should do something about it, and then examine the code:

resolve('web/entry-runtime-with-compiler.js')

-> base = 'web'; 

-> alias[web] = 'src/platforms/web';

-> path.resolve('src/platforms/web/entry-runtime-with-compiler.js')
Copy the code

Ohhhhh!!!!!! Finally found the entrance!! We can finally put an end to the long preparations.

entry-runtime-with-compiler.js

What’s up??

Render (el, render, el); render (el, el, el); render (el, el, el); The sequence is clearly indicated in the code.

/* @flow */

import Vue from "./runtime/index";

const idToTemplate = cached((id) = > {
  const el = query(id);
  return el && el.innerHTML;
});

// Extend the $mount method
const mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && query(el);

  const options = this.$options;
  // resolve template/el and convert to render function

  // Check to see if the render option is available, then mount it directly
  // If not, check to see if the template option exists
  // If template starts with #, take its innerHTML
  // If template is an element, take its innerHTML
  // Otherwise return the instance directly
  //
  // If not, check to see if there is an EL option
  // If the el has outerHTML, fetch its outerHTML
  // If there is and el has no outterHTML, create a div container around it and take the innerHTML of the container
  // Template = getHTML(el)

  // With the above steps, if the template option exists, compile it to get the render function

  if(! options.render) {let template = options.template;
    if (template) {
      if (typeof template === "string") {
        if (template.charAt(0) = = ="#") { template = idToTemplate(template); }}else if (template.nodeType) {
        template = template.innerHTML;
      } else {
        return this; }}else if (el) {
      template = getOuterHTML(el);
    }
    if (template) {
      // Compile to convert template to render function
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: process.env.NODE_ENV ! = ="production",
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments,
        },
        this
      );
      // Get the render function assigned to the option for future useoptions.render = render; options.staticRenderFns = staticRenderFns; }}// Perform the default mount
  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;
Copy the code

Conclusion:

  • Packaged entry
  • Dealing with compilation

src/platforms/web/runtime/index.js

We noticed above that Vue was introduced from this file, so there should be information about Vue here, so let’s move on.

/* @flow */

import Vue from 'core/index'
import config from 'core/config'
import { mountComponent } from 'core/instance/lifecycle'


// install platform patch function
// Install the update function, which converts vDOM to DOM
Vue.prototype.__patch__ = inBrowser ? patch : noop

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


export default Vue
Copy the code

Conclusion:

  • Install patch function to achieve VDOM -> DOM
  • Achieve $mount

src/core/index

The Vue of the above file is introduced from this file. This time, it has been turned into core. It should be the end of it.

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'

// Initialize the global API: vue. use/component/set...
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
Copy the code

Conclusion:

  • Initialize the global API

src/core/instance/index

Heck,, it must be the end of instance.

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')}// init, but where is _init?
  this._init(options)
}

// Initialize instance methods and properties
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue
Copy the code

Confused:

  • Constructor_initWhere did the method come from?

Conclusion:

  • Declaration of the Vue constructor
  • Initialize the properties and methods of the instance

src/core/instance/init

With that in mind, I clicked on the initMixin definition and found the answer.

export function initMixin(Vue: Class<Component>) {
  Vue.prototype._init = function (options? :Object) {
    const vm: Component = this;
    // a uid
    vm._uid = uid++;


    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    // User options are merged with system default 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
      );
    }
    /* istanbul ignore else */
    if(process.env.NODE_ENV ! = ="production") {
      initProxy(vm);
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    / / initialization
    vm._self = vm;

    // Declare period-dependent attributes to initialize $parent
    initLifecycle(vm);

    // Customize listening for component events
    initEvents(vm);

    // Slot processing, $createElement
    initRender(vm);

    // Call the hook function that declares the period
    callHook(vm, "beforeCreate");

    // Next is the component data and state initialization

    initInjections(vm); // resolve injections before data/props

    // data/props/methods/computed/watch
    initState(vm);

    initProvide(vm); // resolve provide after data/props

    callHook(vm, "created");

    // Mount with el option
    if(vm.$options.el) { vm.$mount(vm.$options.el); }}; }Copy the code

Conclusion:

  • Defines the initialization process for Vue instances
  • Initialization operations are performed in sequence
    • InitLifecycle – Initializes lifecycle related properties,$parent/$root/$ref/$childrenEtc.
    • InitEvents – Initializes the properties and functions that custom events listen for
    • InitRender – slot as well$createElementThe processing of
    • callHook(vm, “beforeCreate”); – callbeforeVreate
    • Initinjection – Injection Injection
    • InitState – Initializationdata/props/methods/computed/watch
    • InitProvide – Handles the Provide option
    • CallHook (VM, “created”) – Callscreated
    • If you have one of the choicesel, the call$mount

summary

Through the above series of tracking, we have a basic understanding of the process of New Vue, and at this time we can learn the answers to some common questions:


Q: What does new Vue({el: “#app”}) do?

A: Perform initialization -> get a root instance -> perform mount function -> get render function and execute -> generate VDOM -> convert vDOM to DOM via patch function -> append dom to # EL


Q: When the el, Render, and template options appear in an option, which one will be executed first?

Render > template > el, render function first


Q: Can injection and data be accessed in beforeCreate and Created?

A: Neither can be accessed in beforeCreate and neither can be accessed in Created