vuex-electron-bridge
v1.0.1
Published
Share Vuex mutations across Electron processes using a ContextBridge
Downloads
4
Maintainers
Readme
Vuex Electron Bridge
Share Vuex mutations across Electron processes using a ContextBridge.
Only supports Vuex 4. Example application available here.
Features
- Modern Electron - Works with Context Isolation and Process Sandboxing.
- Shared Mutations - Syncs mutations across all processes, including main.
- Persisted State - Permanently persist state using your choice of storage.
- Defaults to electron-store, a file store that supports Encryption and Migrations.
- Explicit API - Provides clear indication when sharing occurs.
- Vue Devtools Compatible!
Overview
vuex-electron-bridge
utilizes the Electron contextBridge
and IPC to enable mutation sharing across all processes of an Electron app.
Two new methods are provided to your Vuex store, shareCommit()
and localCommit()
, allowing you to explicitly choose
when a mutation is shared or kept local.
The following diagram visualizes the behavior of shareCommit
. Note that shareCommit
will call localCommit
before
sharing, and the broker ensures that the sharer does not receive a duplicate.
Installation
npm install vuex-electron-bridge
Note: Requires Electron 12 or later. Requires Context Isolation and a preload script. Only supports Vuex 4.
If you plan to persist state, npm install electron-store
(optional, can supply your own)
Setup
- Add the plugin to your Vuex store
// [store].js
import Vuex from "vuex"
import VuexBridge from "vuex-electron-bridge"
export default new Vuex.Store({
plugins: [
VuexBridge.createPlugin()
]
})
- Create the
Bridge
andmount
it with your store in the main process.
// [electron].js
import { app } from "electron"
import store from "./path/to/your/store"
import VuexBridge from "vuex-electron-bridge"
const bridge = VuexBridge.createBridge()
app.on('ready', () => bridge.mount(store))
- Expose the
Bridge
in your preload script
// [preload].js
import VuexBridge from "vuex-electron-bridge"
VuexBridge.exposeBridge()
// you may need to use require //
require("vuex-electron-bridge")
.exposeBridge()
Congratulations, you can now share commits across processes!
Usage
Simply use the new method shareCommit()
as you would commit()
. Check out
the example app for more.
// in a store
export default {
mutations: {
['SET_RESULT']: (state, payload) => {
state.result = payload
}
}
actions: {
['GET_RESULT']: async ({ shareCommit }) => {
shareCommit('SET_RESULT', await someApi.getResult())
}
}
}
// in Vue components
export default {
name: 'SomeVueComponent',
methods: {
getResult() {
// using the action
this.$store.dispatch('GET_RESULT')
// or directly
this.$store.shareCommit('SET_RESULT', 'awesome')
}
}
}
// in the main process
import store from "./path/to/your/store"
// using the action
store.dispatch('GET_RESULT')
// or directly
store.shareCommit('SET_RESULT', 'incredible')
IMPORTANT
shareCommit()
works by sending your commit over IPC. This means that any data you pass to it must be serializable according to the HTML standard Structured Clone Algorithm. Some of your data may require cloning or other types of preparation before it can be shared.- Calls to
commit()
are not shared and will log a warning to notify you of this. Although the warning can be disabled, you should instead use the provided aliaslocalCommit()
which does not log a warning and clearly indicates a mutation will be local. - Calls to
dispatch()
are not shared. They execute on the process that called them. - Renderers are initially hydrated with the entire state object from the main process.
- Only the main process state can be persisted. Local mutations on renderers will therefore not be persisted.
Warning
By definition, sharing some mutations and not others leads to a state mismatch between your Vuex stores. If you don't plan to do this (or you do and know what you're doing) go on and skip ahead.
Due to the complexities involved with mixing local and shared mutations, the standing recommendation is to exclusively
use shareCommit()
to save yourself from a potential headache. However, if applied carefully, the flexibility this
technique provides can be an incredibly powerful tool. Being able to share some mutations and not others allows you to
enjoy all the benefits of shared mutations and account for other requirements when working with large or complex apps.
For smaller applications, you should exclusively use shareCommit()
and not look back. For larger, more complex apps,
you may see some benefits, such as performance, from clever usage of localCommit()
.
Helper Module
By default, vuex-electron-bridge
installs a "helper module" into your Vuex store. It can be disabled or renamed,
however, it's recommended to allow this module (don't worry, the module will not be persisted).
The module provides a simple getter for hydration status (default name: vuexIsHydrated
) to allow for your own
conditional logic (such as delaying some operations until state is received), and also enables mutation-based state hydration for renderers. Without the module, state is hydrated
using Vuex.replaceState
, which works just fine for hydration, but has the unfortunate side effect of totally breaking
state viewing/editing in VueDevtools (VueDevtools will only display the default state of your store).
Hydration via the helper module's provided mutation works around this issue, allow you to view and edit state in VueDevtools. There is still an issue with editing state while in a nested module view, but any state can still be edited normally from the root state view. Hopefully this can be completely fixed in future versions.
API
createBridge()
createBridge( store, <object>[options] )
Returns a new Bridge
instance which will broker mutations from renderers and handle state persistence. Accepts your
Vuex store instance and an options object. Passing your store to createBridge
will immediately mount the
bridge. Omitting your store requires you to use mount()
at some later point, such as in app.on('ready')
.
Bridge.mount( store, <object>[options] )
- Mounts the Bridge with your store and choice of options.Bridge.unmount()
- Destroys the Bridge and attempts to persist state.
exposeBridge()
exposeBridge( <string>bridgeName | <object>[options] )
Call in your preload script to expose the Bridge
to your renderers. Accepts a string or options object.
createPlugin()
createPlugin( <object>[options] )
Returns a plugin thats extends your Vuex store with the following methods: Accepts an options object.
shareCommit(type, payload, options)
- Shares a mutation with other processes.localCommit(type, payload, options)
- Locally commit a mutation. Alias ofcommit()
Options
Note: vuex-electron-bridge
has state persistence disabled by default. If you just need to enable persistence, skip to
the Bridge Options. If you are using many options, consider creating an options.js
file
(or similar) that exports your options object, as there are three places in which options can be passed.
Shared Options
Currently just the one - if you change this it must be passed to
createPlugin()
, exposeBridge()
, and either createBridge()
or Bridge.mount()
.
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| bridgeName | string | '__vuex_bridge'
| Name for the contextBridge exposed on a renderer's window
object and also used to namespace IPC events. Not necessary to change unless running multiple Bridge()
instances. |
Plugin Options
Options specific to the Plugin
instance. Pass these to createPlugin( [options] )
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| warnAboutCommit | boolean | true
| Set to false
to disable logging a warning when commit()
is used. |
| allowHelperModule | boolean | true
| Set to false
to disable adding the helper module to your store. (Don't worry... the module is excluded from persistence) |
| moduleName | string | '__vuex_bridge'
| Renames the helper module |
| getterPrefix | string | 'vuex'
| Prefix for the getters provided by the helper module |
Bridge Options
Options specific to the Bridge
instance. Pass these to either Bridge.mount(store, [options])
or createBridge(store, [options])
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| persist | boolean | false
| Toggles state persistence. |
| persistThrottle | int (milliseconds) | 1000
| Throttles state persistence. 0 to disable, increase to reduce I/O load. |
| storageOptions | object | { name: 'vuex' }
| Accepts all electron-store options |
| storageKey | string | 'state'
| Top-level JSON key name used to persist state when using electron-store
|
| storageTestKey | string | 'test'
| Top-level JSON key name used to test save/load functionality of electron-store
. (see option storageTester
) |
| storageProvider | Instance | null | Accepts an instance of your own storage provider. storageOptions
are ignored when using your own provider. storageKey
and storageTestKey
are only used by the Getter/Setter/Tester functions. |
| storageGetter | Function | See definition in options.js | Loads state using your custom storage provider |
| storageSetter | Function | See definition in options.js | Saves state using your custom storage provider |
| storageTester | Function | See definition in options.js | Tests your storage provider for basic functionality |
Likely Questions
What if I use commit()
instead of shareCommit()
on accident?
The commit will still execute and mutate the local state of the process on which it was called. vuex-electron-bridge
will warn you of this, but ultimately, it won't stop you. If this was intentional, you should use the commit
alias localCommit()
which does not generate a warning.
What if I have an action that must only run on the main process?
That behavior is currently beyond the scope of this library, and is therefore left to you. To do this, you will likely
use IPC to call a method on your main process which from there will dispatch your action. If that action
utilizes shareCommit()
, the resulting data will be automagically passed back to the state of the renderers.
Isn't it bad for performance for every process to execute the same commit?
Depends on the application. In my testing, it certainly appears more performant than other solutions which play ping-pong with a giant state object between multiple processes. Ultimately, this answer will depend on your own testing under your own requirements.