/* @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) 
 | 
  
 | 
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 ( 
 | 
  vnode: any, // we know it's MountedComponentVNode but flow doesn't 
 | 
  parent: any, // activeInstance in lifecycle state 
 | 
): 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) 
 | 
} 
 | 
  
 | 
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 
 | 
  } 
 | 
} 
 |