text: /* @flow */
  
  import VNode from "./vnode";
  import { resolveConstructorOptions } from "core/instance/init";
  import { queueActivatedComponent } from "core/observer/scheduler";
  import { createFunctionalComponent } from "./create-functional-component";
  
  import { warn, isDef, isUndef, isTrue, isObject } from "../util/index";
  
  import {
    resolveAsyncComponent,
    createAsyncPlaceholder,
    extractPropsFromVNodeData,
  } from "./helpers/index";
  
  import {
    callHook,
    activeInstance,
    updateChildComponent,
    activateChildComponent,
    deactivateChildComponent,
  } from "../instance/lifecycle";
  
  import {
    isRecyclableComponent,
    renderRecyclableComponentTemplate,
  } from "weex/runtime/recycle-list/render-component-template";
  
  // inline hooks to be invoked on component VNodes during patch
  const componentVNodeHooks = {
    // 实例化和挂在
    init(vnode: VNodeWithData, hydrating: boolean): ?boolean {
      if (
        vnode.componentInstance &&
        !vnode.componentInstance._isDestroyed &&
        vnode.data.keepAlive
      ) {
        // kept-alive components, treat as a patch
        const mountedNode: any = vnode; // work around flow
        componentVNodeHooks.prepatch(mountedNode, mountedNode);
      } else {
        // 实例
        const child = (vnode.componentInstance = createComponentInstanceForVnode(
          vnode,
          activeInstance
        ));
        // 挂载
        child.$mount(hydrating ? vnode.elm : undefined, hydrating);
      }
    },
  
    prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
      const options = vnode.componentOptions;
      const child = (vnode.componentInstance = oldVnode.componentInstance);
      updateChildComponent(
        child,
        options.propsData, // updated props
        options.listeners, // updated listeners
        vnode, // new parent vnode
        options.children // new children
      );
    },
  
    insert(vnode: MountedComponentVNode) {
      const { context, componentInstance } = vnode;
      if (!componentInstance._isMounted) {
        componentInstance._isMounted = true;
        callHook(componentInstance, "mounted");
      }
      if (vnode.data.keepAlive) {
        if (context._isMounted) {
          // vue-router#1212
          // During updates, a kept-alive component's child components may
          // change, so directly walking the tree here may call activated hooks
          // on incorrect children. Instead we push them into a queue which will
          // be processed after the whole patch process ended.
          queueActivatedComponent(componentInstance);
        } else {
          activateChildComponent(componentInstance, true /* direct */);
        }
      }
    },
  
    destroy(vnode: MountedComponentVNode) {
      const { componentInstance } = vnode;
      if (!componentInstance._isDestroyed) {
        if (!vnode.data.keepAlive) {
          componentInstance.$destroy();
        } else {
          deactivateChildComponent(componentInstance, true /* direct */);
        }
      }
    },
  };
  
  const hooksToMerge = Object.keys(componentVNodeHooks);
  
  // 返回自定义组件的vnode
  export function createComponent(
    Ctor: Class<Component> | Function | Object | void,
    data: ?VNodeData,
    context: Component,
    children: ?Array<VNode>,
    tag?: string
  ): VNode | Array<VNode> | void {
    if (isUndef(Ctor)) {
      return;
    }
  
    const baseCtor = context.$options._base;
  
    // plain options object: turn it into a constructor
    if (isObject(Ctor)) {
      Ctor = baseCtor.extend(Ctor);
    }
  
    // if at this stage it's not a constructor or an async component factory,
    // reject.
    if (typeof Ctor !== "function") {
      if (process.env.NODE_ENV !== "production") {
        warn(`Invalid Component definition: ${String(Ctor)}`, context);
      }
      return;
    }
  
    // async component
    let asyncFactory;
    if (isUndef(Ctor.cid)) {
      asyncFactory = Ctor;
      Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
      if (Ctor === undefined) {
        // return a placeholder node for async component, which is rendered
        // as a comment node but preserves all the raw information for the node.
        // the information will be used for async server-rendering and hydration.
        return createAsyncPlaceholder(asyncFactory, data, context, children, tag);
      }
    }
  
    data = data || {};
  
    // resolve constructor options in case global mixins are applied after
    // component constructor creation
    resolveConstructorOptions(Ctor);
  
    // transform component v-model data into props & events
    if (isDef(data.model)) {
      transformModel(Ctor.options, data);
    }
  
    // extract props
    const propsData = extractPropsFromVNodeData(data, Ctor, tag);
  
    // functional component
    if (isTrue(Ctor.options.functional)) {
      return createFunctionalComponent(Ctor, propsData, data, context, children);
    }
  
    // extract listeners, since these needs to be treated as
    // child component listeners instead of DOM listeners
    // 事件处理
    const listeners = data.on;
    // replace with listeners with .native modifier
    // so it gets processed during parent component patch.
    data.on = data.nativeOn;
  
    if (isTrue(Ctor.options.abstract)) {
      // abstract components do not keep anything
      // other than props & listeners & slot
  
      // work around flow
      const slot = data.slot;
      data = {};
      if (slot) {
        data.slot = slot;
      }
    }
  
    // install component management hooks onto the placeholder node
    // 安装组件钩子
    installComponentHooks(data);
  
    // return a placeholder vnode
    const name = Ctor.options.name || tag;
    const vnode = new VNode(
      `vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
      data,
      undefined,
      undefined,
      undefined,
      context,
      { Ctor, propsData, listeners, tag, children },
      asyncFactory
    );
  
    // Weex specific: invoke recycle-list optimized @render function for
    // extracting cell-slot template.
    // https://github.com/Hanks10100/weex-native-directive/tree/master/component
    /* istanbul ignore if */
    if (__WEEX__ && isRecyclableComponent(vnode)) {
      return renderRecyclableComponentTemplate(vnode);
    }
  
    return vnode;
  }
  
  export function createComponentInstanceForVnode(
    // we know it's MountedComponentVNode but flow doesn't
    vnode: any,
    // activeInstance in lifecycle state
    parent: any
  ): Component {
    const options: InternalComponentOptions = {
      _isComponent: true,
      _parentVnode: vnode,
      parent,
    };
    // check inline-template render functions
    const inlineTemplate = vnode.data.inlineTemplate;
    if (isDef(inlineTemplate)) {
      options.render = inlineTemplate.render;
      options.staticRenderFns = inlineTemplate.staticRenderFns;
    }
    return new vnode.componentOptions.Ctor(options);
  }
  
  // 给data添加钩子，未来等待path调用
  // 用户传入钩子和默认钩子合并
  function installComponentHooks(data: VNodeData) {
    const hooks = data.hook || (data.hook = {});
    for (let i = 0; i < hooksToMerge.length; i++) {
      const key = hooksToMerge[i];
      const existing = hooks[key];
      const toMerge = componentVNodeHooks[key];
      if (existing !== toMerge && !(existing && existing._merged)) {
        hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge;
      }
    }
  }
  
  function mergeHook(f1: any, f2: any): Function {
    const merged = (a, b) => {
      // flow complains about extra args which is why we use any
      f1(a, b);
      f2(a, b);
    };
    merged._merged = true;
    return merged;
  }
  
  // transform component v-model info (value and callback) into
  // prop and event handler respectively.
  function transformModel(options, data: any) {
    const prop = (options.model && options.model.prop) || "value";
    const event = (options.model && options.model.event) || "input";
    (data.attrs || (data.attrs = {}))[prop] = data.model.value;
    const on = data.on || (data.on = {});
    const existing = on[event];
    const callback = data.model.callback;
    if (isDef(existing)) {
      if (
        Array.isArray(existing)
          ? existing.indexOf(callback) === -1
          : existing !== callback
      ) {
        on[event] = [callback].concat(existing);
      }
    } else {
      on[event] = callback;
    }
  }
  