Building a Cross‑Platform Gulp UI with Electron, React, Redux, and Immutable.js
feWorkflow replaces the original gulp‑ui by leveraging Electron to run a bundled gulpfile, while React, Redux, and Immutable.js provide a modern UI and state management, and Webpack compiles ES6 code; the article details the architecture, setup, key features, and code examples for this desktop workflow tool.
Background and Motivation
In 2015 a gulp workflow was created for the team, but as project dependencies grew, pulling npm packages became error‑prone. To simplify configuration a quick desktop app called gulp‑ui was built with NW.js to package the gulpfile and its node_modules. After using it for a while the single‑project approach proved inconvenient for multi‑project development, leading to a complete rewrite named feWorkflow.
Framework Selection
The new version switched from NW.js to Electron because Electron offers a cleaner API for creating desktop applications with JavaScript and HTML, and it integrates Node.js for system access. Electron’s deployment is straightforward: the application files must reside in an app folder inside the Electron resources directory ( Electron.app/Contents/Resources/ on macOS, resources/ on Windows/Linux).
electron/Electron.app/Contents/Resources/app/
├── package.json
├── main.js
└── index.htmlRunning Electron.app (or electron / electron.exe on Linux/Windows) launches the bundled app.
UI Stack
feWorkflow uses React for UI rendering, Redux for predictable state management, and Immutable.js for immutable data structures. The UI is written in ES6, replacing the older React.createClass syntax.
class HelloWorld extends React.Component {
render() {
return <section>Hello {this.props.name}</section>;
}
}Webpack Configuration
Webpack bundles the ES6/React source code. It uses babel-loader to transpile JSX and ES6, and less-loader, css-loader, style-loader to embed styles. The ProvidePlugin automatically injects React into every module, avoiding repetitive imports. The final bundle is emitted as dist/bundle.js.
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'source-map',
entry: ['./src/index'],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/dist/'
},
target: 'atom',
module: {
loaders: [{
test: /\.js$/,
include: path.join(__dirname, 'src'),
loader: require.resolve('babel-loader')
}]
}
};Redux Integration
The application defines a single store that holds UI state, button status, and data lists. Actions are dispatched to update the store, and reducers (combined with combineReducers) produce new immutable state objects.
import { createStore } from 'redux';
import reducer from '../reducer/reducer';
export default createStore(reducer);Typical actions include { type: 'PROCESSING', ... } and { type: 'SET_SYNC_FOLDER', ... }. Reducers use Immutable.js methods such as setIn and withMutations to produce updated state without mutating the original.
Immutable.js Usage
Immutable.js provides List and Map structures. The workflow stores configuration data (e.g., sync folders) as immutable lists, updates them with setIn, and persists the state to localStorage by converting to plain JS with toJS() and JSON.stringify. Loading reverses the process with fromJS().
export const saveState = (name = 'state', state) => {
try {
const data = JSON.stringify(state.toJS());
localStorage.setItem(name, data);
} catch (err) {
console.log('err', err);
}
};
export const loadState = (name = 'setting') => {
try {
const data = localStorage.getItem(name);
if (data === null) return undefined;
return fromJS(JSON.parse(data), (k, v) =>
Iterable.isIndexed(v) ? v.toList() : v.toMap()
);
} catch (err) {
return undefined;
}
};Gulp Integration via Child Process
Button components invoke child_process.exec to run gulp commands inside the Electron environment. The command includes --cwd to point to the project’s src folder and --gulpfile to force the use of the bundled gulpfile.js. The UI tracks the process ID, captures stdout/stderr, updates the Redux store, and displays notifications.
let child = exec(`gulp ${cmd} --cwd ${listLocation} ${flag} --gulpfile ${cwd}/gulpfile.js`);
child.stderr.on('data', data => {
console.error('exec error: ' + data);
kill(pid);
// dispatch failure action
});
child.stdout.on('data', data => {
// dispatch processing action
});
child.stdout.on('close', () => {
// dispatch completion action
});Overall Architecture
The final architecture consists of:
Electron as the desktop container.
React for component rendering.
Redux + Immutable.js for state management.
Webpack for bundling ES6/JSX code.
Gulp executed via child processes to perform build tasks.
All source code is open‑source on GitHub, allowing developers to extend or adapt the workflow.
References
Electron documentation
Babel + React on ES6+
Webpack guide
Redux documentation
Aotu Lab
Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
