如何hel框架中,同时使用vue2和vue3

217 阅读1分钟

背景

hel框架中,用于加载和渲染vue2和vue3组件 将vue2或者vue3组件转换为DOM。

vue3ToVue2

实现了一个加载远程vue3组件的vue2应用程序,Vue2应用依赖于vue3应用公开的组件。

vue3 提供方

export { default as Button } Button from './Button.vue'
export { default as vueVersion } from '../../node_modules/vue'

utils/transform.js

  • 集成element-plus
import { createApp, h } from 'vue';
import ElementPlus from 'element-plus'

export const vue3ToVue2 = async (WrapperComponent, wrapperId) => {
  let vm

  return {
    async mounted () {
      await import('element-plus/dist/index.css')
      vm = createApp({
        render: () => {
          return h(
            WrapperComponent,
            {
              ...this.$props
            }
          )
        }
      })
      vm.use(ElementPlus)
      vm.mount(`#${wrapperId}`)
    },
    props: WrapperComponent.props,
    render () {
      vm && vm.$forceUpdate()
    }
  }
}

export default {
  vue3ToVue2,
};

vue2 使用方

vue组件

<template>
  <div>render
    <div id="vue2Button" />
    <!-- <vue2Button /> -->
    <RemoteCompAsyncBetter />
  </div>
</template>

<script>
import { preFetchLib } from 'hel-micro'
  
// 本地联调
const enableCustom = !!window.location.port
const fetchOptions = {
  custom: {
    host: 'http://localhost:7002',
    enable: enableCustom,
    skipFetchHelMeta: true
  }
}
  
export default {
  components: {
    // 可能因为编译器的关系,此写法无效
    // vue2Button: vue3ToVue2('Button', 'vue2Button')

    RemoteCompAsyncBetter: () => ({
      component: new Promise(async(r, j) => {
        try {
          const name = 'hel-tpl-remote-vue3-comps-ts'
          const mod = await preFetchLib(name, fetchOptions)
          const { vue3ToVue2 } = mod.Transform
          const WrapperComponent = mod['Button']
          r(vue3ToVue2(WrapperComponent, 'vue2Button'))
        } catch (err) {
          j(err)
        }
      })
    })

  }
}
</script>

vue2ToVue3

实现了一个加载远程vue2组件的vue3应用程序,Vue3应用依赖于vue2应用公开的组件。

vue2 提供方

export { default as Vue2Test } from '@/views/vue2Test'
export { default as Vue2 } from '../../node_modules/vue/dist/vue'

vue3 使用方

vue组件

<template>
  <div>
    <h3>Vue3 App</h3>
    <Content :count="count" />
    <div id="vue2Button"></div>
    <vue2-button @btnClick="inc"/>
    <RemoteCompAsyncBetter />
  </div>
</template>

<script>
import * as Vue from 'vue'
import { ref } from 'vue';
import { preFetchLib, bindVueRuntime } from 'hel-micro';
import { vue2ToVue3 } from '../utils/transform';
export default {
  components: {
    vue2Button: vue2ToVue3(Button, 'vue2Button'),
    
    // 可能因为编译器的关系,此写法无效
    // RemoteCompAsyncOld: async () => {
    //   const name = 'ib-zhangbb-component';
    //   const mod = await preFetchLib(name, fetchOptions);
    //   console.log("mod===>",mod)
    //   // const mod = await preFetchLib("remote-selection-point", "0.0.1-alpha.3");
    //   // const mod = await preFetchLib("hel-tpl-remote-vue-comps");
    //   return mod.Vue2Test;
    // },

    // 可能因为编译器的关系,此写法无效
    // RemoteCompAsyncBetter: () => ({
    //   component: new Promise(async (r, j) => {
    //     try {
    //       const mod = await preFetchLib('lib-zhangbb-component', fetchOptions);
    //       console.log('mod===>', mod)
    //       const Vue2 =  mod.Vue2
    //       const components = vue2ToVue3(mod.Vue2Test,vue2Button,Vue2)
    //       r(components);
    //       // r(mod.Vue2Test);
    //     } catch (err) {
    //       j(err);
    //     }
    //   }),
    // }),
  },
  setup () {
    const count = ref(0);
    const inc = () => {
      count.value++;
    };

    return {
      count,
      inc,
    };
  },
};
</script>

<style scoped>
h1 {
  font-family: Arial, Helvetica, sans-serif;
}
</style>

utils.js

import { preFetchLib } from 'hel-micro'

// 本地联调
const enableCustom = !!window.location.port
const fetchOptions = {
  custom: {
    host: 'http://localhost:7002',
    enable: enableCustom,
    /**
     * defaut: false
     * 是否跳过获取 hel-meta.json 的获取步骤,true:跳过,false:不跳过
     * 当用户设定 custom.host 配置时,hel-micro 采取总是相信该 host 存在一个 hel-meta.json 文件并尝试去获取
     * 如获取失败时再去解析该 host 对应的首页并现场解析出 hel-meta.json 数据
     * 因此获取动作可能会报一个 404 not found 符合预期的行为,用户可设定 skipFetchHelMeta 为 true 跳过此步骤
     * 但建议加载线上模块时(非本地联调时),保持 skipFetchHelMeta 为 false 比较好,有利于提高模块加载速度( 无html解析hel-meta.json过程 )
     */
    skipFetchHelMeta: true
  }
}

function bindSlotContext(target = {}, context) {
  return Object.keys(target).map((key) => {
    const vnode = target[key]
    vnode.context = context
    return vnode
  })
}

/**
 * Transform vue2 components to DOM.
 * @param {*} WrapperComponentName
 * @param {*} wrapperId
 * @returns
 */
export const vue2ToVue3 = async(WrapperComponentName, wrapperId) => {
  let vm

  const name = 'hel-tpl-remote-vue3-comps-ts'
  const mod = await preFetchLib(name, fetchOptions)
  const Vue = mod.vueVersion
  const WrapperComponent = mod[WrapperComponentName]

  return {
    mounted() {
      const slots = bindSlotContext(this.$slots, this.__self)
      vm = new Vue({
        render: (createElement) => {
          return createElement(
            WrapperComponent,
            {
              on: this.$attrs,
              attrs: this.$attrs,
              props: this.$props,
              scopedSlots: this.$scopedSlots
            },
            slots
          )
        }
      })

      vm.$mount(`#${wrapperId}`)
    },
    props: WrapperComponent.props,
    render() {
      vm && vm.$forceUpdate()
    }
  }
}

实现demo vue2模块: 地址:github.com/zhangbinzhb… 对应分支dev_vue2_in_vue3_test

vue3模块: 地址:github.com/zhangbinzhb… 对应分支dev_vue3_in_vue2_test

特点

  • 双向共享、同时引入多个依赖