diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..770a808 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +dist/ +**/node_modules/** diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..886f901 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,38 @@ +{ + "parser": "babel-eslint", + "env": { + "browser": true, + "node": true, + "es6": true + }, + "extends": [ + "eslint:recommended" + ], + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "modules": true + } + }, + "rules": { + "object-curly-spacing": [2, "always"], + "object-shorthand": [2, "always"], + "one-var": [2, "never"], + "no-array-constructor": 2, + "no-console": 1, + "no-const-assign": 2, + "no-new-object": 2, + "no-unused-vars": 1, + "no-var": 2, + "prefer-arrow-callback": 2, + "prefer-const": 2, + "prefer-spread": 1, + "prefer-template": 2, + "quote-props": [2, "as-needed"], + "quotes": [2, "single"], + "require-yield": 1, + "semi": 2, + "keyword-spacing": 2, + "space-before-function-paren": 2 + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f9244c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +npm-debug.log +*.tgz + +dist/ +lib/ +node_modules/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..4ed96cf --- /dev/null +++ b/.npmignore @@ -0,0 +1,10 @@ +.eslintrc +.eslintignore +.npmignore +webpack.config.js +*.tgz + +!dist/ + +example/ +src/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..2855e27 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Redux Electron IPC Middleware +A [Redux](https://github.com/reactjs/redux) middleware to reduce code around ipc +calls in an [Electron](http://electron.atom.io/) application. You can send and +receive +[IPC](https://github.com/electron/electron/blob/master/docs/api/ipc-main.md) +events with a simple api. + +## Usage +Check out the full demo application. + +### Window +```js +import { applyMiddleware, createStore } from 'redux'; +import createIpc from 'redux-electron-ipc'; +import { pingActionCreator, pongActionCreator } from './actions'; +import { exampleReducer } from './reducer'; + +// register an action creator to an ipc channel (key/channel, value/action creator) +const ipc = createIpc({ + 'pong': pongActionCreator, // receive a message + ... +}); + +const store = createStore(exampleReducer, applyMiddleware(ipc)); + +// send a message with arguments +store.dispatch(pingActionCreator('redux', 'electron', 'ipc')); +``` + +### Main +```js +const electron = require('electron'); +const { ipcMain } = electron; + +... + +// pong event with arguments back to caller +ipcMain.on('ping', (event, ...args) => { + console.log('Ping', ...args); + event.sender.send('pong', ...args); +}); +``` diff --git a/example/app/actions.js b/example/app/actions.js new file mode 100644 index 0000000..238f53f --- /dev/null +++ b/example/app/actions.js @@ -0,0 +1,20 @@ +export function pingActionCreator (arg1, arg2, arg3) { + return { + type: 'IPC_PING', + channel: 'ping', + args: [ + arg1, + arg2, + arg3 + ] + }; +} + +export function pongActionCreator (event, arg1, arg2, arg3) { + return { + type: 'IPC_PONG', + arg1, + arg2, + arg3 + }; +} diff --git a/example/app/app.js b/example/app/app.js new file mode 100644 index 0000000..4f6f507 --- /dev/null +++ b/example/app/app.js @@ -0,0 +1,11 @@ +import { applyMiddleware, createStore } from 'redux'; +import createIpc from '../../'; +import { pingActionCreator, pongActionCreator } from './actions'; +import { exampleReducer } from './reducer'; + +const ipc = createIpc({ + 'pong': pongActionCreator // eslint-disable-line quote-props +}); +const store = createStore(exampleReducer, applyMiddleware(ipc)); + +store.dispatch(pingActionCreator('redux', 'electron', 'ipc')); diff --git a/example/app/reducer.js b/example/app/reducer.js new file mode 100644 index 0000000..176a877 --- /dev/null +++ b/example/app/reducer.js @@ -0,0 +1,9 @@ +export function exampleReducer (state = {}, action) { + switch (action.type) { + case 'IPC_PONG': + console.log('Pong', action); // eslint-disable-line no-console + return state; + default: + return state; + } +} diff --git a/example/main.js b/example/main.js new file mode 100644 index 0000000..19c1e42 --- /dev/null +++ b/example/main.js @@ -0,0 +1,22 @@ +const electron = require('electron'); +const { app, BrowserWindow, ipcMain } = electron; + +let win; + +app.on('ready', () => { + win = new BrowserWindow(); + + win.loadURL(`file://${__dirname}/dist/index.html`); + + win.webContents.openDevTools(); + + win.on('closed', () => { + win = null; + }); +}); + +// ping pong event with arguments back to caller +ipcMain.on('ping', (event, arg1, arg2, arg3) => { + console.log('Ping', arg1, arg2, arg3); // eslint-disable-line no-console + event.sender.send('pong', arg1, arg2, arg3); +}); diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..2b2d7cb --- /dev/null +++ b/example/package.json @@ -0,0 +1,16 @@ +{ + "name": "redux-electron-ipc-middleware-example", + "main": "main.js", + "version": "1.0.0", + "scripts": { + "start": "./node_modules/.bin/webpack ./app/app.js && electron ." + }, + "devDependencies": { + "babel-core": "^6.9.1", + "babel-loader": "^6.2.4", + "babel-preset-es2015": "^6.9.0", + "html-webpack-plugin": "^2.19.0", + "redux": "^3.5.2", + "webpack": "^1.13.1" + } +} diff --git a/example/webpack.config.js b/example/webpack.config.js new file mode 100644 index 0000000..e7af7ef --- /dev/null +++ b/example/webpack.config.js @@ -0,0 +1,29 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + entry: './app/app.js', + output: { + path: 'dist', + filename: 'bundle.js' + }, + module: { + loaders: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + loader: 'babel', + query: { + presets: [ + 'es2015' + ] + } + } + ] + }, + target: 'electron', + plugins: [ + new HtmlWebpackPlugin({ + title: 'Redux Electron IPC Middleware Example' + }) + ] +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..3976324 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./dist/electron-redux-ipc.js'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..276e95a --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "redux-electron-ipc", + "version": "1.0.0", + "description": "Redux Electron IPC Middleware", + "main": "index.js", + "directories": { + "example": "example" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "./node_modules/.bin/eslint .", + "build": "WEBPACK_ENV=production ./node_modules/.bin/webpack", + "dev": "./node_modules/.bin/webpack --watch" + }, + "keywords": [ + "redux", + "electron", + "ipc", + "middleware" + ], + "author": "Mario Tacke ", + "license": "MIT", + "devDependencies": { + "babel-core": "^6.9.1", + "babel-eslint": "^6.0.4", + "babel-loader": "^6.2.4", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-preset-es2015": "^6.9.0", + "eslint": "^2.11.1", + "webpack": "^1.13.1" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e47b935 --- /dev/null +++ b/src/index.js @@ -0,0 +1,23 @@ +import { ipcRenderer as ipc } from 'electron'; + +function createIpc (events = {}, prefix = 'IPC_') { + return ({ dispatch }) => { + Object.keys(events).forEach((key) => { + ipc.on(key, function () { + dispatch(events[key](...arguments)); + }); + }); + + return function (next) { + return function (action) { + if (action.channel && action.type.startsWith(prefix)) { + ipc.send(action.channel, ...(action.args || [])); + } + + return next(action); + }; + }; + }; +} + +export default createIpc; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..224193f --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,48 @@ +'use strict'; + +const webpack = require('webpack'); +const UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; +const env = process.env.WEBPACK_ENV; +const library = 'electron-redux-ipc'; +const plugins = []; + +let filename; + +if (env === 'production') { + plugins.push(new UglifyJsPlugin({ minimize: true })); + filename = `${library}.min.js`; +} else { + filename = `${library}.js`; +} + +const config = { + entry: `${__dirname}/src/index.js`, + output: { + path: `${__dirname}/dist`, + filename, + library, + libraryTarget: 'umd', + umdNamedDefine: true + }, + module: { + loaders: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + loader: 'babel', + query: { + presets: [ + 'es2015' + ], + plugins: [ + 'babel-plugin-add-module-exports' + ] + } + } + ] + }, + target: 'electron', + plugins +}; + +module.exports = config;