text: /* @flow */
  
  import {
    warn,
    nextTick,
    emptyObject,
    handleError,
    defineReactive,
  } from "../util/index";
  
  import { createElement } from "../vdom/create-element";
  import { installRenderHelpers } from "./render-helpers/index";
  import { resolveSlots } from "./render-helpers/resolve-slots";
  import { normalizeScopedSlots } from "../vdom/helpers/normalize-scoped-slots";
  import VNode, { createEmptyVNode } from "../vdom/vnode";
  
  import { isUpdatingChildComponent } from "./lifecycle";
  
  export function initRender(vm: Component) {
    // 虚拟 DOM
    vm._vnode = null; // the root of the child tree
    // 存储静态渲染的节点
    vm._staticTrees = null; // v-once cached trees
    const options = vm.$options;
  
    const parentVnode = (vm.$vnode = options._parentVnode); // the placeholder node in parent tree
    const renderContext = parentVnode && parentVnode.context;
    vm.$slots = resolveSlots(options._renderChildren, renderContext);
    vm.$scopedSlots = emptyObject;
    // bind the createElement fn to this instance
    // so that we get proper render context inside it.
    // args order: tag, data, children, normalizationType, alwaysNormalize
    // internal version is used by render functions compiled from templates
    vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
    // normalization is always applied for the public version, used in
    // user-written render functions.
    vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);
  
    // $attrs & $listeners are exposed for easier HOC creation.
    // they need to be reactive so that HOCs using them are always updated
    const parentData = parentVnode && parentVnode.data;
  
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== "production") {
      defineReactive(
        vm,
        "$attrs",
        (parentData && parentData.attrs) || emptyObject,
        () => {
          !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm);
        },
        true
      );
      defineReactive(
        vm,
        "$listeners",
        options._parentListeners || emptyObject,
        () => {
          !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm);
        },
        true
      );
    } else {
      defineReactive(
        vm,
        "$attrs",
        (parentData && parentData.attrs) || emptyObject,
        null,
        true
      );
      defineReactive(
        vm,
        "$listeners",
        options._parentListeners || emptyObject,
        null,
        true
      );
    }
  }
  
  export let currentRenderingInstance: Component | null = null;
  
  // for testing only
  export function setCurrentRenderingInstance(vm: Component) {
    currentRenderingInstance = vm;
  }
  
  export function renderMixin(Vue: Class<Component>) {
    // install runtime convenience helpers
    installRenderHelpers(Vue.prototype);
  
    Vue.prototype.$nextTick = function (fn: Function) {
      return nextTick(fn, this);
    };
  
    Vue.prototype._render = function (): VNode {
      const vm: Component = this;
      const { render, _parentVnode } = vm.$options;
  
      if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
          _parentVnode.data.scopedSlots,
          vm.$slots,
          vm.$scopedSlots
        );
      }
  
      // set parent vnode. this allows render functions to have access
      // to the data on the placeholder node.
      // 父节点的虚拟 DOM 引用
      vm.$vnode = _parentVnode;
      // render self
      let vnode;
      try {
        // There's no need to maintain a stack because all render fns are called
        // separately from one another. Nested component's render fns are called
        // when parent component is patched.
        currentRenderingInstance = vm;
        vnode = render.call(vm._renderProxy, vm.$createElement);
      } catch (e) {
        handleError(e, vm, `render`);
        // return error render result,
        // or previous vnode to prevent render error causing blank component
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== "production" && vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(
              vm._renderProxy,
              vm.$createElement,
              e
            );
          } catch (e) {
            handleError(e, vm, `renderError`);
            vnode = vm._vnode;
          }
        } else {
          vnode = vm._vnode;
        }
      } finally {
        currentRenderingInstance = null;
      }
      // if the returned array contains only a single node, allow it
      if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0];
      }
      // return empty vnode in case the render function errored out
      if (!(vnode instanceof VNode)) {
        if (process.env.NODE_ENV !== "production" && Array.isArray(vnode)) {
          warn(
            "Multiple root nodes returned from render function. Render function " +
              "should return a single root node.",
            vm
          );
        }
        vnode = createEmptyVNode();
      }
      // set parent
      vnode.parent = _parentVnode;
      return vnode;
    };
  }
  