vue.jsnpmvitevue-router

Why is useRouter() always returning undefined in my library


I have a vue.js library thats supposed to be kinda simple i just have a custom function for creating a vue app like this:

import AppComp from "./components/App.vue";

export const createMyCustomApp = (props): App<Element> => {
    console.log("Creating app with props: ", getNonEmptyProps(props));
    return createApp(AppComp , getNonEmptyProps(props));
}

and the App.vue :

<script setup lang="ts">
import {useRouter} from "vue-router";
import {useFetchingError, useIsFetching} from "../app";
import {ComputedRef, defineComponent, onMounted, PropType} from "vue";
import {computed, getCurrentInstance} from "vue";
import Loading from "./Loading.vue";
import Error from "./Error.vue";

defineProps({
  loadingComponent: {
    type: Object as PropType<ReturnType<typeof defineComponent>>,
    default: Loading
  },
  errorComponent: {
    type: Object as PropType<ReturnType<typeof defineComponent>>,
    default: Error
  }
});

const isFetching: ComputedRef<boolean> = computed(() => useIsFetching());
const fetchingError = computed(() => useFetchingError());

const router = useRouter();

console.log("=".repeat(50));
console.log("router : ", router);
console.log(getCurrentInstance());
console.log("=".repeat(50));

router.afterEach((to, from, failure) => {
  // some code
});


router.afterEach(() => /*some code*/);

onMounted(() => {
  const newRouter = useRouter();
  console.log("After nount" , newRouter);
});
</script>

<template>
  <component v-if="isFetching" :is="loadingComponent"/>
  <component v-if="fetchingError" :is="errorComponent" :error="fetchingError.message"/>
  <router-view v-else></router-view>
</template>

and i have vue and vue-router as peerDependencies, this is my package.json

{
  "name": "@myusername/redacted",
  "version": "0.0.2",
  "description": "something....",
  "main": "dist/lib.umd.js",
  "module": "dist/lib.es.js",
  "browser": "dist/lib.cjs.js",
  "types": "dist/app.d.ts",
  "exports": {
    "." : {
      "import": "./dist/lib.es.js",
      "require": "./dist/lib.cjs.js"
    }
  },
  "files": [
    "dist"
  ],
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/@myusername"
  },
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "keywords": [
    "client",
    "metaframework",
    "watchfulraven"
  ],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "vite": "^6.2.0",
    "vite-plugin-dts": "^4.5.0"
  },
  "peerDependencies": {
    "vue": "^3.5.13",
    "vue-router": "^4.5.0"
  },
  "dependencies": {
    "@vitejs/plugin-vue": "^5.2.1",
    "axios": "^1.8.1"
  }
}

and in my vite.config.ts:

import {defineConfig} from "vite";
import dts from "vite-plugin-dts";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
    build: {
        lib: {
            entry: "src/app.ts",
            name: "lib",
            formats: ["es", "cjs", "umd"],
            fileName: (format) => `lib.${format}.js`,
        },
        rollupOptions: {
            external: ['vue', 'vue-router'],
            output: {
                globals: {
                    vue: 'Vue',
                    'vue-router': 'VueRouter',
                },
            },
        },
    },
    plugins: [
        vue(),
        dts({rollupTypes: true})
    ]
});

and in my app.js in my main app i use it like this :

import {createMyCustomApp} from "@myusername/thepackagename";
import {router} from "@/presentation/routes/router.js";
import {i18n} from "./i18n.ts";


const app = createMyCustomApp({});

app
    .use(router)
    .use(i18n)
    .mount("#app");

but like i said both the console.log router return undefined and in my console it says:

[Vue warn]: injection "Symbol(router)" not found. at <App >

if i use the same App.vue component but locally without the package it works fine too. i spent so much time trying to make it work, Any help would be appreciated.


Solution

  • Composables are supposed to be used directly in the body of setup function or <script setup>, any other usage needs to be confirmed and depends on their implementation. The ones that rely on provide/inject like useRouter cannot be used in other places.

    This usage is incorrect:

    onMounted(() => {
      const newRouter = useRouter();
      ...
    

    This usage is correct:

    
    <script setup lang="ts">
    ...
    const router = useRouter();
    ...
    

    This requires to have router plugin installed, which is done here with use(router).

    Also this is expected to work normally in project dependencies only when vue and vue-router packages are not duplicated in a bundle. This may depend on many factors: correct indication of transient vue and vue-router dependencies (peerDependencies is correctly used here), package manager, build tool, the way a dependency is installed, etc. A combination of them was a likely cause for the problem.

    Whether the packages are duplicated needs to be diagnosed by running npm list vue in the project. The output should contain only one vue@3... entry (at top level), the rest should be vue@3... deduped. Same for vue-router.

    A blunt but straightforward way to detect the problem with package duplication is to use in the project:

    import * as vue from 'vue';
    window.vueProject = vue;
    

    Then do the same in a problematic dependency. In case it's first-party, this can be done in source code:

    import * as vue from 'vue';
    window.vueDependency = vue;
    

    Then vueProject === vueDependency can be verified in a console. Same goes for vue-router. If the dependencies are not duplicated, both conditions are true.