├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .prettierrc.yml
├── .travis.yml
├── .vscode
└── settings.json
├── README.md
├── app
├── main
│ └── index.js
└── renderer
│ ├── .eslintrc
│ ├── actions
│ └── user.js
│ ├── app.js
│ ├── components
│ ├── LoggedIn.js
│ └── Login.js
│ ├── containers
│ ├── LoggedInPage.js
│ └── LoginPage.js
│ ├── index.html
│ ├── reducers
│ └── user.js
│ ├── routes.js
│ └── store.js
├── babel.config.js
├── dist-assets
└── .gitkeep
├── electron-builder.yml
├── gulpfile.js
├── init.js
├── package-lock.json
├── package.json
├── tasks
├── assets.js
├── distribution.js
├── electron.js
├── hotreload.js
├── scripts.js
└── watch.js
└── test
├── .eslintrc
├── actions
└── user.spec.js
└── reducers
└── user.spec.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{js,json,css,html}]
12 | indent_style = space
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | cache
4 | lib
5 | dist
6 | webpack.*.js
7 | server.js
8 | build.js
9 | init.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:react/recommended",
6 | "prettier"
7 | ],
8 | "parser": "babel-eslint",
9 | "parserOptions": {
10 | "ecmaFeatures": {
11 | "jsx": true,
12 | "modules": true
13 | }
14 | },
15 | "plugins": [ "react" ],
16 | "rules": {
17 | "prefer-const": "warn",
18 | "no-console": "off",
19 | "no-loop-func": "warn",
20 | "new-cap": "off",
21 | "no-param-reassign": "warn",
22 | "func-names": "off",
23 | "no-unused-expressions" : "error",
24 | "block-scoped-var": "error",
25 | "react/prop-types": "off"
26 | },
27 | "settings": {
28 | "react": {
29 | "pragma": "React",
30 | "version": "16.2"
31 | }
32 | },
33 | "env": {
34 | "es6": true,
35 | "node": true
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | build
4 | .DS_Store
5 | *.log
6 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | # .prettierrc.yml
2 | # see: https://prettier.io/docs/en/options.html
3 | printWidth: 100
4 | semi: true
5 | singleQuote: true
6 | trailingComma: all
7 | bracketSpacing: true
8 | jsxBracketSameLine: true
9 | arrowParens: always
10 | proseWrap: always
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - '10'
7 | cache: npm
8 | services:
9 | - xvfb
10 |
11 | install:
12 | - npm install
13 |
14 | script:
15 | - npm run check-format
16 | - npm run lint
17 | - npm test
18 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "build/": true,
4 | "dist/": true
5 | }
6 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # electron-react-redux-boilerplate
2 | [](https://travis-ci.org/jschr/electron-react-redux-boilerplate)
3 | [](https://david-dm.org/jschr/electron-react-redux-boilerplate)
4 | [](https://david-dm.org/jschr/electron-react-redux-boilerplate?type=dev)
5 |
6 | A minimal boilerplate to get started with [Electron](http://electron.atom.io/), [React](https://facebook.github.io/react/) and [Redux](http://redux.js.org/).
7 |
8 | Including:
9 |
10 | * [React Router](https://reacttraining.com/react-router/)
11 | * [Redux Thunk](https://github.com/gaearon/redux-thunk/)
12 | * [Redux Actions](https://github.com/acdlite/redux-actions/)
13 | * [Redux Local Storage](https://github.com/elgerlambert/redux-localstorage/)
14 | * [Electron Packager](https://github.com/electron-userland/electron-packager)
15 | * [Electron DevTools Installer](https://github.com/MarshallOfSound/electron-devtools-installer)
16 | * [Electron Mocha](https://github.com/jprichardson/electron-mocha)
17 | * [Browsersync](https://browsersync.io/)
18 |
19 | ## Quick start
20 |
21 | Clone the repository
22 | ```bash
23 | git clone --depth=1 https://github.com/jschr/electron-react-redux-boilerplate
24 | ```
25 |
26 | Install dependencies
27 | ```bash
28 | cd electron-react-redux-boilerplate
29 | npm install
30 | ```
31 |
32 | Development
33 | ```bash
34 | npm run develop
35 | ```
36 |
37 | ## DevTools
38 |
39 | Toggle DevTools:
40 |
41 | * macOS: Cmd Alt I or F12
42 | * Linux: Ctrl Shift I or F12
43 | * Windows: Ctrl Shift I or F12
44 |
45 | ## Packaging
46 |
47 | Modify [electron-builder.yml](./electron-builder.yml) to edit package info.
48 |
49 | For a full list of options see: https://www.electron.build/configuration/configuration
50 |
51 | Create a package for macOS, Windows or Linux using one of the following commands:
52 |
53 | ```
54 | npm run pack:mac
55 | npm run pack:win
56 | npm run pack:linux
57 | ```
58 |
59 | ## Tests
60 |
61 | ```
62 | npm run test
63 | ```
64 |
65 | ## Maintainers
66 |
67 | - [@jschr](https://github.com/jschr)
68 | - [@pronebird](https://github.com/pronebird)
69 |
70 | ## Apps using this boilerplate
71 |
72 | - [Mullvad VPN app](https://github.com/mullvad/mullvadvpn-app)
73 | - [YouTube Downloader Electron](https://github.com/vanzylv/youtube-downloader-electron)
74 | - [Martian: A Websocket test tool](https://github.com/drex44/martian)
75 |
--------------------------------------------------------------------------------
/app/main/index.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { app, crashReporter, BrowserWindow, Menu } from 'electron';
3 |
4 | const isDevelopment = process.env.NODE_ENV === 'development';
5 |
6 | let mainWindow = null;
7 | let forceQuit = false;
8 |
9 | const installExtensions = async () => {
10 | const installer = require('electron-devtools-installer');
11 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
12 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
13 | for (const name of extensions) {
14 | try {
15 | await installer.default(installer[name], forceDownload);
16 | } catch (e) {
17 | console.log(`Error installing ${name} extension: ${e.message}`);
18 | }
19 | }
20 | };
21 |
22 | crashReporter.start({
23 | productName: 'YourName',
24 | companyName: 'YourCompany',
25 | submitURL: 'https://your-domain.com/url-to-submit',
26 | uploadToServer: false,
27 | });
28 |
29 | app.on('window-all-closed', () => {
30 | // On OS X it is common for applications and their menu bar
31 | // to stay active until the user quits explicitly with Cmd + Q
32 | if (process.platform !== 'darwin') {
33 | app.quit();
34 | }
35 | });
36 |
37 | app.on('ready', async () => {
38 | if (isDevelopment) {
39 | await installExtensions();
40 | }
41 |
42 | mainWindow = new BrowserWindow({
43 | width: 1000,
44 | height: 800,
45 | minWidth: 640,
46 | minHeight: 480,
47 | show: false,
48 | webPreferences: {
49 | nodeIntegration: true,
50 | },
51 | });
52 |
53 | mainWindow.loadFile(path.resolve(path.join(__dirname, '../renderer/index.html')));
54 |
55 | // show window once on first load
56 | mainWindow.webContents.once('did-finish-load', () => {
57 | mainWindow.show();
58 | });
59 |
60 | mainWindow.webContents.on('did-finish-load', () => {
61 | // Handle window logic properly on macOS:
62 | // 1. App should not terminate if window has been closed
63 | // 2. Click on icon in dock should re-open the window
64 | // 3. ⌘+Q should close the window and quit the app
65 | if (process.platform === 'darwin') {
66 | mainWindow.on('close', function (e) {
67 | if (!forceQuit) {
68 | e.preventDefault();
69 | mainWindow.hide();
70 | }
71 | });
72 |
73 | app.on('activate', () => {
74 | mainWindow.show();
75 | });
76 |
77 | app.on('before-quit', () => {
78 | forceQuit = true;
79 | });
80 | } else {
81 | mainWindow.on('closed', () => {
82 | mainWindow = null;
83 | });
84 | }
85 | });
86 |
87 | if (isDevelopment) {
88 | // auto-open dev tools
89 | mainWindow.webContents.openDevTools();
90 |
91 | // add inspect element on right click menu
92 | mainWindow.webContents.on('context-menu', (e, props) => {
93 | Menu.buildFromTemplate([
94 | {
95 | label: 'Inspect element',
96 | click() {
97 | mainWindow.inspectElement(props.x, props.y);
98 | },
99 | },
100 | ]).popup(mainWindow);
101 | });
102 | }
103 | });
104 |
--------------------------------------------------------------------------------
/app/renderer/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | }
5 | }
--------------------------------------------------------------------------------
/app/renderer/actions/user.js:
--------------------------------------------------------------------------------
1 | import { createAction } from 'redux-actions';
2 |
3 | export default {
4 | login: createAction('USER_LOGIN'),
5 | logout: createAction('USER_LOGOUT'),
6 | };
7 |
--------------------------------------------------------------------------------
/app/renderer/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { ConnectedRouter } from 'connected-react-router';
5 | import { createMemoryHistory } from 'history';
6 | import routes from './routes';
7 | import configureStore from './store';
8 |
9 | const syncHistoryWithStore = (store, history) => {
10 | const { router } = store.getState();
11 | if (router && router.location) {
12 | history.replace(router.location);
13 | }
14 | };
15 |
16 | const initialState = {};
17 | const routerHistory = createMemoryHistory();
18 | const store = configureStore(initialState, routerHistory);
19 | syncHistoryWithStore(store, routerHistory);
20 |
21 | const rootElement = document.querySelector(document.currentScript.getAttribute('data-container'));
22 |
23 | ReactDOM.render(
24 |
25 | {routes}
26 | ,
27 | rootElement,
28 | );
29 |
--------------------------------------------------------------------------------
/app/renderer/components/LoggedIn.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class LoggedIn extends Component {
5 | static propTypes = {
6 | onLogout: PropTypes.func.isRequired,
7 | };
8 |
9 | handleLogout = () => {
10 | this.props.onLogout({
11 | username: '',
12 | loggedIn: false,
13 | });
14 | };
15 |
16 | render() {
17 | return (
18 |
19 |
Logged in as {this.props.user.username}
20 |
21 |
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/renderer/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class Login extends Component {
5 | static propTypes = {
6 | onLogin: PropTypes.func.isRequired,
7 | };
8 |
9 | state = {
10 | username: '',
11 | };
12 |
13 | handleLogin = () => {
14 | this.props.onLogin({
15 | username: this.state.username,
16 | loggedIn: true,
17 | });
18 | };
19 |
20 | handleChange = (e) => {
21 | this.setState({
22 | username: e.target.value,
23 | });
24 | };
25 |
26 | render() {
27 | return (
28 |
29 |
Login
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/renderer/containers/LoggedInPage.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { push } from 'connected-react-router';
3 | import { bindActionCreators } from 'redux';
4 | import LoggedIn from '../components/LoggedIn';
5 | import userActions from '../actions/user';
6 |
7 | const mapStateToProps = (state) => {
8 | return state;
9 | };
10 |
11 | const mapDispatchToProps = (dispatch) => {
12 | const user = bindActionCreators(userActions, dispatch);
13 | return {
14 | onLogout: (data) => {
15 | user.logout(data);
16 | dispatch(push('/'));
17 | },
18 | };
19 | };
20 |
21 | export default connect(mapStateToProps, mapDispatchToProps)(LoggedIn);
22 |
--------------------------------------------------------------------------------
/app/renderer/containers/LoginPage.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { push } from 'connected-react-router';
3 | import { bindActionCreators } from 'redux';
4 | import Login from '../components/Login';
5 | import userActions from '../actions/user';
6 |
7 | const mapStateToProps = (state) => {
8 | return state;
9 | };
10 |
11 | const mapDispatchToProps = (dispatch) => {
12 | const user = bindActionCreators(userActions, dispatch);
13 | return {
14 | onLogin: (data) => {
15 | user.login(data);
16 | dispatch(push('/loggedin'));
17 | },
18 | };
19 | };
20 |
21 | export default connect(mapStateToProps, mapDispatchToProps)(Login);
22 |
--------------------------------------------------------------------------------
/app/renderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | My App
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/renderer/reducers/user.js:
--------------------------------------------------------------------------------
1 | import { handleActions } from 'redux-actions';
2 | import actions from '../actions/user';
3 |
4 | export default handleActions(
5 | {
6 | [actions.login]: (state, action) => {
7 | return { ...state, ...action.payload };
8 | },
9 | [actions.logout]: (state, action) => {
10 | return { ...state, ...action.payload };
11 | },
12 | },
13 | {},
14 | );
15 |
--------------------------------------------------------------------------------
/app/renderer/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route } from 'react-router';
3 |
4 | import LoginPage from './containers/LoginPage';
5 | import LoggedInPage from './containers/LoggedInPage';
6 |
7 | export default (
8 |
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/app/renderer/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
2 | import { connectRouter, routerMiddleware, push } from 'connected-react-router';
3 | import persistState from 'redux-localstorage';
4 | import thunk from 'redux-thunk';
5 |
6 | import user from './reducers/user';
7 | import userActions from './actions/user';
8 |
9 | export default function configureStore(initialState, routerHistory) {
10 | const router = routerMiddleware(routerHistory);
11 |
12 | const actionCreators = {
13 | ...userActions,
14 | push,
15 | };
16 |
17 | const reducers = {
18 | router: connectRouter(routerHistory),
19 | user,
20 | };
21 |
22 | const middlewares = [thunk, router];
23 |
24 | const composeEnhancers = (() => {
25 | const compose_ = window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
26 | if (process.env.NODE_ENV === 'development' && compose_) {
27 | return compose_({ actionCreators });
28 | }
29 | return compose;
30 | })();
31 |
32 | const enhancer = composeEnhancers(applyMiddleware(...middlewares), persistState());
33 | const rootReducer = combineReducers(reducers);
34 |
35 | return createStore(rootReducer, initialState, enhancer);
36 | }
37 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | targets: {
7 | electron: '6.0',
8 | },
9 | },
10 | ],
11 | '@babel/preset-react',
12 | ],
13 | plugins: [
14 | ['@babel/plugin-proposal-decorators', { legacy: true }],
15 | ['@babel/plugin-proposal-class-properties', { loose: true }],
16 | ],
17 | };
18 |
--------------------------------------------------------------------------------
/dist-assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jschr/electron-react-redux-boilerplate/3af7a5ebcbcbe37de980ff95802600486558198a/dist-assets/.gitkeep
--------------------------------------------------------------------------------
/electron-builder.yml:
--------------------------------------------------------------------------------
1 | appId: com.example.app
2 | copyright: Example co
3 | productName: MyApp
4 |
5 | asar: true
6 |
7 | directories:
8 | buildResources: dist-assets/
9 | output: dist/
10 |
11 | files:
12 | - package.json
13 | - init.js
14 | - build/
15 | - node_modules/
16 |
17 | dmg:
18 | contents:
19 | - type: link
20 | path: /Applications
21 | x: 410
22 | y: 150
23 | - type: file
24 | x: 130
25 | y: 150
26 |
27 | mac:
28 | target: dmg
29 | category: public.app-category.tools
30 |
31 | win:
32 | target: nsis
33 |
34 | linux:
35 | target:
36 | - deb
37 | - AppImage
38 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const { task, series } = require('gulp');
2 | const rimraf = require('rimraf');
3 |
4 | const scripts = require('./tasks/scripts');
5 | const assets = require('./tasks/assets');
6 | const watch = require('./tasks/watch');
7 | const dist = require('./tasks/distribution');
8 |
9 | task('clean', function (done) {
10 | rimraf('./build', done);
11 | });
12 | task('build', series('clean', assets.copyHtml, scripts.build));
13 | task('develop', series('clean', watch.start));
14 | task('pack-win', series('build', dist.packWin));
15 | task('pack-linux', series('build', dist.packLinux));
16 | task('pack-mac', series('build', dist.packMac));
17 |
--------------------------------------------------------------------------------
/init.js:
--------------------------------------------------------------------------------
1 | require('./build/main');
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-react-redux-boilerplate",
3 | "version": "0.0.0",
4 | "description": "electron-react-redux-boilerplate",
5 | "main": "init.js",
6 | "author": {
7 | "name": "Jordan Schroter",
8 | "email": "email@author.com"
9 | },
10 | "repository": "https://github.com/jschr/electron-react-redux-boilerplate",
11 | "license": "MIT",
12 | "dependencies": {
13 | "@babel/register": "^7.9.0",
14 | "connected-react-router": "^6.8.0",
15 | "history": "^4.10.1",
16 | "prop-types": "^15.7.2",
17 | "react": "^16.13.1",
18 | "react-dom": "^16.13.0",
19 | "react-redux": "^7.2.0",
20 | "react-router": "^5.1.2",
21 | "redux": "^4.0.5",
22 | "redux-actions": "^2.6.5",
23 | "redux-localstorage": "^0.4.1",
24 | "redux-thunk": "^2.2.0"
25 | },
26 | "devDependencies": {
27 | "@babel/core": "^7.9.0",
28 | "@babel/plugin-proposal-class-properties": "^7.8.3",
29 | "@babel/plugin-proposal-decorators": "^7.8.3",
30 | "@babel/preset-env": "^7.9.5",
31 | "@babel/preset-react": "^7.9.4",
32 | "babel-eslint": "^10.1.0",
33 | "browser-sync": "^2.26.7",
34 | "chai": "^4.1.0",
35 | "electron": "^9.4.0",
36 | "electron-builder": "^22.4.1",
37 | "electron-devtools-installer": "^3.0.0",
38 | "electron-mocha": "^8.2.1",
39 | "eslint": "^6.8.0",
40 | "eslint-config-prettier": "^6.10.1",
41 | "eslint-plugin-react": "^7.19.0",
42 | "gulp": "^4.0.2",
43 | "gulp-babel": "^8.0.0",
44 | "gulp-inject-string": "^1.1.2",
45 | "gulp-sourcemaps": "^2.6.5",
46 | "prettier": "^2.0.4",
47 | "redux-mock-store": "^1.5.4",
48 | "rimraf": "^3.0.2"
49 | },
50 | "scripts": {
51 | "postinstall": "electron-builder install-app-deps",
52 | "develop": "gulp develop",
53 | "test": "electron-mocha --renderer -R spec --require @babel/register test/**/*.spec.js",
54 | "lint": "eslint --no-ignore tasks app test *.js",
55 | "format": "npm run private:format -- --write",
56 | "check-format": "npm run private:format -- --list-different",
57 | "pack:mac": "gulp pack-mac",
58 | "pack:win": "gulp pack-win",
59 | "pack:linux": "gulp pack-linux",
60 | "private:format": "prettier gulpfile.js babel.config.js \"tasks/*.js\" \"app/**/*.js\" \"test/**/*.js\""
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tasks/assets.js:
--------------------------------------------------------------------------------
1 | const { src, dest } = require('gulp');
2 |
3 | function copyHtml() {
4 | return src('app/renderer/index.html').pipe(dest('build/renderer'));
5 | }
6 |
7 | copyHtml.displayName = 'copy-html';
8 |
9 | exports.copyHtml = copyHtml;
10 |
--------------------------------------------------------------------------------
/tasks/distribution.js:
--------------------------------------------------------------------------------
1 | const builder = require('electron-builder');
2 |
3 | function packWin() {
4 | return builder.build({
5 | targets: builder.Platform.WINDOWS.createTarget(),
6 | });
7 | }
8 |
9 | function packMac() {
10 | return builder.build({
11 | targets: builder.Platform.MAC.createTarget(),
12 | });
13 | }
14 |
15 | function packLinux() {
16 | return builder.build({
17 | targets: builder.Platform.LINUX.createTarget(),
18 | });
19 | }
20 |
21 | packWin.displayName = 'builder-win';
22 | packMac.displayName = 'builder-mac';
23 | packLinux.displayName = 'builder-linux';
24 |
25 | exports.packWin = packWin;
26 | exports.packMac = packMac;
27 | exports.packLinux = packLinux;
28 |
--------------------------------------------------------------------------------
/tasks/electron.js:
--------------------------------------------------------------------------------
1 | const { spawn } = require('child_process');
2 | const electron = require('electron');
3 |
4 | let subprocess;
5 |
6 | function startElectron(done) {
7 | subprocess = spawn(electron, ['.', '--no-sandbox'], {
8 | env: { ...process.env, NODE_ENV: 'development' },
9 | stdio: 'inherit',
10 | });
11 | done();
12 | }
13 |
14 | function stopElectron() {
15 | subprocess.kill();
16 | return subprocess;
17 | }
18 |
19 | startElectron.displayName = 'start-electron';
20 | stopElectron.displayName = 'stop-electron';
21 |
22 | exports.start = startElectron;
23 | exports.stop = stopElectron;
24 |
--------------------------------------------------------------------------------
/tasks/hotreload.js:
--------------------------------------------------------------------------------
1 | const { series, src, dest } = require('gulp');
2 | const inject = require('gulp-inject-string');
3 |
4 | const browserSync = require('browser-sync').create();
5 |
6 | function startBrowserSync(done) {
7 | browserSync.init(
8 | {
9 | ui: false,
10 | localOnly: true,
11 | port: 35829,
12 | ghostMode: false,
13 | open: false,
14 | notify: false,
15 | logSnippet: false,
16 | },
17 | function (error) {
18 | done(error);
19 | },
20 | );
21 | }
22 |
23 | function injectBrowserSync() {
24 | return src('app/renderer/index.html')
25 | .pipe(inject.before('