Vue.js/Vuex to Pinia migration

Introduction
The intent of this article is to support mediawiki migration from Vuex to Pinia in internal projects that provide an opportunity to do so. Currently there is no need to upgrade existing project that are already in production and this update should just be performed on new project that have yet to be published or are at early stage of development.

The front end team has decided to use Pinia going forward, has it is now been recommended by the Vue core team as the successor of Vuex.

Migration in summary
The migration between this two stores is quite simple and it can even happen in stages where both store co-exist in the same application while the migration is in place.

The migration steps are:


 * Add Pinia to your current Vue instance
 * Create a Pinia store for each Vuex module
 * Update other Vuex store to use Pinia
 * Update the components to use Pinia
 * Update store unit tests

In the following sections we are going to covering in details the step required to perform the migration. A multi-step migration has already been implemented in the foundation. You can see the first partial migration of just one module here, and the complete migration here.

Pinia's official website also include a great migration guide, and this article should just be used a supplement of that guide as there are parts specific to Mediawiki or not included in the official migration (like unit test)

Add Pinia to your current Vue instance
Pinia has been added into core with the following task T326174. Pinia can be added to your existing Vue application by using the following code:

Create a store for each module you had
The main difference between Vuex and Pinia is that the latter expects you to create multiple stores. Do not worry, you can still create the nested structure you previously could achieve with Vuex.

Create folders and files
Create a folder called stores (as Pinia expects multiple stores) and add files within that folder as shown below. The difference in name Store for Vuex and Stores for Pinia also help to us to be able to clearly achieve a "step migration".

Update the store module
I suggest to update a single module at the time and start from the smaller one (do not start from root). The main documentation includes step by step tutorial on how to update the module, so I am not going to repeat that here, but just mention the most important points.


 * 1) Remove "context" from all Actions
 * 2) Get rid of mutations (they will just become private functions or actions)
 * 3) Make sure actions are NOT arrow functions. This will make the "this" undefined!
 * 4) change the function name to align with pinia standards useModuleNameStore

The following link showcase the migration of a single module by showing the comparison of the two files: Event module file migration

Update other Vuex modules to use Pinia
While achieving a step migration, you will find yourself in situation where one store (Vuex) need to call the other (Pinia). In this section we are going to see how to achieve this.

Call Pinia from a Vuex store
To update the above action call, we need to fist import the newly created store and then call it. The code after a step migration will look like this:

Update the components to use Pinia
Pinia is ready to be used within our component as it has been initialised in our Vue instance and exposes a module ready to be used. As with before, i would first read the official documentation and use this as additional support. The steps that should be followed for the migration in your components are:


 * 1) Search and replace all instances of "mapActions", "mapGetters", "mapState", "mapMutations" to "mapVuexActions", "mapVuexGetters" and so on..
 * 2) Import the Pinia store in the component that require it
 * 3) Import Pinia's methods as required mapPiniaActions, mapPiniaGetters...
 * 4) Replace the code appropriately to use the correct store.

An example migration can be seen in this patch.

Let's create a simple example to demonstrate the migration steps:

The above code is going to be changed with the following:

Update store unit tests
Updating unit tests can be tricky if your modules require each other. This section explains how to migrate unit tests from Vuex to Pinia and also explain how to handle situation in which your Vuex store depends on your Pinia store (situation that happen during a module by module migration). It is also important to note that due to the fact that Pinia architecture support the creation of multiple modules, you may find yourself creating more dependencies that when you had a single vuex store (eg with vuex you may just have a module, but with Pinia you may create one for the sidebar, one for the requestStatus, etc).

The section is going to be divided into different scenarios that will hopefully help you update your unit test with no issues at all

Vuex that depends on Pinia store
This is a simple example that is going to be very common as you work on your migration. In this case we have Vuex store that require a Pinia store. First we need to mock our Pinia store. To do this we use Jest.mock. So for our example we would create the following: Then we would use this mock in our test. This mock will automatically be used in our test.

Vuex Action that require Pinia store
Following up from the above example, also in this case we require a mock of the store to be created, then the usage is very simple and follow Jest standards:

Pinia store that require Pinia store
During the course of your migration you will also encounter situation in which your Pinia module may require another module. The code required in that case is the following.

NOTE: you can still use the mock methodology above if you prefer to have less of an integration test.

Let's start by setting our test up. In the following test we have 2 store beind setup. The first is the one we are going to test, the second is the dependent one. Now it is time to see how we would use both actions and state/getters from the dependent store:

Circular dependencies issue
Unfortunately, due to the "dependence architecture" proposed by Pinia, you may find yourself with a circular dependence. This happen when there is a never ending circle when one resource require another that require another that require the first one. The resource loader is not currently able to handle this and would require us to work around this.

There is no correct way in solving this issue, but if you encounter this issue the solutions could be:


 * Move the shared value into its own store (even if it is going to be a simple store that has one value and one action)
 * Instead than depend on values, pass them when calling actions as paramethers.
 * Decouple the dependencies on that value all together