Skip to content

Commit

Permalink
fix: remove default custom serialization and make it an opt-in option (
Browse files Browse the repository at this point in the history
…#272)

Removing custom serializer as a default, since its not alligned with redux recomendation about store
& serialization

fix #261
  • Loading branch information
matmalkowski committed Oct 26, 2020
1 parent 4911227 commit 5238bce
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 186 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -8,3 +8,4 @@ es
types
*.log
e2e_dist
.DS_Store
10 changes: 10 additions & 0 deletions .npmignore
@@ -0,0 +1,10 @@
node_modules
coverage
*.log
e2e_dist
src
tests
.github
.vscode

.DS_Store
17 changes: 2 additions & 15 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "electron-redux",
"version": "2.0.0-alpha.0",
"version": "0.0.0-dev.0",
"description": "Redux & Electron: Make sure all your stores are on the same page",
"repository": "https://github.com/klarna/electron-redux.git",
"authors": [
Expand All @@ -11,18 +11,15 @@
"license": "MIT",
"private": false,
"main": "lib/electron-redux.js",
"unpkg": "dist/electron-redux.js",
"module": "es/electron-redux.js",
"types": "types/index.d.ts",
"files": [
"dist",
"lib",
"es",
"src",
"types"
],
"scripts": {
"clean": "rimraf lib dist es coverage types",
"clean": "rimraf lib es coverage types",
"start:electron": "electron-webpack dev",
"build": "rollup -c",
"build:electron": "electron-webpack",
Expand Down Expand Up @@ -53,7 +50,6 @@
"@babel/preset-typescript": "^7.10.4",
"@rollup/plugin-commonjs": "^15.0.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-replace": "^2.3.3",
"@types/jest": "^26.0.14",
"@types/node": "^14.11.1",
"@typescript-eslint/eslint-plugin": "^4.4.0",
Expand All @@ -78,7 +74,6 @@
"rimraf": "^3.0.2",
"rollup": "^2.27.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.27.2",
"semantic-release": "^17.2.1",
"spectron": "^11.1.0",
Expand All @@ -87,14 +82,6 @@
"webpack": "^4.44.2"
},
"npmName": "electron-redux",
"npmFileMap": [
{
"basePath": "/dist/",
"files": [
"*.js"
]
}
],
"jest": {
"testRegex": "(/tests/.*\\.spec\\.[tj]s)$"
},
Expand Down
76 changes: 0 additions & 76 deletions rollup.config.js
@@ -1,9 +1,7 @@
import nodeResolve from '@rollup/plugin-node-resolve'
import babel from 'rollup-plugin-babel'
import replace from '@rollup/plugin-replace'
import commonjs from '@rollup/plugin-commonjs'
import typescript from 'rollup-plugin-typescript2'
import { terser } from 'rollup-plugin-terser'

import pkg from './package.json'

Expand Down Expand Up @@ -43,78 +41,4 @@ export default [
}),
],
},

// ES for Browsers
{
input: 'src/index.ts',
output: { file: 'es/electron-redux.mjs', format: 'es', indent: false },
plugins: [
...basePlugins,
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
babel({
extensions,
exclude: 'node_modules/**',
}),
terser({
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
warnings: false,
},
}),
],
},

// UMD Development
{
input: 'src/index.ts',
output: {
file: 'dist/electron-redux.js',
format: 'umd',
name: 'ElectronRedux',
indent: false,
},
plugins: [
...basePlugins,
babel({
extensions,
exclude: 'node_modules/**',
}),
replace({
'process.env.NODE_ENV': JSON.stringify('development'),
}),
],
},

// UMD Production
{
input: 'src/index.ts',
output: {
file: 'dist/electron-redux.min.js',
format: 'umd',
name: 'ElectronRedux',
indent: false,
},
plugins: [
...basePlugins,
babel({
extensions,
exclude: 'node_modules/**',
}),
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
terser({
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
warnings: false,
},
}),
],
},
]
6 changes: 3 additions & 3 deletions src/index.ts
@@ -1,5 +1,5 @@
import { syncMain } from './syncMain'
import { mainStateSyncEnhancer } from './mainStateSyncEnhancer'
import { stopForwarding } from './utils'
import { syncRenderer } from './syncRenderer'
import { rendererStateSyncEnhancer } from './rendererStateSyncEnhancer'

export { syncMain, syncRenderer, stopForwarding }
export { mainStateSyncEnhancer, rendererStateSyncEnhancer, stopForwarding }
64 changes: 64 additions & 0 deletions src/mainStateSyncEnhancer.ts
@@ -0,0 +1,64 @@
import { ipcMain, webContents } from 'electron'
import { Action, applyMiddleware, Middleware, StoreCreator, StoreEnhancer } from 'redux'

import { preventDoubleInitialization, stopForwarding, validateAction } from './utils'

