vue.jsvuejs2vuejs3bootstrap-vuetinymce-5

Migrating Vue 2 to Vue 3 requiring at least a library in Vue 3 and bootstrap-vue (Vue 2): options?


We are trying to update a library and the newer version requires Vue 3 instead of Vue 2, namely tinymce-vue. Unfortunately, it is a company project using bootstrap-vue, which has no full compatibility with Vue 3 yet (bootstrap-vue3 is not production-ready and we use some components that are not migrated yet).

Migrating the full app to Vue 3 has been the main attempt. However, it does not allow to use the Bootstrap components in Vue 3, or if the compatibility mode is used, part of the app works but those that would require the component do not appear/work or then the other parts of the component needing Vue 3 are broken. Is there any way to provide maybe library-specific compatibility or what is the suggested way to proceed in this case when needing two libraries that require two different versions of Vue in the same component?

I am not sure if this question should be asked differently, it is my first question in StackOverflow, so please let me know if I need to reformulate or provide more details.


Solution

  • The problem is that Vue 2 and 3 applications are hard to impossible to coexist in the same project because they rely on vue package with the same name but different versions. Even if it's possible to alias vue package under a different name or use modular Vue (import Vue from 'vue') for one version and Vue CDN (window.Vue) for another version in first-party code, another problem that needs to be addressed is that Vue libraries need to use specific Vue version.

    This requires to build and bundle sub-apps with their preferred Vue version and libraries, which is quite close to the concept of micro frontend applications.

    Given that there is Vue 3 sub-app that uses Vue 3-specific library (tinymce-vue) and specifically written to expose all public API to communicate with the outside world:

    let MyV3Comp = {
      template: `<div>{{ myV3Prop }} {{ myV3Data }}</div`,
      props: ['myV3Prop'],
      emits: ['myV3Event'],
      setup(props, ctx) {
        const myV3Data = ref(1);
        const myV3Method = () => {};
    
        ctx.emit('myV3Event', Math.random());
    
        // Component public api needs to be exposed to be available on mount() instance
        ctx.expose({ myV3Data, myV3Method });
    
        return { myV3Data, myV3Method }
    
      },
    };
    
    // Sub-app entry point
    let createMyV3App = initialProps => createApp(MyV3Comp, initialProps);
    export default createMyV3App;
    

    There is Vue 2 wrapper component that acts as a bridge between Vue 3 sub-app and the rest of Vue 2 app:

    
    import createMyV3App from '.../my-v3-app-bundled';
    
    let MyV2WrapperComp = {
      template: `<div ref="v3AppWrapper"></div>`,
      props: ['myV2Prop'],
      emits: ['myV2Event'],
      data() {
        return { myV2Data: null };
      },
      methods: {
        // Sync wrapper events
        onMyV3Event(v) {
          this.$emit('myV2Event', v);
        }
      },
      watch: {
        // Sync wrapper props and data
        myV2Data(v) {
          this.v3AppCompInstance.myV3Data.value = v;
        },
        myV2Prop(v) {
          // Hacky! Better use data and methods from public api to pass info downwards
          this.v3AppCompInstance._instance.props.myV3Prop = v;
        },
      },
      mounted() {
        // Vue 3 automatically translates onMyV3Event prop as myV3Event event listener
        // Initial prop values make app props reactive
        // and allow to be changed through _instance.props
        this.v3App = createMyV3App({ onMyV3Event: this.onMyV3Event, myV3Prop: null });
    
        // also available as undocumented this.v3App._instance.proxy
        this.v3AppCompInstance = this.v3App.mount(this.$refs.v3AppWrapper);
    
        // Sync wrapper data
        // Hacky! Better use event from public api to pass info upwards
        this.v3AppCompInstance._instance.proxy.$watch('myV3Data', v => this.myV2Data = v);
      },
      unmounted() {
        this.v3App.unmount();
      },
    };
    

    In case wrapper and sub-app need to be additionally synchronized based on specific points, e.g. provide/inject, template refs, etc, this needs to be specifically implemented. At this point it's no different than Vue 3->Vue 2 adapter or adapters that involve other frameworks (Angular, React).