The outline

At present, the community has a lot of Vue3 source code analysis article, but the quality level is not uniform, not systematic and comprehensive, always a knowledge point a knowledge point interpretation, so I read, there will be a fault, unable to integrate the whole Vue3 knowledge system, so can only own

And I built a Github I will be in my source code some notes, as well as some of my understanding of the source code, all put in this Github, not only for their own to review the old to learn new, but also to facilitate later partners can quickly understand the whole vUE system

Github address attached to vue source code parsing v3.2.26

The project includes mind map (will be updated gradually later), source annotations, simple version of handwriting principle, and help to understand the article, we hope you manual star

Without further ado, we are going to do the source guide, the source guide, will be divided into the following aspects:

  1. engineering
  2. componentization
  3. The renderer
  4. Responsive system
  5. The compiler
  6. Why read vue source code

Let’s do it one by one

engineering

learn(monorepo)

Monorepo means that the code for many projects is contained in a single code base of a version control system. These projects, while they may be related, are usually logically independent and maintained by different teams. To understand what he is, let’s use a picture

As you can see in the figure above, the general idea is that a repository has many packages, which can be packaged or distributed separately, and maintained by different people, which is the way many open source libraries are today

roullp

Rollup is a JavaScript module wrapper that compiles small chunks of code into large, complex chunks of code, and is currently the preferred packaging for libraries, Bundler

Typescirpt

Typescirpt, of course, has a hot community

CICD

Each project is different, interested can refer to

Dependency package Management

PNPM – Fast, disk space saving software package manager

Unit testing

Jest is an enjoyable JavaScript testing framework that focuses on simplicity

Code check

ESLint assembles JavaScript and JSX checking tools

The directory structure

Here we mainly introduce a few main core libraries, the source itself does not need to see details, we only need to understand the main principles, and can absorb some excellent code design and ideas

Learn build project, all directories are scattered in packages, and packages in packages can also use more important packages individually

  • Compiler-core (the core logic of the compiler)
  • Compiler-dom (dom platform compiler)
  • Vue-compiler-sfc (Parsing SFC components is similar to VUE-lorder in VUE2)
  • Reactivity (reactive)
  • Run-time core (runtime code, including renderer, VNode, scheduler)
  • Runtime-dom (DOM platform dependent)
  • Vue (type the directory of different packages at the end)
  • Shared (initialized variables, utility functions, etc.)

componentization

Another core idea of Vue3 is componentization. Componentization is to divide a page into multiple components, and the CSS, JavaScript, templates, images and other resources that each component relies on are developed and maintained together. Components are resource independent, reusable within the system, and nested among components.

When we develop real projects with Vue3, we write a bunch of components and assemble them into pages like building blocks. The vue.js website also spends a lot of time explaining what components are, how to write them, and the properties and features they have.

Of course, the concept of componentization is not unique to VUE3, it has been around for a long time

Component nature

Back in the days of JQuery, the concept of a template engine was something you’ve heard of for years.

For example

import { template } from 'lodash'

const compiler = template('<h1><%= title %></h1>')
const html = compiler({ title: 'My Component' })

document.getElementById('app').innerHTML = html
Copy the code

The above code is a template engine for LoDash, and its essence is: Template + data =HTML. I’m sure many of the old front ends know that back in the days when the front and back end didn’t separate, templates were the fate of most Web sites. The template SRR solution of that era was essentially a template engine

However, the template engine has changed in the vUE and React era

Template + Data =Vdom He introduced the concept of Virtual DOM

In Vue 3, our template is abstracted into the render function, which is our template, for example:

<div id="demo">
  <div @click="handle">Click on the switch</div>
  <div @click="handle1">Click Toggle 1</div>
  <div v-if="falg">{{num}}</div>
  <div v-else>{{num1}}</div>
</div>
Copy the code

So he’s going to end up compiling something like this

const _Vue = Vue
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue

const _hoisted_1 = ["onClick"]
const _hoisted_2 = ["onClick"]
const _hoisted_3 = { key: 0 }
const _hoisted_4 = { key: 1 }

