Does Vue.js have an idiomatic way of declaring application-wide globals like baseUrl and then referencing them throughout the app (for API calls etc)?
If so, what is the best way to configure such globals at build-time to create development, test and prod instances? For example, I imagine doing export BASEURL='http://dev.myapp.com', running a build and getting an app with this baseUrl configured. This would in turn allow me to create automated builds for Continuous Delivery certain branches are updated (develop -> dev.myapp.com, master -> www.myapp.com etc).
Thanks!
What I do: use some state management tool, like Vuex.
If you don't want to add complexity to your app, I use a parent Vue instance, let say App.vue and define global data in that instance, then when I need that data from the parent I call this.$root.baseUrl in case you want the baseUrl. Notice that you can also call this.$parent.baseUrl but this will only work for direct childs, if you are inside a child of a child, you wont get the global data, that's why is better the $root object.
Related
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.
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)
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.
First, I'd like to explain that I have a Vue component repository that is responsible for displaying data retrieved from an http service. Rather than the component itself managing the same data retrieval per instance and spamming the client with network requests, I've managed to find a solution which allows another component to be mounted to the root directly (which I've dubbed as a "Service" due to its similarity to Angular) to manage the data those components need instead. This works great and other components can access it via Vue.prototype (via this.$TestService.value). It has some caveats but for the most part it accomplishes exactly what I needed. This may be uncommon, but those that use Vuex are using a similar methodology and I don't want to use the store paradigm.
I've made a very simple Vue JsFiddle to show this in action...
https://jsfiddle.net/spronkets/8v31tcfd/18
Now, to the point... I'm using #testing-library/vue, #vue/test-utils, and Jest to test the components and get test coverage and now I get errors anytime I run the tests due to the service not existing on the Vue.prototype during the test execution. I don't want to mock out the functionality of the "Service" layer, so does anyone have a solution to test these root-mounted components? I've tried manually exporting the services (unmounted and mounted) and including them in the mock section as well as importing the files directly into the test files but the "Service" is always undefined when the component is trying to retrieve the value and ONLY during test execution...
I've also created a simple repository modelled after the Vue component repository I am working with below...
https://github.com/kcrossman/VueServiceExample
To get started, clone the repo and follow the README.md included in the repo. Thanks!
I would go against using the real service if it is asyncronous, but if you just want to register it to be available you can follow the mock instructions but instead of mocking with an object just import the real service. Although after seeing your TestService implementation you will need to separate the real service from the service registration and export it to be able to register it in local vue.
You need to create and prepare your custom Vue instance in your tests in order to use any custom functionalities in your unit tests (like stores, routers, and anything else). (You can use your real modules with the custom instance, don't have to mock anything.)
In your case you should create a new Vue instance with "createLocalVue" function from '#vue/test-utils' and apply your custom prototype functionalities on that. After that you can write proper test cases accessing that custom features as well.
Update:
For those that might be referring to this in the future, Vue Plugins might be a better solution for this kind of functionality.
I stumbled along this issue in GitHub and that led me to the fix I made below:
https://github.com/testing-library/vue-testing-library/issues/113
Specifically, this comment by user nikravi:
ok, I found the fix. The trick was to add
import Vue from "vue";
import Vuetify from "vuetify";
Vue.use(Vuetify);
and then the render() works without warnings.
After I manually imported Vue and set Vue.prototype.$TestService = TestService directly in the unit test, it got passed that error. Personally, I think this is pretty silly, but it worked.
After this worked, I also found that you can access the Vue instance directly within the render callback (from #testing-library/vue), so I finished on this code instead of importing Vue:
render(TestComponent, {}, vue => {
vue.prototype.$TestService = TestService;
});
I've included all the commits to solve my issue in the repo I posted previously:
https://github.com/kcrossman/VueServiceExample
Some of the tests were malformed but once I made those changes, the tests started to work and I updated some other files to be a bit nicer for people to refer to.
I'm trying to initialize a Shopify AppBridge instance (https://shopify.dev/tools/app-bridge) one time per store (user), and then use that same instance throughout my app to use the various AppBridge features.
My original idea was to add the AppBridge instance into a Vuex store; but I am unable to call any functions within the AppBridge object when retrieving it from the store (something to do with it being a serialized object?). There are various functions I need to use within the instance/object; so this won't work.
So, what is the best way for me to do the following:
App loads
Middleware or function runs, initializing AppBridge with the user's / store's details
Other pages are loaded, use the same AppBridge instance for various features
My current setup is that I re-create a new AppBridge instance on every page / component, but that causes complications.
Any ideas? Much appreciated!
due to the structure of our websites we currently are unable to create one main app instance, as there is too much HTML within this.
So instead we are currently looking for the class of app and then creating a new Vue instance per component, which isn't great for communicating between components but it's our current work around.
We are working to create a new structure to support just one overall app. However, just wondering if creating a new instance of Vue for each component is bad for browser performance over having just one instance with the component inside this?
Short answer: No
There won’t be any performance difference between an app that uses a root Vue component with child components and an app that uses multiple root Vue components.
All components are still just Vue instances - so it isn’t any different. The only difference is the organization and usage of the instances.
I have a working VueJs application, and when a user logs in, he gets some rights from the server like administrator, readOnlyUser, worker etc. Based on these privileges, some components/functionality in the application should be or, should not be available to the user. For an example I would like to put a reference to an access-right method on my differenct components/divs:
<MyComponent v-if="hasRights()"></MyComponent>
Or just access this method from the script section.
I am thinking of using mixins, this seem to suit my needs, but is mixin´s the right way to solve this issue? Are there another more patten-like correct way to do it?
My recommendation for architecture:
Create files per rights:
Admin.js
User1.js
User2.js
etc.
Now you can create a function (wherever that takes in the user from your server, and then referes you to the right file. By doing this you can call in . your components: this.$config.get().displaySomething . Where the get() knows which file to go to. Make your config available on bootstrap.
The reason I don't recommend vuex is since it sounds like your config is static and can just be hard coded into file.