Adding a base path to vite.config.js host module in vue3-demo-esm-expose-store pinia store sharing doesn't work
fbrillante opened this issue · 6 comments
Versions
- vite-plugin-federation: 1.3.2
- vite: 4.1.4
Reproduction
Adding a base path to vite.config.js, the pinia store sharing doesnt' work.
To reproduce the issue you can start from "vue3-demo-esm-expose-store" demo and add the following property:
base: "/test",
in config object defined here (e.g. line 28):
https://github.com/originjs/vite-plugin-federation/blob/main/packages/examples/vue3-demo-esm-expose-store/host/vite.config.ts
The host app will run with the specified base path but the store initialization will fail with the following error:
Uncaught Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?
Found a workaround I'll put here for future reference, if someone gets the same error, but however is not suitable for production environment
You can get rid of it adding the following configuration in server section:
**server: {
origin: "http://localhost:5173/test",
},**
Can the usage of window.location.href in the "wrapShareScope" function be a fix on the library?
I have the same problem, any fixes for the problem?
Hello, I'm encountering a problem when specifying a relative path inside base: "/test". Is there an official solution?
Found a workaround I'll put here for future reference, if someone gets the same error, but however is not suitable for production environment You can get rid of it adding the following configuration in server section:
**server: { origin: "http://localhost:5173/test", },**
Can the usage of window.location.href in the "wrapShareScope" function be a fix on the library?
It appears that the issue lies in the order of module loading. As seen in the screenshot, the defineStore of the Pinia store module from the host app (the app from which the Pinia module is exposed) is loaded before the createPinia call of the local app (the app that uses the exposed Pinia module).
Therefore, there should be a way to specify the exact order of loading modules while in development mode.
This error only occurs in development mode.
It seems like the order doesn't matter. In the production build, the order is the same. However, when the local app is in development mode (but the host is in production), the getCurrentInstance call on the side of the local app returns nothing inside of the useStore function of Pinia's internal code. But in production mode, everything works fine. By the way, the error occurred when the host app was running in production mode (using Vite preview), but the local app was running in development mode. However, when both sides are in production mode, there are no errors.
I figure it out. The Pinia module loads twice and rewrites some of its own internal state.
You can investigate in the following steps: You need mainApp and remoteApp. MainApp exposes some stores with a default store - you run it through npm run build && vite preview
.
But remoteApp, you run through the dev way - just vite --host
.
In that case, inside main.ts of remoteApp, it will be something like this.
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
That code will trigger first loading of local dependency . It can be seen inside the Nework tab - the name of file will something like this :
http://172.25.64.96:5173/dictionary/node_modules/.vite/deps/pinia.js?v=253339aa
Secondly, App.vue will be mounted, and the code inside of which will be something like this
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
import { useAuthStore } from 'mainApp/auth-store'
const instance = getCurrentInstance()
console.log("INSTANCE", instance)
const authStore = useAuthStore()
console.log("AUTH STORE", authStore)
</script>
<template>
APP
</template>
As you can see there are import { useAuthStore } from 'mainApp/auth-store'
That module will be imported second time through vite-module-federation plugin , because the generated code of useAuthStore will something like this :
import { importShared } from './__federation_fn_import-1d150bd9.js';
const {defineStore} = await importShared('pinia');
console.log("DEFINE STORE", defineStore);
const token = localStorage.getItem("token");
const useAuthStore = defineStore({
id: "auth",
state: () => ({
token,
login: "",
data: null,
loading: false,
importShared will do some magic , but it totally ignores already imported dependency http://172.25.64.96:5173/dictionary/node_modules/.vite/deps/pinia.js?v=253339aa
importShared will import pinia again from file __federation_fn_import-1d150bd9.js
using moduleMap from __federation_fn_import-1d150bd9.js
// eslint-disable-next-line no-undef
const moduleMap = {
'vue':{get:()=>()=>__federation_import(new URL('__federation_shared_vue-fee7d8a8.js', import.meta.url).href),import:true},
'vue-router':{get:()=>()=>__federation_import(new URL('__federation_shared_vue-router-28720dca.js', import.meta.url).href),import:true},
'pinia':{get:()=>()=>__federation_import(new URL('__federation_shared_pinia-04447d99.js', import.meta.url).href),import:true},
'@vueuse/core':{get:()=>()=>__federation_import(new URL('__federation_shared_@vueuse/core-57dd04b9.js', import.meta.url).href),import:true},
'element-plus':{get:()=>()=>__federation_import(new URL('__federation_shared_element-plus-4836b0d5.js', import.meta.url).href),import:true},
'vue-i18n':{get:()=>()=>__federation_import(new URL('__federation_shared_vue-i18n-b86e26b4.js', import.meta.url).href),import:true}};
and secondly rewrites pinia module internals .
To prove that, you can go to remoteApp/node_modules/pinia/dist/pinia.mjs and at the top of the file, write code like this
const tag =Math.random()
console.log(tag)
And print it also in defineStore and useStore functions- you will see , that the tag will be different in some cases.
So, to solve that ( at least temporary ) you can explicitly export the entire pinia module from mainApp
// mainApp : pinia.ts
import * as piniaModule from 'pinia'
export { piniaModule }
// mainApp : vite.config.ts
federation({
name: 'mainApp',
filename: 'remoteEntry.js',
exposes: {
"./auth-store": "./src/store/auth.ts",
"./pinia": "./src/store/pinia.ts",
},
And at remoteApp import it like this:
//main.ts
import { piniaModule } from 'mainApp/pinia'
const { createPinia } = piniaModule;
....
const pinia = createPinia()
app.use(pinia)
app.mount('#app')