DamianOsipiuk / vue-query

Hooks for fetching, caching and updating asynchronous data in Vue
https://vue-query.vercel.app/
MIT License
1.11k stars 48 forks source link

Plugin not installing after update to vue2.7 #242

Closed jens-morten-mikkelsen closed 1 year ago

jens-morten-mikkelsen commented 2 years ago

Hello 👋 After updating from vue2.6 with vue-demi, to vue2.7 without vue-demi, im running into an issue where Vue no longer is registering the VueQueryPlugin. here is how i have been using the plugin up till now: Vue.use(VueQueryPlugin).

Previously i was using vue-query1.26 but just updated to vue-query2, but it is still not working

DamianOsipiuk commented 2 years ago

👀

jens-morten-mikkelsen commented 2 years ago

Bit of extra info here :) The setup isn't a regular vue setup ^^' Our vue lives within an angular app, since its a customer project, that needs IE11 support, and sadly was made in angular 1 🙄 Without spilling too much, i can say that basically we do not have a normal new Vue({...}).mount() but instead we technically mount a new root Vue component when ever a vue component is placed within the angular app :)

The weird part for me is that it just stopped working when upgrading to 2.7, but i will try and see if a setup where we still use vue-demi but our vue version is bumped to 2.7 will work.

I will keep this issue updated with my findings, and will happily answer any questions and test potential theories :)

jens-morten-mikkelsen commented 2 years ago

Update: TLDR - still not working.

I have tried the ways i have found for installing/using the plugin, but so far nothing worked. Tried using vue-demi (no dice) Tried different ways of instantiating Vue (createApp, new Vue() and nothing like it was before the update to 2.7) Tried installing and using vue-query where our different "ng-vue components" are defined.

I will here provide main.ts and registerVueComponents.ts files. If any other files could be usefull, just ask and i shall provide what i can :)

main.ts:

import Vue from "vue";
import TranslatePlugin from "./core/plugins/translate";
import PortalVue from "portal-vue";
import { config as configureComponents } from "./core/components/componentRegistry";
import { VueQueryPlugin } from "vue-query";
import { setupAngularRegistrations } from "./angular-registration";
import "./styling/main.css";
import { configCookies } from "@/core/cookies/cookieService";

Vue.use(TranslatePlugin);
Vue.use(PortalVue);
Vue.use(VueQueryPlugin);

configureComponents();
configCookies();
setupAngularRegistrations();

registerVueComponents:

import angular from "angular";
import Vue, { VueConstructor } from "vue";
import { Vue as VueInstance, CombinedVueInstance } from "vue/types/vue";

type VueComponentInstance = CombinedVueInstance<Vue, object, object, object, Record<never, any>>;

interface VueConstructorInternals extends VueConstructor
{
    options: VueInstance["$options"];
}

export const registerVueComponent = (module: angular.IModule, name: string, component: any, hasNgSlot?: boolean) => {
    component = Vue.extend(component); //<-- Tried .use(VueQueryPlugin) and VueQueryPlugin.install() here.

    // Get vue component props or use manually passed props (from compositionApi component)
    const propsList = Object.keys((component as VueConstructorInternals).options?.props || {});

    // convert vue props to angular props
    const scope = propsList.reduce((obj, key) => {
        obj[ key ] = "<"; return obj;
    }, {} as Record<string, string>);

    // register angular component
    module.directive(name, () => ({
        restrict: "E",

        scope,
        link: (scope: angular.IScope, element: JQLite) => {
            const targetNode = document.createElement("div");
            element[ 0 ].classList.add("vue-universe");
            element[ 0 ].appendChild(targetNode);

            const cInstance = new component({
                propsData: scope
            });
            cInstance.$mount(targetNode);
            if (hasNgSlot) {
                const ngContentSlot = cInstance.$el.querySelector("[data-ngslot=\"content\"]");
                if (ngContentSlot && ngContentSlot.parentNode) {
                    ngContentSlot.parentNode.replaceChild(element[ 0 ].children[ 0 ], ngContentSlot);
                } else {
                    console.error("data-ng-slot=\"content\", is not present on the component");
                }
            }

            // watch angular props and rebind to vue instance
            syncScopeChangesToProps(propsList, scope, cInstance);

            cleanupOnDestroy(scope, cInstance, targetNode);
        }
    }));
};

function syncScopeChangesToProps(propsList: string[], scope: angular.IScope, cInstance: VueComponentInstance) {
    for (const propName of propsList) {
        scope.$watch(propName, (value) => {
            setPropValueFromAngular(cInstance, propName, value);
        });
    }
}

function cleanupOnDestroy(scope: angular.IScope, cInstance: VueComponentInstance, element: HTMLElement) {
    scope.$on("$destroy", () => {
        cInstance.$destroy();
        element.parentElement?.removeChild(element);
    });
}