return function render(_ctx, _cache) {
  with (_ctx) {
    const { createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode, Fragment: _Fragment } = _Vue

    return (_openBlock(), _createElementBlock(_Fragment, null, [
      _createElementVNode("div", { onClick: handle }, "Click to switch".8 /* PROPS */, _hoisted_1),
      _createElementVNode("div", { onClick: handle1 }, "Click toggle 1".8 /* PROPS */, _hoisted_2),
      falg
        ? (_openBlock(), _createElementBlock("div", _hoisted_3, _toDisplayString(num), 1 /* TEXT */))
        : (_openBlock(), _createElementBlock("div", _hoisted_4, _toDisplayString(num1), 1 /* TEXT */)),64 /* STABLE_FRAGMENT */))}}Copy the code

The result of the render function should be a VDOM

Virtual DOM

The Virtual DOM is a JS object, for example

{
    tag: "div".props: {},
    children: [
        "Hello World", 
        {
            tag: "ul".props: {},
            children: [{
                tag: "li".props: {
                    id: 1.class: "li-1"
                },
                children: ["The first".1]}]}Copy the code

It corresponds to the DOM of expression

<div>
    Hello World
    <ul>
        <li id="1" class="li-1">1</li>
    </ul>
</div>
Copy the code

Why did the component go from producing HTML directly to producing the Virtual DOM? The reason is that Virtual DOM brings layered design, it abstracts the rendering process, makes the framework can render to the Web (browser) outside of the platform, and can achieve SSR, etc., and is not Virtual DOM’s performance is good

React test results are faster than React test results.

Components in VUE

In our daily writing components are as follows:


 <template>
    <div>This is a component {{num}}</div>
</template>
<script>
export default {
name:home
  setup(){
    const num=ref(1)
    return {num}
  }
};
</script>
Copy the code

The result is as follows:

const home={
	 setup(){
    const num=ref(1)
     function handleClick() {
        num.value++
      }
    	return {num,handleClick}
  },
  render(){
   with (_ctx) {
    const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
    return (_openBlock(), _createElementBlock("div", { onClick: handleClick }, "This is a component." + _toDisplayString(num), 9 /* TEXT, PROPS */, _hoisted_1))
  }
  }
}
Copy the code

In fact, you discover that it is a configuration object, which contains the data, the methods to manipulate the data, and the compiled template template functions

And when we use it, we use it as a tag reference

<template>
  <div>
    <home></home>
    </div>
</template>
Copy the code

The final compiled result

const elementVNode = {
  tag: 'div'.data: null.children: {
    tag: home,
    data: null}}Copy the code

This way, our components can participate in the VDOM, and our entire page can be rendered as a VDOM tree containing components, which can be assembled into a page by building blocks, as shown below:

The renderer

The so-called renderer is simply a tool (a function, usually called render) that forms a Virtual DOM from building blocks and renders it into a real DOM under a specific platform. The workflow of the renderer is divided into two stages: Mount and patch, if the old VNode exists, the new VNode is compared with the old VNode in an attempt to complete the DOM update with minimal resource overhead. This process is called patch, or “patching”. If the old VNode does not exist, the new VNode is directly mounted to the new DOM, a process called mount.

Before we do that, let’s look at the Virtual DOM categories.

Vue also represents vNode types in binary

export const enum ShapeFlags {
  ELEMENT = 1.// Common node
  FUNCTIONAL_COMPONENT = 1 << 1.//2 // function components
  STATEFUL_COMPONENT = 1 << 2.//4 // Common components
  TEXT_CHILDREN = 1 << 3.//8 // Text child node
  ARRAY_CHILDREN = 1 << 4.//16 // Array child node
  SLOTS_CHILDREN = 1 << 5./ / 32
  TELEPORT = 1 << 6.//64 // Portal
  SUSPENSE = 1 << 7.//128 // Can be asynchronous in components
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8./ / 256
  COMPONENT_KEPT_ALIVE = 1 << 9.//512// keepALIVE
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 6 represents functional components and normal components
}
Copy the code

With these type distinctions, we can perform different mount logic and patch logic through different types

  switch (type) {
      // Text node
      case Text:
        processText(n1, n2, container, anchor)
        break
      // Comment the node
      case Comment:
        processCommentNode(n1, n2, container, anchor)
        break
      // Static node, this should be used in SSR
      // Static vNodes are common only in SSR
      case Static:
        if (n1 == null) {
          mountStaticNode(n2, container, anchor, isSVG)
        } else if (__DEV__) {
          patchStaticNode(n1, n2, container, isSVG)
        }
        break
      / / fragments
      case Fragment:
        processFragment(
          n1,
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          slotScopeIds,
          optimized
        )
        break
      default:
        // Remove the special case node rendering, which is normal vNode rendering
        // If it is a node type
        if (shapeFlag & ShapeFlags.ELEMENT) {
          processElement(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
          // If it is a component type
          // The first time the mount is performed, it is initialized as a component type
          // After the vue3 revision, the component vNode is created directly with the configuration of common objects
          // This configuration requires a function to fetch, which is also dynamically loaded
          // The incoming name is picked up at runtime by resolvecompinent
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
          // If it is a portal
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          ; (type as typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
          Suspense // Suspense experimental content
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          ; (type as typeof SuspenseImpl).process(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        }
    }
Copy the code

So to this, the main method of the whole renderer patch core logic is explained to this, we intend to understand the whole source system, and context, do not tangle with the specific implementation, we also need to find the specific implementation in the source code.

Component mounted

Mount components is the initialization of the vnode node component types include reactive initialization, rely on collection, life cycle, compilation and so on, so it is necessary for us to know about the whole process, the components mounted to study the components mounted process, let us more clearly understand how the data in a vue and render function binding.

We removed the core logic of the renderer to explain the process of initialization of the entire component


        const nodeOps = {
            insert: (child, parent) = > {
                parent.insertBefore(child, null)},createElement: (tag) = > {
                return document.createElement(tag)
            },
            setElementText: (el, text) = > {
                el.textContent = text
            },
        }
        // We only consider the patch event
        function patchEvent(el, key, val) {
            el.addEventListener(key.slice(2).toLowerCase(), function () {
                val()
            })
        }
        / / processing props
        function patchProp(el, key, val) {
            patchEvent(el, key, val)
        }
        // check if it is ref
        function isRef(r) {
            return Boolean(r && r.__v_isRef === true)}// Return the correct value
        function unref(ref) {
            return isRef(ref) ? ref.value : ref
        }
        const toDisplayString = (val) = > {
            return val == null
                ? ' '
                : String(val)
        }
        const computed = Vue.computed
        Vue.computed = function (fn) {
            const val = computed(fn)
            // 模拟拿到effect
            recordEffectScope(val.effect)
            return val
        }
        const ref = Vue.ref
        Vue.ref = function (val) {
            const newRef = ref(val)
            // 模拟拿到effect
            recordEffectScope(val.effect)
            return newRef
        }
        The current function is used in computed, watch, watchEffect, and so on
        function recordEffectScope(effect, scope) {
            scope = scope || activeEffectScope
            if (scope && scope.active) {
                scope.effects.push(effect)
            }
        }
        // Internal use of vUE
        // Use EffectScope inside the page to unload dependencies when the component is destroyed

        // The current instance
        let currentInstance = null
        // Set the current instance
        const setCurrentInstance = (instance) = > {
            currentInstance = instance
            instance.scope.on()
        }
        function parentNode(node) {
            return node.parentNode
        }
        // error tolerance
        function callWithErrorHandling(fn, instancel, args) {
            let res
            try{ res = args ? fn(... args) : fn() }catch (err) {
                console.error(err)
            }
            return res
        }
        // create a response
        function proxyRefs(objectWithRefs) {
            return new Proxy(objectWithRefs, {
                get: (target, key, receiver) = > {
                    return unref(Reflect.get(target, key, receiver))
                },
                set: (target, key, value, receiver) = > {
                    return Reflect.set(target, key, value, receiver)
                }
            })
        }
        // Assign the render function
        function finishComponentSetup(instance) {
            Component = instance.type
            // There may be a compilation process, temporarily omitted
            instance.render = Component.render
        }
        function handleSetupResult(instance, setupResult) {
            instance.setupState = proxyRefs(setupResult)
            // There is also compiled content below
            finishComponentSetup(instance)
        }
        // Create a component instance
        function createComponentInstance(vnode, parent) {
            // Get the type
            const type = vnode.type
            const instance = {
                vnode,
                type,
                parent,
                // An EffectScope has been created to handle dependencies in batches
                scope: new EffectScope(true /* detached */),
                render: null.subTree: null.effect: null.update: null,}return instance
        }
        // Component-type processing, including mount and update
        function processComponent(n1, n2, container) {
            if (n1 == null) {
                mountComponent(n2, container)
            } else {
                updateComponent()
            }

        }
        const processText = (n1, n2, container, anchor) = > {
            if (n1 == null) {
                nodeOps.insert(
                    (n2.el = createText(n2.children)),
                    container
                )
            }
        }
        // The node is initialized
        function mountElement(n2, container, parentComponent) {
            const { type, props, children, shapeFlag } = n2
            let el = nodeOps.createElement(type)

            // Determine the content with text child node
            if (n2.shapeFlag === 9) {
                nodeOps.setElementText(el, children)
            }
            // if there is a case for props
            if (props) {
                for (key in props) {
                    // Note that only events are handled, not styles
                    patchProp(el, key, props[key])
                }
            }
            nodeOps.insert(el, container)
        }
        // Node type processing
        function processElement(n1, n2, container, parentComponent) {
            if (n1 == null) {
                // If not, the type of mountelement is used
                mountElement(
                    n2,
                    container,
                    parentComponent
                )
            } else {
                // Next is the component update, which follows the content of patch, including the diff, which is the most core content}}// Component destruction method
        function unmountComponent() {}/ / create a vnode
        function createVNode(type, props, children, shapeFlag = 1) {
            if (typeof children === "string") {
                shapeFlag = 9
            }
            const vnode = {
                type,
                props,
                children,
                shapeFlag,
                component: null,}return vnode
        }
        // Patch contains the mount of various components, nodes, comments, etc. Here we analyze only the mount of components in order to analyze dependency tracing
        function patch(n1, n2, container, parentComponent = null) {
            // If it is equal, it is the same
            if (n1 === n2) {
                return
            }
            const { type, shapeFlag } = n2
            switch (type) {

                // We only deal with component types and common node types

                default:
                    if (shapeFlag & 1) {
                        processElement(n1, n2, container, parentComponent)
                    } else {
                        processComponent(n1, n2, container)
                    }


            }
        }
        function setupStatefulComponent(instance) {
            const Component = instance.type
            const { setup } = Component
            if (setup) {
                setCurrentInstance(instance)
                // Get the setup result
                const setupResult = callWithErrorHandling(
                    setup,
                    instance,
                    [instance.props]
                )
                // We only consider the return object case, not the other case
                handleSetupResult(instance, setupResult)
            }
        }
        // Execute the setup function
        function setupComponent(instance) {
            // Execute the core logic
            const setupResult = setupStatefulComponent(instance)
            return setupResult
        }
        / / get the vnode
        function renderComponentRoot(instance) {
            // The source code uses a Proxy to Proxy all responsive content access and modification and stored in instance. Proxy, unified processing
            // We only need setupState for the main process
            const {
                type: Component,
                render,
                setupState,
            } = instance
            debugger
            result = render.call(
                setupState,
                setupState,
            )
            return result
        }
        // Rely on collection to generate effect
        function setupRenderEffect(instance, n2, container) {
            const componentUpdateFn = () = > {
                const nextTree = renderComponentRoot(instance)
                const prevTree = instance.subTree
                instance.subTree = nextTree
                // This is the patch diff inside the component
                patch(
                    prevTree,
                    nextTree,
                    container,
                    instance
                )
            }
            // Call the internal ReactiveEffect
            const effect = (instance.effect = new Vue.ReactiveEffect(
                componentUpdateFn,
                null,
                instance.scope // Track it in component's effect scope
            ))
            const update = (instance.update = effect.run.bind(effect))
            update()
        }

        // Component initialization method
        function mountComponent(initialVNode, container, parentComponent = null) {
            const instance =
                (initialVNode.component = createComponentInstance(
                    initialVNode,
                    parentComponent,
                ))
            / / the setup
            setupComponent(instance)
            // Rely on collection
            setupRenderEffect(instance, initialVNode, container)
        }

        // Component update
        function updateComponent() {
            // Update logic
        }
        // The initialization of the render component is basically executing the path
        function createApp(rootComponent, rootProps = null) {
            // We pass the value directly
            const vnode = createVNode(rootComponent, rootProps, null.4)
            const mount = (container) = > {
                if (typeof container === 'string') {
                    container = document.querySelector(container)
                }
                patch(null, vnode, container)
            }
            return { mount }

        }
        / / initialization
        createApp({
            setup() {
                const num = Vue.ref(1)
                console.log(num)
                const double = Vue.computed(() = > {
                    console.log(1111)
                    debugger
                    return num.value * 2
                })
                function handleClick() {
                    num.value++
                }
                return {
                    num,
                    double,
                    handleClick
                }
            },
            render(_ctx) {
                with (_ctx) {
                    return createVNode("div", { onClick: handleClick }, toDisplayString(double))
                }

            }
        }).mount('#app')
        
Copy the code

The above code you can paste to run, you can also view on Github

Just to explain the core logic, we createApp, we pass in the current component configuration, we initialize the component with patch, we initialize the component with setupComponent, The instance this is not passed in through call, so this is not available in the function.

We then execute setupRenderEffect to start the dependency collection, and execute the Render method in the current method to trigger a reactive GET in setup to trigger the dependency collection process, which we’ll discuss in more detail in the reactive section later

ReactiveEffect

Reactiveeffects are at the heart of reactive systems, and reactive systems are at the heart of VUe3, so we have to understand what reactiveeffects are all about

Reactive ReactiveEffect is the core of reactive, and is responsible for collecting and updating dependencies.

// Record the current active object
let activeEffect
// Mark whether to trace
let shouldTrack = false

class ReactiveEffect{
active = true // Whether it is in the active state
deps = [] // All responsive objects that depend on this effect
onStop = null // function
constructor(fn, scheduler) {
  this.fn = fn // Callback functions, for example, computed(/* fn */() => {return testref.value ++})
  // function type. If it is not null then effect. Scheduler is executed in TriggerRefValue, otherwise effect
  this.scheduler = scheduler
}

run() {
  // If this effect does not need to be collected by reactive objects
  if(!this.active) {
    return this.fn()
  }

  // Two utility functions are used here: pauseTracking and enableTracking to change the state of shouldTrack
  shouldTrack = true
  activeEffect = this
  
  ActiveEffect dependencies can be collected in the method after the activeEffect is set
  const result = this.fn()
  
  shouldTrack = false
  // Empty the currently active effects after executing the side effects function
  activeEffect = undefined

  return result
}

// Pause the trace
stop() {
  if (this.active) {
    // Find all responsive objects that depend on this effect
    // Remove effect from these reactive objects
    cleanupEffect(this)
    // Execute the onStop callback
    if (this.onStop) {
      this.onStop();
    }
    this.active = false; }}}Copy the code

This.fn (), at the component level, is a render function that triggers updates by re-executing the current method

In the whole reactive system, he established the relationship between reactive data and ReactiveEffect through a DEP, in which ReactiveEffect is stored. A reactive data has a DEP steward that manages the reactiveeffects associated with it. When the reactive data changes, the DEP steward is triggered to batch the reactiveeffects in it for updating. Note that in componentification, A component has only one render ReactiveEffect.

When it comes to implementing Render, you have to think of Diff

Diff in the renderer

As mentioned above, the purpose of using the Virtual DOM is not performance, but cross-platform. Therefore, when the page has a large number of content updates, performance cannot be guaranteed, and an algorithm is needed to reduce the performance overhead of DOM operations

The core of the basic principle of diFF algorithm on the market is diFF same-level comparison, not cross-level comparison, which can greatly reduce the calculation of JS, and there are different schools on the core algorithm of same-level comparison

  • ReactA series of diff algorithms – find the nodes to move from front to back
  • Double diff- Walk from both ends to the middle to find the node to move
  • Longest recursive subsequence diff– Find the node to move by solving the longest recursive subsequence

Vue3 currently uses Inferno and the core algorithm is the longest recursive subsequence

Here we are not redundant, the whole process of comparison, a lot of big guys have also analyzed, we are just an introduction, do not do in-depth exploration, intended to master a vUE source system together with you

Responsive system

Responsiveness — This term is thrown around a lot in programming, but what does it mean? Responsiveness is a programming paradigm that allows us to adapt to change in a declarative way.

The principle of responsiveness in VUE3 is described in the Proxy documentation

  • Trace when a value is read: the proxygetIn the processing functiontrackThe function records the property and the current side effect.
  • Detect when a value changes: Invoked on proxysetProcessing functions.
  • Rerun the code to read the original value:triggerThe function finds which side effects depend on the property and executes them.

The proxied objects are not visible to the user, but internally, they enable Vue to do dependency tracking and change notification in case the value of the property is accessed or modified.

So what did he tell you? Reactiveeffect.run reactiveEffect.run ReactiveEffect.run ReactiveEffect.run reactiveEffect.run

We have also prepared a working code, you can also paste down, practical experience

 // Save temporary dependency functions for wrapping
        const effectStack = [];

        // Dependency map objects can only accept objects
        let targetMap = new WeakMap(a);// Determine if it is an object
        const isObject = (val) = >val ! = =null && typeof val === 'object';
        // function of ref
        function ref(val) {
            Value ->proxy = value->proxy = value->proxy
            // We don't use value access in the object case
            return isObject(val) ? reactive(val) : new refObj(val);
        }

        // Create a responsive object
        class refObj {
            constructor(val) {
                this._value = val;
            }
            get value() {
                // Trigger GET after the first execution to collect dependencies
                track(this.'value');
                return this._value;
            }
            set value(newVal) {
                console.log(newVal);
                this._value = newVal;
                trigger(this.'value'); }};// We will not consider nested objects in the object for the sake of understanding the principle
        // The object is reactive
        function reactive(target) {
            return new Proxy(target, {
                get(target, key, receiver) {
                    // Reflect is used to perform the default action on an object, more formally and functionally
                    // Both Proxy and Object methods Reflect
                    const res = Reflect.get(target, key, receiver);
                    track(target, key);
                    return res;
                },
                set(target, key, value, receiver) {
                    const res = Reflect.set(target, key, value, receiver);
                    trigger(target, key);
                    return res;
                },
                deleteProperty(target, key) {
                    const res = Reflect.deleteProperty(target, key);
                    trigger(target, key);
                    returnres; }}); }// At this point, the current ref object is already listening for data changes
        const newRef = ref(0);
        // But there is still no responsiveness, so how does it achieve responsiveness ---- rely on collection, trigger updates =
        // for dependency collection
        // In order to generalize the method in the source code, it also passes in many parameters to be compatible with different situations
        // We want to understand the principle, just need to wrap fn
        function effect(fn) {
            // Wrap the current dependency function
            const effect = function reactiveEffect() {
                // Error handling is also included in the mock source code, in order to prevent you from writing errors, which is the genius of the framework
                if(! effectStack.includes(effect)) {try {
                        // Put the current function on a temporary stack, in order to trigger get in the following execution, can be found in the dependency collection of the current variable dependencies to establish relationships
                        effectStack.push(fn);
                        // Execute the current function and start to rely on the collection
                        return fn();
                    } finally {
                        // Execute successfully unstackeffectStack.pop(); }}; }; effect(); }// Establish relationships in collected dependencies
        function track(target, key) {
            // Retrieve the last data content
            const effect = effectStack[effectStack.length - 1];
            // If the current variable has dependencies
            if (effect) {
                // Check whether target exists in the current map
                let depsMap = targetMap.get(target);
                // If not
                if(! depsMap) {// New Map stores the current WeakMap
                    depsMap = new Map(a); targetMap.set(target, depsMap); }// Get the set of response functions corresponding to key
                let deps = depsMap.get(key);
                if(! deps) {// Establish the relationship between the current key and its dependencies, because a key can have multiple dependencies
                    // To prevent duplicate dependencies, use set
                    deps = new Set(a); depsMap.set(key, deps); }// Store the current dependency
                if(! deps.has(effect)) { deps.add(effect); }}}// Used to trigger updates
        function trigger(target, key) {
            // Get all dependencies
            const depsMap = targetMap.get(target);
            // If there are dependencies, pull them out
            if (depsMap) {
                // Get the set of response functions
                const deps = depsMap.get(key);
                if (deps) {
                    // Execute all response functions
                    const run = (effect) = > {
                        // The source code has asynchronous scheduling tasks, which we omit here
                        effect();
                    };
                    deps.forEach(run);
                }
            }
        }
        effect(() = > {
            console.log(11111);
            // In my implementation of effect, set cannot be triggered because it is not compatible to demonstrate the principle, otherwise it will be an infinite loop
            // The vue source code triggers the compatibility process in effect only once
            newRef.value;
        });

        newRef.value++;
Copy the code

In the above code, there is no render effect because if you use render effect it’s too big and you get confused, so we use a user effect in the Composition API, which is similar to Vue3, The difference is that render effect is a ReactiveEffect and user effect is a function that we pass in, and they all end up in the DEPS butler

The compiler

The compiler plays a big role in vuE3’s performance improvement. Due to the traversability of the template, there are many optimizations that can be made during compilation. Before that, let’s briefly outline the basic flow of the compiler

For those of you who know the compiler workflow, a complete compiler workflow looks like this:

  • First of all,parseThe raw code string is parsed to generate the abstract syntax tree AST.
  • Second,transformTransform the abstract syntax tree into a structure closer to the target DSL.
  • In the end,codegenGenerate the target “DSL” from the transformed abstract syntax tree (A programming language with limited expressiveness designed for a specific domain) executable code.

That’s the same thing with vUE

The Parse phase

The parse function takes place by converting a template to an AST. It uses regular expressions to match the string in the template. The result is a crude AST object, which is an abstract representation of the source code syntax. It represents the syntactic structure of a programming language as a tree, with each node in the tree representing a structure in the source code. In our VUE compilation, it’s just a JS object

For example:

<template>
  <p>hello World!</p>
</template>

Copy the code

Into the ast

{
  "type": 0."children": [{"type": 1."ns": 0."tag": "template"."tagType": 0."props": []."isSelfClosing": false."children": [{"type": 1."ns": 0."tag": "p"."tagType": 0."props": []."isSelfClosing": false."children": [{"type": 2."content": "hello World!"."loc": {
                "start": {
                  "column": 6."line": 2."offset": 16
                },
                "end": {
                  "column": 18."line": 2."offset": 28
                },
                "source": "hello World!"}}]."loc": {
            "start": {
              "column": 3."line": 2."offset": 13
            },
            "end": {
              "column": 22."line": 2."offset": 32
            },
            "source": "

hello World!

"
}}]."loc": { "start": { "column": 1."line": 1."offset": 0 }, "end": { "column": 12."line": 3."offset": 44 }, "source": "<template>\n <p>hello World!</p>\n</template>"}}]."helpers": []."components": []."directives": []."hoists": []."imports": []."cached": 0."temps": 0."loc": { "start": { "column": 1."line": 1."offset": 0 }, "end": { "column": 1."line": 4."offset": 45 }, "source": "<template>\n <p>hello World!</p>\n</template>\n"}}Copy the code

If you are interested, go to the AST Explorer and you can see the AST generated by parsing javascript code from different Parsers online.

The Transform phase

In the Transform phase, Vue performs some transformation operations on the AST, and further processes some hoistStatic static elevations of UE and The PatchFlags patch identification of the cacheHandlers cache function

Codegen phase

Generating the render function

Vue3 compiler optimization strategy

As we’ve said before, the Transform phase does a lot of optimization, so let’s go through the details and start with a compiled piece of code

const _Vue = Vue
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue

const _hoisted_1 = ["onClick"]
const _hoisted_2 = ["onClick"]
const _hoisted_3 = /*#__PURE__*/_createElementVNode("div".null."Static node", -1 /* HOISTED */)

return function render(_ctx, _cache) {
 with (_ctx) {
   const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

   return (_openBlock(), _createElementBlock(_Fragment, null, [
     _createCommentVNode("
      
\n comment \n
"
), _createElementVNode("div", { onClick: handle }, "Click to switch".8 /* PROPS */, _hoisted_1), _createElementVNode("div", { onClick: handle1 }, "Click toggle 1".8 /* PROPS */, _hoisted_2), _hoisted_3, _createElementVNode("div".null, _toDisplayString(num1) + "Dynamic node".1 /* TEXT */)].64 /* STABLE_FRAGMENT */))}}Copy the code

1. Static nodes are promoted

Static nodes are promoted to prevent patches from being processed at diff and thus flagged at compile time, so that static content is excluded from render and prevented from being created again when render is executed

   // Use closures to place the result within render to prevent the current static node from being created by repeating render
 const _hoisted_3 = /*#__PURE__*/_createElementVNode("div".null."Static node", -1 /* HOISTED */)
Copy the code

2. Patch marking and dynamic attribute recording

_createElementVNode("div", {onClick: handle}, "toggle ", 8 /* PROPS */, _hoisted_1),Copy the code

There is an 8 in the above code, which is patchFlag, and all its types are shown as follows:

export const enum PatchFlags {
// Dynamic text node
TEXT = 1.// 2 Dynamic class
CLASS = 1 << 1.// 4 dynamic style
STYLE = 1 << 2./ / dynamic props
PROPS = 1 << 3./** * indicates an element with an item with a dynamic key point. When the key is changed, a complete * always requires diff to remove the old key. This flag is mutually exclusive with class, style and props. * /
// Has a dynamic key property. When the key changes, a full diff is required
FULL_PROPS = 1 << 4.// 32 Nodes with listening events
HYDRATE_EVENTS = 1 << 5.// 64 A fragment that does not change the order of child nodes
STABLE_FRAGMENT = 1 << 6.// 128 Fragment with key
KEYED_FRAGMENT = 1 << 7.// 256 Fragment without key
UNKEYED_FRAGMENT = 1 << 8.// 512 a child node will only compare non-props
NEED_PATCH = 1 << 9.// 1024 Dynamic slot
DYNAMIC_SLOTS = 1 << 10.// The following are special, i.e., skipped in the diff phase
// 2048 represents a fragment that was created only because the user placed comments at the root level of the template, a flag that is used only for development because the comments are stripped out in production
DEV_ROOT_FRAGMENT = 1 << 11.// Static node, whose contents never change, no diff required
HOISTED = -1.// The diff used to indicate a node should end
BAIL = -2
}
Copy the code

After having a patchFlag, special diff logic can be executed according to the type of patchFlag, which can prevent the performance waste caused by full diff

3. Cache event handlers

By default, onClick is treated as a dynamic binding, so it is tracked every time it changes, but because it is the same function, it is not tracked and can be cached and reused

/ / template
<div> <button @click = 'onClick'>Am I</button> </div>

/ / the compiled
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div".null, [
    _createElementVNode("button", {
      onClick: _cache[0] || (_cache[0] = (. args) = >(_ctx.onClick && _ctx.onClick(... args))) },"点我")))}// Check the console for the AST
Copy the code

In the above code, there is no type of patchFlag, that is, patchFlag is a default value of 0. In this case, no diff is performed

  if (patchFlag > 0) {
    
      // The presence of patchFlag indicates the element's rendering code
      // Generated by the compiler, can take a fast path.
      // In this path, the old node and the new node are guaranteed to have the same shape
      // (that is, in exactly the same place in the source template)
      if (patchFlag & PatchFlags.FULL_PROPS) {
       
        patchProps(
          el,
          n2,
          oldProps,
          newProps,
          parentComponent,
          parentSuspense,
          isSVG
        )
      } else {
        // class
   
        if (patchFlag & PatchFlags.CLASS) {
          if(oldProps.class ! == newProps.class) { hostPatchProp(el,'class'.null, newProps.class, isSVG)
          }
        }

        // style
        // this flag is matched when the element has dynamic style bindings
        if (patchFlag & PatchFlags.STYLE) {
          hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
        }

     
        if (patchFlag & PatchFlags.PROPS) {
          // if the flag is present then dynamicProps must be non-null
          const propsToUpdate = n2.dynamicProps!
          for (let i = 0; i < propsToUpdate.length; i++) {
            const key = propsToUpdate[i]
            const prev = oldProps[key]
            const next = newProps[key]
            // #1471 force patch value
            if(next ! == prev || key ==='value') {
              hostPatchProp(
                el,
                key,
                prev,
                next,
                isSVG,
                n1.children as VNode[],
                parentComponent,
                parentSuspense,
                unmountChildren
              )
            }
          }
        }
      }

      // text
      // This flag is matched when the element has only dynamic text children.
      if (patchFlag & PatchFlags.TEXT) {
        if(n1.children ! == n2.children) { hostSetElementText(el, n2.childrenas string)
        }
      }
    } else if(! optimized && dynamicChildren ==null) {
      // unoptimized, full diff
      patchProps(
        el,
        n2,
        oldProps,
        newProps,
        parentComponent,
        parentSuspense,
        isSVG
      )
    }
Copy the code

In the above code, different diff logic is implemented according to the type of patchFlag. Once the patchFlag diff is removed, the old VNode will be reused directly, and the events of the old Vnode will be cached, so we can directly use it, saving the cost of re-creating the packaging function. Many people may not understand what I say, paste the vUE code, you will understand:

export function patchEvent(el: Element & { _vei? : Record<string, Invoker |undefined> },
 rawName: string,
 prevValue: EventValue | null,
 nextValue: EventValue | null,
 instance: ComponentInternalInstance | null = null
) {
 // vei = vue event invokers
 const invokers = el._vei || (el._vei = {})
 const existingInvoker = invokers[rawName]
 if (nextValue && existingInvoker) {
   // patch
   existingInvoker.value = nextValue
 } else {
   const [name, options] = parseName(rawName)
   if (nextValue) {
     // Wrap the event function first, then bind the wrapper function to the DOM
     // If caching is enabled, the current wrapper function will be cached, saving the overhead of diff
     const invoker = (invokers[rawName] = createInvoker(nextValue, instance))
     addEventListener(el, name, invoker, options)
   } else if (existingInvoker) {
     // remove
     removeEventListener(el, name, existingInvoker, options)
     invokers[rawName] = undefined}}}Copy the code

4. Piece of block

Create openBlock(), open a block, createBlock creates a block, save the dynamic parts of the block in a property called dynamicChildren. In the future, when the module is updated, only the dynamicChildren will be updated. In this way, patch can only process dynamic child nodes, so as to improve performance. We also combed the code:

   /** * The main idea is to skillfully use the stack structure to put dynamic nodes into dynamicChildren **/
        let blockStack = []
        let currentBlock = null
        // Initializers put the block's child dynamic elements on the stack using the stack structure
        function _openBlock(disableTracking = false) {
            blockStack.push((currentBlock = disableTracking ? null:)} [])function closeBlock() {
            blockStack.pop()
            currentBlock = blockStack[blockStack.length - 1] | |null
        }
        function setupBlock(vnode) {
            vnode.dynamicChildren = currentBlock
            closeBlock()
            return vnode
        }
        function createVnode(type, porps, children, isBlockNode = false.) {
            const vnode = {
                type,
                porps,
                children,
                isBlockNode,
                dynamicChildren: null
            }
            if(! isBlockNode && currentBlock) { currentBlock.push(vnode) }return vnode
        }
        function _createElementBlock(type, porps, children) {
            // Passing true directly indicates that the current vNode is the start of a current block
            return setupBlock(createVnode(type, porps, children, true))}// We assume source is a number
        function _renderList(source, renderItem,) {
            ret = new Array(source)
            for (let i = 0; i < source; i++) {
                ret[i] = renderItem(i + 1)}return ret
        }
        / / generated vnode
        function render(ctx) {
            return (_openBlock(), _createElementBlock('Fragment'.null, [
                createVnode("div".null."11111".true/* CLASS */),
                createVnode("div".null, ctx.currentBranch, /* TEXT */),
                (_openBlock(true), _createElementBlock('Fragment'.null, _renderList(5.(i) = > {
                    return (_openBlock(), _createElementBlock("div".null, i, /* TEXT */}})))))))console.log(render({ currentBranch: An old hero and a new costraint }))
Copy the code

You can copy it and see how dynamicChildren are generated

Why read vue source code

  • 1. It may be the most advanced engineering solution on the market
  • 2. The best learning material for TS
  • 3, standard and maintainable code quality, elegant code skills,
  • 4. Targeted performance optimization and packaging during development
  • 5, faster positioning of problems encountered in the work

1, 2, 5, we’re not going to talk too much about it, but we’re going to focus on 3 and 4 for two examples

Specification of maintainable code quality, elegant coding techniques

  // Plugin mechanics tips
     // a global variable
       let compile
       function registerRuntimeCompiler(_compile) {
           // Assign to the current template compiler so that other functions within the current template can be called
           // This registration method is required for use in the Runtime
           compile = _compile
       }
       // When the code is exported, it only needs to be registered in non-Runtime versions using the registration method
       //finishComponentSetup contains compilation logic inside
       function finishComponentSetup(instance) {
           Component = instance.type
           // Just check whether there is compile and render
           if(compile && ! Component.render) {// Execute the compile logic}}Copy the code
// Vue3
function makeMap ( str, expectsLowerCase ) {
       var map = Object.create(null);
       var list = str.split(', ');
       for (var i = 0; i < list.length; i++) {
           map[list[i]] = true;
       }
       return expectsLowerCase
           ? function (val) { return map[val.toLowerCase()]; }
           : function (val) { returnmap[val]; }}var isHTMLTag = makeMap(
       'html,body,base,head,link,meta,style,title,' +
       'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
       'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
       'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
       's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
       'embed,object,param,source,canvas,script,noscript,del,ins,' +
       'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
       'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
       'output,progress,select,textarea,' +
       'details,dialog,menu,menuitem,summary,' +
       'content,element,shadow,template,blockquote,iframe,tfoot'
   );
   var isHTMLTag = isHTMLTag('div');
Copy the code
// Function extension technique

       function createAppAPI(rootComponent, rootProps = null) {
           const mount = (container) = >{}return { mount }

       }
       function createRenderer() {
           return {
               createApp: createAppAPI()
           }
       }
       function createApp(. args) {
           constapp = createRenderer().createApp(... args)const { mount } = app
           app.mount = () = > {
               console.log('Execute your own logic')
               mount()
           }
       }
Copy the code

Development can be targeted performance optimization, as well as packaging

// Implement a popover encapsulation technique
CreateVNode Render generates the real DOM
const { createVNode,  render, ref } = Vue
       const message = {
           setup() {
               const num = ref(1)
               return {
                   num
               }
           },
           template: ` < div > < div > {{num}} < / div > < div > this is a popup window < / div > < / div > `
       }
       // Generate an instance
       const vm = createVNode(message)
       const container = document.createElement('div')
       // Change from patch to DOM
       render(vm, container)
       document.body.appendChild(container.firstElementChild)
Copy the code

The last

Vue source system guidance, I uneducated, all the understanding of this, if there is a mistake, please big guys criticism.

Here, on a higher level, to instill a little honesty

Is that I recently found in the community, a lot of people are particularly impetuous, think that understand the source code is much less, do not understand the source code is dish chicken, here I want to say a little bit I see the source code a little bit of feeling:

  • 1, read the source code does not necessarily explain how strong you are, because he is not you write, he is reading with reading can not make you make more money the same truth

  • 2, look at the source code can let us know what code is good, there is no doubt that vUE source code baseline, is a lot of people may write a lifetime can not reach the height

  • Look at the purpose of the source code, is to let us prevent closed doors, toward a correct direction to work

  • 4, VUE source code is just a part of our discipline, the whole front-end ecology is very complex, if it is not interested in the source code (principle or to understand, to deal with the interview), there is no need to worry, to learn what you are interested in hone your craft.