2

I am trying to iteratively replace .cshtml razor views with what I wanted to call Vue "mini-apps". Which should be somewhere in between a micro-frontend and a classic SPA. The aim is to share some of the code base, mainly dependencies. Compile a common chunk-vendors.js and have the "mini-apps" as separate javascript entry files to place on appropriate views. As performance demand would grow, I would progress into splitting chunk-vendors.js and optimize via lazy-loading.

The problem I am hitting here is trying to make two root Vue instances talk to each other through a shared state. Right now only the app that is imported/mounted first stays reactive. I thought that my problem was in the Vue 2 reactivity system/how Vuex binds itself to a concrete Vue instance here. When I implemented a primitive store, the situation ended up being exactly the same.

What confuses me about this is that if I were to instantiate two applications in a single main.js entry file, the store sharing would just work. Which suggest that Vue is either creating some kind of hidden root instance or that my Vuex code analysis deduction of it binding to a concrete instance was incorrect.

I would highly appreciate it if someone could tell me why this can't work, optionally suggest a workaround?

I have created a reproduction both in Vue 2 with Vuex and in Vue 3 with composition API/primitiveStoreImplementation here. enter image description here Vue-cli is building the app in an MPA mode with pages specified in vue.config.json, then imported in the root index.html file. The store is initialised once and saved for later check/loading on the window object. In the context of asp/razor I would have webpack set up to remove the redundant files, only leaving javascript bundles. Also, the dev proxy would proxy everything except the path to the script bundles. All of this is removed for the sake of the demonstration.

(once I find a solution I hope to replace the source link with specific code snippets)

Options considered:

I am trying to avoid it, but I might have to always run a "coordinator" root instance that will check the presence of certain elements on a page and load/unload the "mini-apps" as components using something like portal-vue when needed. That coordinator would also contain a state with modules, some of which would be marked as "shared" thus operations from multiple "mini-apps" would be allowed (ownership flag check).

I have considered sessionStorage/localStorage, the problem is that the 'storage' events are only triggered across tabs and not within one document first |Note. I would have to trigger a custom event or play around with iframes. Feels too hacky, but that might be an axiom here. It would also duplicate the same state across many store instances.

These are some relevant articles I have found on this topic:

Probably closest to what I am trying to achieve: Using Vuex with multiple Vue instances

Same but different: Build Vue microfrontend app (with routing and vuex store)

FeelaV
  • 102
  • 2
  • 12

1 Answers1

1

The use case for multiple entries are sub-apps that don't coexist on the same page, although they can. They could be web components or regular app bundles. They can even interact with each other but they need something else for this - global event bus or a mediator app that passes data between them.

The problem is that there are more than one Vue library copies and/or more than one Vuex store instance. In order to avoid this, they would need to be precisely loaded only once on the page and reused between apps, i.e. vue and vuex are loaded as CDN libs, possibly used as Webpack externals to use window.Vue and window.Vuex transparently for respective import. Not only Vuex but store needs to be a singleton on the page (basically a said mediator). This is acceptable solution but primarily suitable for existing applications that have design restrictions and need a workaround.

I am trying to avoid it, but I might have to always run a "coordinator" root instance that will check the presence of certain elements on a page and load/unload the "mini-apps" as components using something like portal-vue when needed.

This is the common way to do this. Vue 3 has teleports that has give less control than portal-vue. It has no downsides for application design if done properly. The same thing is achieved similarly in other frameworks (Angular, React) as well, where portals appeared earlier.

I have considered sessionStorage/localStorage, the problem is that the 'storage' events are only triggered across tabs and not within one document

This is solved by using window postMessage and message event in the same tab. In case this shouldn't be limited to a single window, there are third party libs that use both for cross-tab synchronzation, a native alternative is BroadcastChannel that has less browser support than LS but doesn't have its limitations regarding tabs.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Your answer isn't addressing the problem. As seen in the example project: https://github.com/PheelaV/MultipleVueRootAppsMultipleEntryFilesSameStore/search?q=window., the store can be easily shared between different vue bundles using window, the app that loads first set its store in the window, and the window.store is the one you inject in your components. The problem is that for some reason, this shared store loses its reactivity – Jasper Jun 17 '22 at 10:25
  • Hey Estus, yes your answer is pointing to the multiple instances are root cause, and your answer describes how to handle that. But I'm missing the reasoning _why_ multiple Vue/Vuex instances would be the problem. – Jasper Jun 17 '22 at 13:17
  • @Jasper This is about how Vue reactivity works. Vuex relies on Vue's reactivity for it's reactivity, it creates `new Vue` reactive instance in 2 and `reactive` object in 3. You may want to check how it works, but as for 2, getter/setter pairs are tracked how they are used and how an instance should react on their changes, this info is stored within an instance, but multiple instances of the same Vue constructors can share this "info" and react to each other, they orchestrated by common `Vue` logic. – Estus Flask Jun 17 '22 at 15:24
  • 2
    An instance of another `Vue` copy (Vuex store in this case) is a totally alien object that doesn't expose that "info" and can't be interacted that way. This is perceived as the loss of reactivity. It doesn't matter at this point if another copy is exactly the same version of `Vue` (something that happens when `import Vue from 'vue'` is done by separately bundled app1 and app2, the apps are different scripts with different `Vue`), or different Vue version (2 and 3), or separate React and Vue app. The reactivity between unrelated reactive apps needs to be set up from scratch based on public api. – Estus Flask Jun 17 '22 at 15:29