Electron is a framework for quickly developing cross-platform desktop applications. Together with Vue.js and Vuex an individual can be productive in a short period of time.

In a past project a client wanted a desktop app that supported multiple browser windows and all windows shared a state. That last point is a tricky one.

Electron Browser Windows are basically the same as a Chrome Browser Window since Electron is built on top of Chromium. The windows cannot communicate directly with each other and any communication has to be done through the main process.

The structure of windows will be a master/slave architecture. The main window will commit a mutation and the secondary window will follow suit. The main process will receive messages from the main window renderer and relay them to the desired target. ipcRenderer module will allow us to send a message from the windows to the main process. ipcMain module will allow us to listen to any messages sent by the main window.

The main process will listen for the vuex-mutation message and then relay that information to the secondary window:

1
2
3
4
5
6
7
8
ipcMain.on('vuex-mutation', (event, mutation) => {
    // currently only 1 window, but if you require multiple windows
    // you can store them in an array and loop over all the items
    secondaryWindow.webContents.send(
        'vuex-scoreboard-mutation',
        mutation
    );
});

Any time the Vuex module commits a mutation we will send that data to the main process that will dispatch to all the other windows. Luckily Vuex supports a plugin architecture that allows us to subscribe to any mutations.

Lets create the plugin:

1
2
3
4
5
6
7
8
9
import { ipcRenderer } from "electron";

export const plugin = (store) => {
    // send a message to main process every time
    // there is a mutation.
    store.subscribe((mutation, state) => {
        ipcRenderer.send("vuex-mutation", mutation);
    });
};

Add plugin to the vuex constructor:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import Vue from "vue";
import Vuex from "vuex";
import { plugin } from "./plugin.js";
Vue.use(Vuex);

const store = new Vuex.Store({
    // ...
    plugins: [
        plugin
    ],
});

The slave window needs to listen for vuex-scoreboard-mutation messages from the main process and then relays that data to the vuex action. In the slave window add:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const { ipcRenderer } = require("electron");

export default {
    mounted () {
        ipcRenderer.on("vuex-scoreboard-mutation", (event, mutation) => {
            // commit the mutation to the local state
            this.$store.commit(mutation.type, mutation.payload);
        });
    },
};

That’s it (more or less). Depending on your needs this could be most of the logic you require to get it working. You could go a step further and split your vuex code into modules, which is what I needed to do.

I actually needed a mostly synced state. You might also need to know if the current Vue instance is in the master or slave instance and that is where the states would differ.

Other things that could be added in order to make the windows state as desired or seamless as possible:

  • Full state load when secondary window is created.
  • Ignore unwated mutations in secondary window.
  • Load from local storage.