function createMiddleware(options: MainStateSyncEnhancerOptions) {
const middleware: Middleware = (store) => {
ipcMain.handle('electron-redux.INIT_STATE', async () => {
// Serialize the initial state using custom replacer
return JSON.stringify(store.getState(), options.replacer)
})

// When receiving an action from a renderer
ipcMain.on('electron-redux.ACTION', (event, action: Action) => {
const localAction = stopForwarding(action)
store.dispatch(localAction)

// Forward it to all of the other renderers
webContents.getAllWebContents().forEach((contents) => {
// Ignore the renderer that sent the action
if (contents.id !== event.sender.id) {
contents.send('electron-redux.ACTION', localAction)
}
})
})

return (next) => (action) => {
if (validateAction(action)) {
webContents.getAllWebContents().forEach((contents) => {
contents.send('electron-redux.ACTION', action)
})
}

return next(action)
}
}
return middleware
}

export type MainStateSyncEnhancerOptions = {
/**
* Custom store serializaton function. This function is called for each member of the object.
* If a member contains nested objects,
* the nested objects are transformed before the parent object is.
*/
replacer?: (this: unknown, key: string, value: unknown) => unknown
}

const defaultOptions: MainStateSyncEnhancerOptions = {}

/**
* Creates new instance of main process redux enhancer.
* @param {MainStateSyncEnhancerOptions} options Additional enhancer options
* @returns StoreEnhancer
*/
export const mainStateSyncEnhancer = (options = defaultOptions): StoreEnhancer => (
createStore: StoreCreator
) => {
preventDoubleInitialization()
const middleware = createMiddleware(options)
return (reducer, state) => {
return createStore(reducer, state, applyMiddleware(middleware))
}
}
41 changes: 33 additions & 8 deletions src/syncRenderer.ts → src/rendererStateSyncEnhancer.ts
@@ -1,19 +1,24 @@
import { ipcRenderer } from 'electron'
import { Action, applyMiddleware, Middleware, Reducer, StoreCreator, StoreEnhancer } from 'redux'

import { hydrate, preventDoubleInitialization, stopForwarding, validateAction } from './utils'
import { preventDoubleInitialization, stopForwarding, validateAction } from './utils'

async function getRendererState(callback: (state: unknown) => void) {
async function fetchInitialState(
options: RendererStateSyncEnhancerOptions,
callback: (state: unknown) => void
) {
// Electron will throw an error if there isn't a handler for the channel.
// We catch it so that we can throw a more useful error
const state = await ipcRenderer.invoke('electron-redux.FETCH_STATE').catch((error) => {
console.error(error)
throw new Error('No Redux store found in main process. Did you use the syncMain enhancer?')
const state = await ipcRenderer.invoke('electron-redux.INIT_STATE').catch((error) => {
console.warn(error)
throw new Error(
'No Redux store found in main process. Did you use the mainStateSyncEnhancer in the MAIN process?'
)
})

// We do some fancy hydration on certain types like Map and Set.
// See also `freeze`
callback(JSON.parse(state, hydrate))
callback(JSON.parse(state, options.reviver))
}

/**
Expand Down Expand Up @@ -61,7 +66,27 @@ const middleware: Middleware = (store) => {
}
}

export const syncRenderer: StoreEnhancer = (createStore: StoreCreator) => {
export type RendererStateSyncEnhancerOptions = {
/**
* Custom function used during de-serialization of the redux store to transform the object.
* This function is called for each member of the object. If a member contains nested objects,
* the nested objects are transformed before the parent object is.
*/
reviver?: (this: unknown, key: string, value: unknown) => unknown
}

const defaultOptions: RendererStateSyncEnhancerOptions = {}

/**
* Creates new instance of renderer process redux enhancer.
* Upon initialization, it will fetch the state from the main process & subscribe for event
* communication required to keep the actions in sync.
* @param {RendererStateSyncEnhancerOptions} options Additional settings for enhancer
* @returns StoreEnhancer
*/
export const rendererStateSyncEnhancer = (options = defaultOptions): StoreEnhancer => (
createStore: StoreCreator
) => {
preventDoubleInitialization()

return (reducer, state) => {
Expand All @@ -74,7 +99,7 @@ export const syncRenderer: StoreEnhancer = (createStore: StoreCreator) => {
// This is the reason we need to be an enhancer, rather than a middleware.
// We use this (along with the wrapReducer function above) to dispatch an
// action that initializes the store without needing to fetch it synchronously.
getRendererState((state) => {
fetchInitialState(options, (state) => {
store.dispatch(replaceState(state))
})

Expand Down
45 changes: 0 additions & 45 deletions src/syncMain.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/utils/index.ts
@@ -1,3 +1,2 @@
export * from './actions'
export * from './json'
export * from './misc'
30 changes: 0 additions & 30 deletions src/utils/json.ts

This file was deleted.

0 comments on commit 5238bce

Please sign in to comment.