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.
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
.