cloning vnodes in Vue

by modernweb

HTML

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/lodash"></script>

<div id="app">
  <wrapper ref="wrapper">
    <p slot="scopeTest" slot-scope="_">{{_.name}}</p>
    <test :value="message" @some-event="lognodes"><span class="chidl-of-test">Child of Test</span></test>
    <div class="test2" :class="message ? 'dyn' : false" @click="lognodes">Test2</div>
  </wrapper>
</div>

Babel + JSX

console.clear()

const DATA_KEYS = [
	'class', 'staticClass', 'style',
  'attrs', 'props', 'domProps',
  'on', 'nativeOn', 
  'directives', 'scopesSlots',
  'slot', 'ref', 'key'
]

let cid = 0
function mutateKey(key) {
  return '' + key + `-cloned-cid`
}

function extractData(vnode, isComp) {
	const data = _.pick(vnode.data, DATA_KEYS)
	if (isComp) {
  	const cOpts = vnode.componentOptions
    _.assign(data, {
      props: cOpts.propsData,
      on: cOpts.listeners
    })
  }
  
  if (data.key) {
    data.key = mutateKey(data.key)
  }
  
  return data
}

function cloneVNode(vnode, newData = {}) {
	// use the context that the original vnode was created in.
  const h = vnode.context && vnode.context.$createElement
  const isComp = !!vnode.componentOptions
  const isText = !vnode.tag // this will also match comments but those will be dropped, essentially
  const children = isComp
  	? vnode.componentOptions.children
    : vnode.children
  
  if (isText) return vnode.text
  
  const data = extractData(vnode, isComp)
  
  const tag = isComp
  	? vnode.componentOptions.Ctor
    : vnode.tag
  
  const childNodes = children ? children.map(c => cloneVNode(c)) : undefined
  return h(tag, data, childNodes)
}

Vue.component('wrapper', {
  template: '<div class="wrapper"><slot/></div>',
})

Vue.component('test', {
  props: ['value'],
  template: `<div class="test">{{value}}</div>`,
})

const app = new Vue({
  el: '#app',
  data: {
    message: 'message'
  },
  mounted() { this.lognodes() },
  updated() { this.lognodes() },
  methods: {
    lognodes() {
    	const nodes = this.$refs.wrapper.$slots.default
      console.log('originals: ', nodes)
      
      console.log('cloned: ', cloneVNode(nodes[2]))
    }
  }
})