function setPropValueFromAngular(cInstance: VueComponentInstance, propName: string, value: unknown) {
    /*
    * If a prop value is set from angular, it is set as the prop value.
    * This is only done to preserve the option to have default values for props on components that needs to be accessed from angular.
    * */
    cInstance.$props[ propName ] = value;
}
jens-morten-mikkelsen commented 2 years ago

also package.json:

{
  "name": "modern",
  "version": "1.0.0",
  "main": "index.js",
  "license": "UNLICENSED",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build --emptyOutDir",
    "build:watch": "vite build --mode watch --watch",
    "start-all": "concurrently --kill-others \"npm run dev\" \"npm run build:watch\"",
    "lint": "eslint \"**/*.{vue,ts,js}\"",
    "lint:fix": "eslint \"**/*.{vue,ts,js}\" --fix",
    "tsc-check": "vue-tsc --noEmit",
    "validate-code": "concurrently \"npm run tsc-check\" \"npm run lint\""
  },
  "dependencies": {
    "@googlemaps/js-api-loader": "^1.13.6",
    "@types/angular": "^1.8.3",
    "@vitejs/plugin-vue2": "^1.1.2",
    "@vueuse/core": "^8.5.0",
    "@vueuse/integrations": "^8.7.5",
    "axios": "^0.26.1",
    "gsap": "^3.10.0",
    "lodash": "^4.17.21",
    "mitt": "^3.0.0",
    "portal-vue": "^2.1.7",
    "postcss": "^8.4.12",
    "postcss-loader": "^6.2.1",
    "tailwindcss": "^1",
    "tslib": "^2.3.1",
    "universal-cookie": "^4.0.4",
    "vue": "^2.7.10",
    "vue-query": "^2.0.0-beta.10"
  },
  "devDependencies": {
    "@types/google.maps": "^3.49.0",
    "@vitejs/plugin-legacy": "^1.8.2",
    "@vue/eslint-config-typescript": "^11.0.0",
    "concurrently": "^7.1.0",
    "eslint": "^8.10.0",
    "eslint-plugin-promise": "^6.0.0",
    "eslint-plugin-vue": "^9.4.0",
    "husky": "^7.0.4",
    "postcss-import": "^14.1.0",
    "postcss-nested": "^5.0.6",
    "typescript": "^4.5.4",
    "unplugin-icons": "^0.14.3",
    "unplugin-vue-components": "^0.19.6",
    "vite": "^2.8.6"
  }
}
jens-morten-mikkelsen commented 2 years ago

Next step will be, to try using vue-query in a clean vue2.7 project, and try using public api's for testing

DamianOsipiuk commented 2 years ago

Vue 2.7 seems to be working just fine. Diff for 2.x-basic example - https://github.com/DamianOsipiuk/vue-query/commit/8723b99014acc2530abaf070424bf4f81d34f5b1

jens-morten-mikkelsen commented 2 years ago

Vue 2.7 seems to be working just fine. Diff for 2.x-basic example - 8723b99

Okay, then i'll look further into our own setup, thanks :)

jens-morten-mikkelsen commented 2 years ago

@DamianOsipiuk The main difference that i can find in the repo you linked to is that in vue 2.7 we cannot install @vur/composition-api because it is built into 2.7, but, in your example you use createApp to create a new vue App. I have tested on a base vue 2.7 set with new Vue() and it does seem to work there, but still no luck on our "frankenstein" project.

Will keep trying more stuff and see if i can get something working :)

DamianOsipiuk commented 2 years ago

Yeah, for that reason I have provided a diff to accommodate example to v2.7 which do not use composition API. https://github.com/DamianOsipiuk/vue-query/commit/8723b99014acc2530abaf070424bf4f81d34f5b1

It's weird that it was working before, but does not work with 2.7 as there suppose to be no breaking changes.

It would be very hard to nail down what exactly is broken without the ability to set breakpoints in code.

The non standard way of instantiating Vue component might be the culprit, as Query client context propagation is done via mixin in Vue 2.

If nothing works, maybe gradual migration to Vue 3 via new integration layer would be an option and leaving already working code on v2.6?

jens-morten-mikkelsen commented 2 years ago

Sadly i dont thinks its an option ti slowly migrate to vue3, since we need IE11 support. Plus we are already slowly migrating from angular1 to vue2 :)

jens-morten-mikkelsen commented 2 years ago

I have now also asked the vue discord, and a link to this issue has been provided to it

jens-morten-mikkelsen commented 2 years ago

Update, i have come to the conclusion that the issue doesn't lie with the installation of the plugin, since when i log the vue instance from components before useQuery is called, i can see under _provided that VUE_QUERY_CLIENT: QueryClient2 is there. So now im even more at a loss :/

DamianOsipiuk commented 1 year ago

If this is still an issue, you can try with @tanstack/vue-query There you can provide queryClient as an option to useQuery hook, basically skipping Vue context, which should solve your issue.