├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── actions │ └── .gitkeep ├── api │ └── .gitkeep ├── app.global.css ├── app.html ├── app.icns ├── components │ ├── AppBar.css │ ├── AppBar.js │ ├── Home.css │ ├── Home.js │ ├── Main.css │ ├── Main.js │ ├── MainLeft.css │ ├── MainLeft.js │ ├── MainRight.css │ ├── MainRight.js │ ├── Nav.css │ ├── Nav.js │ ├── NavBottom.css │ ├── NavBottom.js │ ├── NavTop.css │ ├── NavTop.js │ ├── StatusBar.css │ └── StatusBar.js ├── containers │ ├── App.js │ └── HomePage.js ├── index.js ├── reducers │ └── index.js ├── routes.js ├── store │ ├── configureStore.development.js │ ├── configureStore.js │ └── configureStore.production.js └── utils │ └── .gitkeep ├── appveyor.yml ├── demo.gif ├── main.development.js ├── package.js ├── package.json ├── server.js ├── test ├── .eslintrc ├── e2e.js ├── example.js └── setup.js ├── webpack.config.base.js ├── webpack.config.development.js ├── webpack.config.electron.js ├── webpack.config.eslint.js ├── webpack.config.node.js └── webpack.config.production.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["add-module-exports"], 4 | "env": { 5 | "production": { 6 | "presets": ["react-optimize"], 7 | "plugins": [ 8 | "babel-plugin-dev-expression" 9 | ] 10 | }, 11 | "development": { 12 | "presets": ["react-hmre"] 13 | }, 14 | "test": { 15 | "plugins": [ 16 | ["webpack-loaders", { "config": "webpack.config.node.js", "verbose": false }] 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{json,js,jsx,html,css}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [.eslintrc] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | main.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "mocha": true, 7 | "node": true 8 | }, 9 | "rules": { 10 | "consistent-return": 0, 11 | "comma-dangle": 0, 12 | "no-use-before-define": 0, 13 | "import/no-unresolved": [2, { "ignore": ["electron"] }], 14 | "import/no-extraneous-dependencies": 0, 15 | "react/jsx-no-bind": 0, 16 | "react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx"] }], 17 | "react/prefer-stateless-function": 0, 18 | "arrow-body-style": "off", 19 | # https://github.com/eslint/eslint/issues/6274 20 | "generator-star-spacing": 0 21 | }, 22 | "plugins": [ 23 | "import", 24 | "react" 25 | ], 26 | "settings": { 27 | "import/resolver": { 28 | "webpack": { 29 | "config": "webpack.config.eslint.js" 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # OSX 30 | .DS_Store 31 | 32 | # App packaged 33 | dist 34 | release 35 | /main.js 36 | /main.js.map 37 | 38 | jsconfig.json 39 | typings/ 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 6 7 | - 5 8 | - 4 9 | 10 | cache: 11 | directories: 12 | - node_modules 13 | 14 | addons: 15 | apt: 16 | sources: 17 | - ubuntu-toolchain-r-test 18 | packages: 19 | - g++-4.8 20 | - icnsutils 21 | - graphicsmagick 22 | - xz-utils 23 | - xorriso 24 | 25 | install: 26 | - export CXX="g++-4.8" 27 | - npm install 28 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 29 | 30 | before_script: 31 | - export DISPLAY=:99.0 32 | - sh -e /etc/init.d/xvfb start & 33 | - sleep 3 34 | 35 | script: 36 | - npm run lint 37 | - npm run test 38 | - npm run build 39 | - npm run test-e2e 40 | - npm run package 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present C. T. Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Common Electron App Layout (with React) 2 | 3 | *Note: this repo is meant to show guidance on how to structure the UI in an Electron/React app with a common approach. Use it as a guide to quickly get a UI together for you to modify to your app* 4 | 5 | ## What does it look like? 6 | 7 | ![demo](demo.gif) 8 | 9 | ## What should you do with this? 10 | 11 | Make it your own! :boom: Use this as a guide to structure the UI for you Electron/React app. 12 | 13 | ## Credit 14 | 15 | This app was created from a clone of the amazing [electron-react-boilerplate](https://github.com/chentsulin/electron-react-boilerplate) repo. So many thanks to [chentsulin](https://github.com/chentsulin) for creating and maintaining this wonderful boilerplate! 16 | -------------------------------------------------------------------------------- /app/actions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trstringer/electron-basic-ui-layout/7b813c71213bb094566456304a54337bca155722/app/actions/.gitkeep -------------------------------------------------------------------------------- /app/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trstringer/electron-basic-ui-layout/7b813c71213bb094566456304a54337bca155722/app/api/.gitkeep -------------------------------------------------------------------------------- /app/app.global.css: -------------------------------------------------------------------------------- 1 | body { 2 | position: relative; 3 | color: white; 4 | background-color: #232C39; 5 | background-image: linear-gradient(45deg, rgba(0, 216, 255, .5) 10%, rgba(0, 1, 127, .7)); 6 | font-family: Arial, Helvetica, Helvetica Neue; 7 | overflow-y: hidden; 8 | margin: 0px 0px 0px 0px; 9 | } 10 | 11 | h2 { 12 | margin: 0; 13 | font-size: 2.25rem; 14 | font-weight: bold; 15 | letter-spacing: -.025em; 16 | color: #fff; 17 | } 18 | 19 | p { 20 | font-size: 24px; 21 | } 22 | 23 | li { 24 | list-style: none; 25 | } 26 | 27 | a { 28 | color: white; 29 | opacity: .75; 30 | text-decoration: none; 31 | } 32 | 33 | a:hover { 34 | opacity: 1; 35 | text-decoration: none; 36 | cursor: pointer; 37 | } 38 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Electron React! 6 | 7 | 15 | 16 | 17 |
18 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trstringer/electron-basic-ui-layout/7b813c71213bb094566456304a54337bca155722/app/app.icns -------------------------------------------------------------------------------- /app/components/AppBar.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: inherit; 3 | background-color: #EF5350; 4 | height: 30px; 5 | } 6 | -------------------------------------------------------------------------------- /app/components/AppBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import style from './AppBar.css'; 3 | 4 | const AppBar = () => { 5 | return ( 6 |
7 | App Bar 8 |
9 | ); 10 | }; 11 | 12 | export default AppBar; 13 | -------------------------------------------------------------------------------- /app/components/Home.css: -------------------------------------------------------------------------------- 1 | .container h2 { 2 | font-size: 5rem; 3 | } 4 | 5 | .container a { 6 | font-size: 1.4rem; 7 | } 8 | -------------------------------------------------------------------------------- /app/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import styles from './Home.css'; 3 | import Nav from './Nav'; 4 | import Main from './Main'; 5 | 6 | 7 | export default class Home extends Component { 8 | render() { 9 | return ( 10 |
11 |
12 |
15 |
16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/components/Main.css: -------------------------------------------------------------------------------- 1 | .container { 2 | background-color: blue; 3 | margin-left: 200px; 4 | height: 100vh; 5 | } 6 | -------------------------------------------------------------------------------- /app/components/Main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import styles from './Main.css'; 3 | import MainLeft from './MainLeft'; 4 | import MainRight from './MainRight'; 5 | import StatusBar from './StatusBar'; 6 | import AppBar from './AppBar'; 7 | 8 | export default class Main extends Component { 9 | render() { 10 | return ( 11 |
12 |
13 | 14 | 15 | 16 | 17 |
18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/components/MainLeft.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100vh; 3 | width: 50%; 4 | background-color: #EF9A9A; 5 | float: left; 6 | } 7 | -------------------------------------------------------------------------------- /app/components/MainLeft.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import style from './MainLeft.css'; 3 | 4 | const MainLeft = () => { 5 | return ( 6 |
7 | Main (Left) 8 |
9 | ); 10 | }; 11 | 12 | export default MainLeft; 13 | -------------------------------------------------------------------------------- /app/components/MainRight.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 50%; 3 | height: 100vh; 4 | margin-left: 50%; 5 | background-color: #E57373; 6 | } 7 | -------------------------------------------------------------------------------- /app/components/MainRight.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import style from './MainRight.css'; 3 | 4 | const MainRight = () => { 5 | return ( 6 |
7 | Main (Right) 8 |
9 | ); 10 | }; 11 | 12 | export default MainRight; 13 | -------------------------------------------------------------------------------- /app/components/Nav.css: -------------------------------------------------------------------------------- 1 | .navigation { 2 | background-color: green; 3 | position: fixed; 4 | width: 200px; 5 | height: 100vh; 6 | } 7 | -------------------------------------------------------------------------------- /app/components/Nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Nav.css'; 3 | import NavTop from './NavTop'; 4 | import NavBottom from './NavBottom'; 5 | 6 | const Nav = () => { 7 | return ( 8 |
9 | 10 | 11 |
12 | ); 13 | }; 14 | 15 | export default Nav; 16 | -------------------------------------------------------------------------------- /app/components/NavBottom.css: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: 50vh; 3 | background-color: #EF5350; 4 | width: inherit; 5 | height: 50vh; 6 | } 7 | -------------------------------------------------------------------------------- /app/components/NavBottom.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import style from './NavBottom.css'; 3 | 4 | const NavBottom = () => { 5 | return ( 6 |
7 | Navigation (Bottom) 8 |
9 | ); 10 | }; 11 | 12 | export default NavBottom; 13 | -------------------------------------------------------------------------------- /app/components/NavTop.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: fixed; 3 | height: 50vh; 4 | background-color: #B71C1C; 5 | width: inherit; 6 | } 7 | -------------------------------------------------------------------------------- /app/components/NavTop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import style from './NavTop.css'; 3 | 4 | const NavTop = () => { 5 | return ( 6 |
7 | Navigation (Top) 8 |
9 | ); 10 | }; 11 | 12 | export default NavTop; 13 | -------------------------------------------------------------------------------- /app/components/StatusBar.css: -------------------------------------------------------------------------------- 1 | .container { 2 | background-color: #C62828; 3 | width: 100%; 4 | position: absolute; 5 | bottom: 0; 6 | } 7 | -------------------------------------------------------------------------------- /app/components/StatusBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import style from './StatusBar.css'; 3 | 4 | const StatusBar = () => { 5 | return ( 6 |
7 | Status Bar 8 |
9 | ); 10 | }; 11 | 12 | export default StatusBar; 13 | -------------------------------------------------------------------------------- /app/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | export default class App extends Component { 4 | static propTypes = { 5 | children: PropTypes.element.isRequired 6 | }; 7 | 8 | render() { 9 | return ( 10 |
11 | {this.props.children} 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/containers/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Home from '../components/Home'; 3 | 4 | export default class HomePage extends Component { 5 | render() { 6 | return ( 7 | 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { Router, hashHistory } from 'react-router'; 5 | import { syncHistoryWithStore } from 'react-router-redux'; 6 | import routes from './routes'; 7 | import configureStore from './store/configureStore'; 8 | import './app.global.css'; 9 | 10 | const store = configureStore(); 11 | const history = syncHistoryWithStore(hashHistory, store); 12 | 13 | render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer as routing } from 'react-router-redux'; 3 | 4 | const rootReducer = combineReducers({ 5 | routing 6 | }); 7 | 8 | export default rootReducer; 9 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import App from './containers/App'; 4 | import HomePage from './containers/HomePage'; 5 | 6 | 7 | export default ( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /app/store/configureStore.development.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import createLogger from 'redux-logger'; 4 | import { hashHistory } from 'react-router'; 5 | import { routerMiddleware, push } from 'react-router-redux'; 6 | import rootReducer from '../reducers'; 7 | 8 | const actionCreators = { 9 | push 10 | }; 11 | 12 | const logger = createLogger({ 13 | level: 'info', 14 | collapsed: true, 15 | }); 16 | 17 | const router = routerMiddleware(hashHistory); 18 | 19 | const enhancer = compose( 20 | applyMiddleware(thunk, router, logger), 21 | window.devToolsExtension ? 22 | window.devToolsExtension({ actionCreators }) : 23 | noop => noop 24 | ); 25 | 26 | export default function configureStore(initialState) { 27 | const store = createStore(rootReducer, initialState, enhancer); 28 | 29 | if (window.devToolsExtension) { 30 | window.devToolsExtension.updateStore(store); 31 | } 32 | 33 | if (module.hot) { 34 | module.hot.accept('../reducers', () => 35 | store.replaceReducer(require('../reducers')) // eslint-disable-line global-require 36 | ); 37 | } 38 | 39 | return store; 40 | } 41 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.production'); // eslint-disable-line global-require 3 | } else { 4 | module.exports = require('./configureStore.development'); // eslint-disable-line global-require 5 | } 6 | -------------------------------------------------------------------------------- /app/store/configureStore.production.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { hashHistory } from 'react-router'; 4 | import { routerMiddleware } from 'react-router-redux'; 5 | import rootReducer from '../reducers'; 6 | 7 | const router = routerMiddleware(hashHistory); 8 | 9 | const enhancer = applyMiddleware(thunk, router); 10 | 11 | export default function configureStore(initialState) { 12 | return createStore(rootReducer, initialState, enhancer); 13 | } 14 | -------------------------------------------------------------------------------- /app/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trstringer/electron-basic-ui-layout/7b813c71213bb094566456304a54337bca155722/app/utils/.gitkeep -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: unstable 2 | cache: 3 | - node_modules 4 | environment: 5 | matrix: 6 | - nodejs_version: 6 7 | - nodejs_version: 5 8 | - nodejs_version: 4 9 | install: 10 | - ps: Install-Product node $env:nodejs_version 11 | - set CI=true 12 | - npm install -g npm@latest 13 | - set PATH=%APPDATA%\npm;%PATH% 14 | - npm install 15 | matrix: 16 | fast_finish: true 17 | build: off 18 | version: '{build}' 19 | shallow_clone: true 20 | clone_depth: 1 21 | test_script: 22 | - node --version 23 | - npm --version 24 | - npm run lint 25 | - npm run test 26 | - npm run build 27 | - npm run test-e2e 28 | - npm run package 29 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trstringer/electron-basic-ui-layout/7b813c71213bb094566456304a54337bca155722/demo.gif -------------------------------------------------------------------------------- /main.development.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, Menu, shell } from 'electron'; 2 | 3 | let menu; 4 | let template; 5 | let mainWindow = null; 6 | 7 | 8 | if (process.env.NODE_ENV === 'development') { 9 | require('electron-debug')(); // eslint-disable-line global-require 10 | } 11 | 12 | 13 | app.on('window-all-closed', () => { 14 | if (process.platform !== 'darwin') app.quit(); 15 | }); 16 | 17 | 18 | const installExtensions = async () => { 19 | if (process.env.NODE_ENV === 'development') { 20 | const installer = require('electron-devtools-installer'); // eslint-disable-line global-require 21 | 22 | const extensions = [ 23 | 'REACT_DEVELOPER_TOOLS', 24 | 'REDUX_DEVTOOLS' 25 | ]; 26 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 27 | for (const name of extensions) { 28 | try { 29 | await installer.default(installer[name], forceDownload); 30 | } catch (e) {} // eslint-disable-line 31 | } 32 | } 33 | }; 34 | 35 | app.on('ready', async () => { 36 | await installExtensions(); 37 | 38 | mainWindow = new BrowserWindow({ 39 | show: false, 40 | width: 1024, 41 | height: 728 42 | }); 43 | 44 | mainWindow.loadURL(`file://${__dirname}/app/app.html`); 45 | 46 | mainWindow.webContents.on('did-finish-load', () => { 47 | mainWindow.show(); 48 | mainWindow.focus(); 49 | }); 50 | 51 | mainWindow.on('closed', () => { 52 | mainWindow = null; 53 | }); 54 | 55 | if (process.env.NODE_ENV === 'development') { 56 | mainWindow.openDevTools(); 57 | mainWindow.webContents.on('context-menu', (e, props) => { 58 | const { x, y } = props; 59 | 60 | Menu.buildFromTemplate([{ 61 | label: 'Inspect element', 62 | click() { 63 | mainWindow.inspectElement(x, y); 64 | } 65 | }]).popup(mainWindow); 66 | }); 67 | } 68 | 69 | if (process.platform === 'darwin') { 70 | template = [{ 71 | label: 'Electron', 72 | submenu: [{ 73 | label: 'About ElectronReact', 74 | selector: 'orderFrontStandardAboutPanel:' 75 | }, { 76 | type: 'separator' 77 | }, { 78 | label: 'Services', 79 | submenu: [] 80 | }, { 81 | type: 'separator' 82 | }, { 83 | label: 'Hide ElectronReact', 84 | accelerator: 'Command+H', 85 | selector: 'hide:' 86 | }, { 87 | label: 'Hide Others', 88 | accelerator: 'Command+Shift+H', 89 | selector: 'hideOtherApplications:' 90 | }, { 91 | label: 'Show All', 92 | selector: 'unhideAllApplications:' 93 | }, { 94 | type: 'separator' 95 | }, { 96 | label: 'Quit', 97 | accelerator: 'Command+Q', 98 | click() { 99 | app.quit(); 100 | } 101 | }] 102 | }, { 103 | label: 'Edit', 104 | submenu: [{ 105 | label: 'Undo', 106 | accelerator: 'Command+Z', 107 | selector: 'undo:' 108 | }, { 109 | label: 'Redo', 110 | accelerator: 'Shift+Command+Z', 111 | selector: 'redo:' 112 | }, { 113 | type: 'separator' 114 | }, { 115 | label: 'Cut', 116 | accelerator: 'Command+X', 117 | selector: 'cut:' 118 | }, { 119 | label: 'Copy', 120 | accelerator: 'Command+C', 121 | selector: 'copy:' 122 | }, { 123 | label: 'Paste', 124 | accelerator: 'Command+V', 125 | selector: 'paste:' 126 | }, { 127 | label: 'Select All', 128 | accelerator: 'Command+A', 129 | selector: 'selectAll:' 130 | }] 131 | }, { 132 | label: 'View', 133 | submenu: (process.env.NODE_ENV === 'development') ? [{ 134 | label: 'Reload', 135 | accelerator: 'Command+R', 136 | click() { 137 | mainWindow.webContents.reload(); 138 | } 139 | }, { 140 | label: 'Toggle Full Screen', 141 | accelerator: 'Ctrl+Command+F', 142 | click() { 143 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 144 | } 145 | }, { 146 | label: 'Toggle Developer Tools', 147 | accelerator: 'Alt+Command+I', 148 | click() { 149 | mainWindow.toggleDevTools(); 150 | } 151 | }] : [{ 152 | label: 'Toggle Full Screen', 153 | accelerator: 'Ctrl+Command+F', 154 | click() { 155 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 156 | } 157 | }] 158 | }, { 159 | label: 'Window', 160 | submenu: [{ 161 | label: 'Minimize', 162 | accelerator: 'Command+M', 163 | selector: 'performMiniaturize:' 164 | }, { 165 | label: 'Close', 166 | accelerator: 'Command+W', 167 | selector: 'performClose:' 168 | }, { 169 | type: 'separator' 170 | }, { 171 | label: 'Bring All to Front', 172 | selector: 'arrangeInFront:' 173 | }] 174 | }, { 175 | label: 'Help', 176 | submenu: [{ 177 | label: 'Learn More', 178 | click() { 179 | shell.openExternal('http://electron.atom.io'); 180 | } 181 | }, { 182 | label: 'Documentation', 183 | click() { 184 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 185 | } 186 | }, { 187 | label: 'Community Discussions', 188 | click() { 189 | shell.openExternal('https://discuss.atom.io/c/electron'); 190 | } 191 | }, { 192 | label: 'Search Issues', 193 | click() { 194 | shell.openExternal('https://github.com/atom/electron/issues'); 195 | } 196 | }] 197 | }]; 198 | 199 | menu = Menu.buildFromTemplate(template); 200 | Menu.setApplicationMenu(menu); 201 | } else { 202 | template = [{ 203 | label: '&File', 204 | submenu: [{ 205 | label: '&Open', 206 | accelerator: 'Ctrl+O' 207 | }, { 208 | label: '&Close', 209 | accelerator: 'Ctrl+W', 210 | click() { 211 | mainWindow.close(); 212 | } 213 | }] 214 | }, { 215 | label: '&View', 216 | submenu: (process.env.NODE_ENV === 'development') ? [{ 217 | label: '&Reload', 218 | accelerator: 'Ctrl+R', 219 | click() { 220 | mainWindow.webContents.reload(); 221 | } 222 | }, { 223 | label: 'Toggle &Full Screen', 224 | accelerator: 'F11', 225 | click() { 226 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 227 | } 228 | }, { 229 | label: 'Toggle &Developer Tools', 230 | accelerator: 'Alt+Ctrl+I', 231 | click() { 232 | mainWindow.toggleDevTools(); 233 | } 234 | }] : [{ 235 | label: 'Toggle &Full Screen', 236 | accelerator: 'F11', 237 | click() { 238 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 239 | } 240 | }] 241 | }, { 242 | label: 'Help', 243 | submenu: [{ 244 | label: 'Learn More', 245 | click() { 246 | shell.openExternal('http://electron.atom.io'); 247 | } 248 | }, { 249 | label: 'Documentation', 250 | click() { 251 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 252 | } 253 | }, { 254 | label: 'Community Discussions', 255 | click() { 256 | shell.openExternal('https://discuss.atom.io/c/electron'); 257 | } 258 | }, { 259 | label: 'Search Issues', 260 | click() { 261 | shell.openExternal('https://github.com/atom/electron/issues'); 262 | } 263 | }] 264 | }]; 265 | menu = Menu.buildFromTemplate(template); 266 | mainWindow.setMenu(menu); 267 | } 268 | }); 269 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | /* eslint strict: 0, no-shadow: 0, no-unused-vars: 0, no-console: 0 */ 2 | 'use strict'; 3 | 4 | require('babel-polyfill'); 5 | const os = require('os'); 6 | const webpack = require('webpack'); 7 | const electronCfg = require('./webpack.config.electron'); 8 | const cfg = require('./webpack.config.production'); 9 | const packager = require('electron-packager'); 10 | const del = require('del'); 11 | const exec = require('child_process').exec; 12 | const argv = require('minimist')(process.argv.slice(2)); 13 | const pkg = require('./package.json'); 14 | 15 | const deps = Object.keys(pkg.dependencies); 16 | const devDeps = Object.keys(pkg.devDependencies); 17 | 18 | const appName = argv.name || argv.n || pkg.productName; 19 | const shouldUseAsar = argv.asar || argv.a || false; 20 | const shouldBuildAll = argv.all || false; 21 | 22 | 23 | const DEFAULT_OPTS = { 24 | dir: './', 25 | name: appName, 26 | asar: shouldUseAsar, 27 | ignore: [ 28 | '^/test($|/)', 29 | '^/release($|/)', 30 | '^/main.development.js' 31 | ].concat(devDeps.map(name => `/node_modules/${name}($|/)`)) 32 | .concat( 33 | deps.filter(name => !electronCfg.externals.includes(name)) 34 | .map(name => `/node_modules/${name}($|/)`) 35 | ) 36 | }; 37 | 38 | const icon = argv.icon || argv.i || 'app/app'; 39 | 40 | if (icon) { 41 | DEFAULT_OPTS.icon = icon; 42 | } 43 | 44 | const version = argv.version || argv.v; 45 | 46 | if (version) { 47 | DEFAULT_OPTS.version = version; 48 | startPack(); 49 | } else { 50 | // use the same version as the currently-installed electron-prebuilt 51 | exec('npm list electron --dev', (err, stdout) => { 52 | if (err) { 53 | DEFAULT_OPTS.version = '1.2.0'; 54 | } else { 55 | DEFAULT_OPTS.version = stdout.split('electron@')[1].replace(/\s/g, ''); 56 | } 57 | 58 | startPack(); 59 | }); 60 | } 61 | 62 | 63 | function build(cfg) { 64 | return new Promise((resolve, reject) => { 65 | webpack(cfg, (err, stats) => { 66 | if (err) return reject(err); 67 | resolve(stats); 68 | }); 69 | }); 70 | } 71 | 72 | async function startPack() { 73 | console.log('start pack...'); 74 | 75 | try { 76 | await build(electronCfg); 77 | await build(cfg); 78 | const paths = await del('release'); 79 | 80 | if (shouldBuildAll) { 81 | // build for all platforms 82 | const archs = ['ia32', 'x64']; 83 | const platforms = ['linux', 'win32', 'darwin']; 84 | 85 | platforms.forEach((plat) => { 86 | archs.forEach((arch) => { 87 | pack(plat, arch, log(plat, arch)); 88 | }); 89 | }); 90 | } else { 91 | // build for current platform only 92 | pack(os.platform(), os.arch(), log(os.platform(), os.arch())); 93 | } 94 | } catch (error) { 95 | console.error(error); 96 | } 97 | } 98 | 99 | function pack(plat, arch, cb) { 100 | // there is no darwin ia32 electron 101 | if (plat === 'darwin' && arch === 'ia32') return; 102 | 103 | const iconObj = { 104 | icon: DEFAULT_OPTS.icon + (() => { 105 | let extension = '.png'; 106 | if (plat === 'darwin') { 107 | extension = '.icns'; 108 | } else if (plat === 'win32') { 109 | extension = '.ico'; 110 | } 111 | return extension; 112 | })() 113 | }; 114 | 115 | const opts = Object.assign({}, DEFAULT_OPTS, iconObj, { 116 | platform: plat, 117 | arch, 118 | prune: true, 119 | 'app-version': pkg.version || DEFAULT_OPTS.version, 120 | out: `release/${plat}-${arch}` 121 | }); 122 | 123 | packager(opts, cb); 124 | } 125 | 126 | 127 | function log(plat, arch) { 128 | return (err, filepath) => { 129 | if (err) return console.error(err); 130 | console.log(`${plat}-${arch} finished!`); 131 | }; 132 | } 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "productName": "ElectronReact", 4 | "version": "0.10.0", 5 | "description": "Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development", 6 | "main": "main.js", 7 | "scripts": { 8 | "test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha --compilers js:babel-register --recursive --require ./test/setup.js test/**/*.spec.js", 9 | "test-watch": "npm test -- --watch", 10 | "test-e2e": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha --compilers js:babel-register --require ./test/setup.js ./test/e2e.js", 11 | "lint": "eslint app test *.js", 12 | "hot-server": "node -r babel-register server.js", 13 | "build-main": "cross-env NODE_ENV=production node -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.electron.js --progress --profile --colors", 14 | "build-renderer": "cross-env NODE_ENV=production node -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.production.js --progress --profile --colors", 15 | "build": "npm run build-main && npm run build-renderer", 16 | "start": "cross-env NODE_ENV=production electron ./", 17 | "start-hot": "cross-env HOT=1 NODE_ENV=development electron -r babel-register -r babel-polyfill ./main.development", 18 | "package": "cross-env NODE_ENV=production node -r babel-register -r babel-polyfill package.js", 19 | "package-all": "npm run package -- --all", 20 | "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json", 21 | "dev": "concurrently --kill-others \"npm run hot-server\" \"npm run start-hot\"" 22 | }, 23 | "bin": { 24 | "electron": "./node_modules/.bin/electron" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/chentsulin/electron-react-boilerplate.git" 29 | }, 30 | "author": { 31 | "name": "C. T. Lin", 32 | "email": "chentsulin@gmail.com", 33 | "url": "https://github.com/chentsulin" 34 | }, 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues" 38 | }, 39 | "keywords": [ 40 | "electron", 41 | "boilerplate", 42 | "react", 43 | "react-router", 44 | "flux", 45 | "webpack", 46 | "react-hot" 47 | ], 48 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme", 49 | "devDependencies": { 50 | "asar": "^0.12.2", 51 | "babel-core": "^6.14.0", 52 | "babel-eslint": "^6.1.2", 53 | "babel-loader": "^6.2.5", 54 | "babel-plugin-add-module-exports": "^0.2.1", 55 | "babel-plugin-dev-expression": "^0.2.1", 56 | "babel-plugin-webpack-loaders": "^0.7.1", 57 | "babel-polyfill": "^6.13.0", 58 | "babel-preset-es2015": "^6.14.0", 59 | "babel-preset-react": "^6.11.1", 60 | "babel-preset-react-hmre": "^1.1.1", 61 | "babel-preset-react-optimize": "^1.0.1", 62 | "babel-preset-stage-0": "^6.5.0", 63 | "babel-register": "^6.14.0", 64 | "chai": "^3.5.0", 65 | "concurrently": "^2.2.0", 66 | "cross-env": "^2.0.0", 67 | "css-loader": "^0.24.0", 68 | "del": "^2.2.2", 69 | "devtron": "^1.3.0", 70 | "electron": "^1.3.4", 71 | "electron-devtools-installer": "^2.0.1", 72 | "electron-packager": "^7.7.0", 73 | "electron-rebuild": "^1.2.0", 74 | "eslint": "^3.3.1", 75 | "eslint-config-airbnb": "^10.0.1", 76 | "eslint-import-resolver-webpack": "^0.5.1", 77 | "eslint-plugin-import": "^1.14.0", 78 | "eslint-plugin-jsx-a11y": "^2.1.0", 79 | "eslint-plugin-react": "^6.1.2", 80 | "express": "^4.14.0", 81 | "extract-text-webpack-plugin": "^1.0.1", 82 | "fbjs-scripts": "^0.7.1", 83 | "jsdom": "^9.4.2", 84 | "json-loader": "^0.5.4", 85 | "minimist": "^1.2.0", 86 | "mocha": "^3.0.2", 87 | "node-libs-browser": "^1.0.0", 88 | "react-addons-test-utils": "^15.3.1", 89 | "redux-logger": "^2.6.1", 90 | "sinon": "^1.17.5", 91 | "spectron": "^3.3.0", 92 | "style-loader": "^0.13.1", 93 | "webpack": "^1.13.2", 94 | "webpack-dev-middleware": "^1.6.1", 95 | "webpack-hot-middleware": "^2.12.2", 96 | "webpack-merge": "^0.14.1" 97 | }, 98 | "dependencies": { 99 | "css-modules-require-hook": "^4.0.2", 100 | "electron-debug": "^1.0.1", 101 | "font-awesome": "^4.6.3", 102 | "postcss": "^5.1.2", 103 | "react": "^15.3.1", 104 | "react-dom": "^15.3.1", 105 | "react-redux": "^4.4.5", 106 | "react-router": "^2.7.0", 107 | "react-router-redux": "^4.0.5", 108 | "redux": "^3.5.2", 109 | "redux-thunk": "^2.1.0", 110 | "source-map-support": "^0.4.2" 111 | }, 112 | "devEngines": { 113 | "node": "4.x || 5.x || 6.x", 114 | "npm": "2.x || 3.x" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | 3 | import express from 'express'; 4 | import webpack from 'webpack'; 5 | import webpackDevMiddleware from 'webpack-dev-middleware'; 6 | import webpackHotMiddleware from 'webpack-hot-middleware'; 7 | 8 | import config from './webpack.config.development'; 9 | 10 | const app = express(); 11 | const compiler = webpack(config); 12 | const PORT = process.env.PORT || 3000; 13 | 14 | const wdm = webpackDevMiddleware(compiler, { 15 | publicPath: config.output.publicPath, 16 | stats: { 17 | colors: true 18 | } 19 | }); 20 | 21 | app.use(wdm); 22 | 23 | app.use(webpackHotMiddleware(compiler)); 24 | 25 | const server = app.listen(PORT, 'localhost', err => { 26 | if (err) { 27 | console.error(err); 28 | return; 29 | } 30 | 31 | console.log(`Listening at http://localhost:${PORT}`); 32 | }); 33 | 34 | process.on('SIGTERM', () => { 35 | console.log('Stopping dev server'); 36 | wdm.close(); 37 | server.close(() => { 38 | process.exit(0); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "no-unused-expressions": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/e2e.js: -------------------------------------------------------------------------------- 1 | import { Application } from 'spectron'; 2 | import { expect } from 'chai'; 3 | import electronPath from 'electron'; 4 | 5 | const delay = time => new Promise(resolve => setTimeout(resolve, time)); 6 | 7 | describe('main window', function spec() { 8 | this.timeout(5000); 9 | 10 | before(async () => { 11 | this.app = new Application({ 12 | path: electronPath, 13 | args: ['.'], 14 | }); 15 | return this.app.start(); 16 | }); 17 | 18 | after(() => { 19 | if (this.app && this.app.isRunning()) { 20 | return this.app.stop(); 21 | } 22 | }); 23 | 24 | it('should open window', async () => { 25 | const { client, browserWindow } = this.app; 26 | 27 | await client.waitUntilWindowLoaded(); 28 | await delay(500); 29 | const title = await browserWindow.getTitle(); 30 | expect(title).to.equal('Hello Electron React!'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | /* eslint func-names: 0 */ 2 | import { expect } from 'chai'; 3 | 4 | 5 | describe('description', () => { 6 | it('description', () => { 7 | expect(1 + 2).to.equal(3); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import { jsdom } from 'jsdom'; 3 | 4 | global.document = jsdom(''); 5 | global.window = document.defaultView; 6 | global.navigator = global.window.navigator; 7 | window.localStorage = window.sessionStorage = { 8 | getItem(key) { 9 | return this[key]; 10 | }, 11 | setItem(key, value) { 12 | this[key] = value; 13 | }, 14 | removeItem(key) { 15 | this[key] = undefined; 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export default { 4 | module: { 5 | loaders: [{ 6 | test: /\.jsx?$/, 7 | loaders: ['babel-loader'], 8 | exclude: /node_modules/ 9 | }, { 10 | test: /\.json$/, 11 | loader: 'json-loader' 12 | }] 13 | }, 14 | output: { 15 | path: path.join(__dirname, 'dist'), 16 | filename: 'bundle.js', 17 | libraryTarget: 'commonjs2' 18 | }, 19 | resolve: { 20 | extensions: ['', '.js', '.jsx', '.json'], 21 | packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'] 22 | }, 23 | plugins: [ 24 | 25 | ], 26 | externals: [ 27 | // put your node 3rd party libraries which can't be built with webpack here 28 | // (mysql, mongodb, and so on..) 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | /* eslint max-len: 0 */ 2 | import webpack from 'webpack'; 3 | import merge from 'webpack-merge'; 4 | import baseConfig from './webpack.config.base'; 5 | 6 | const port = process.env.PORT || 3000; 7 | 8 | export default merge(baseConfig, { 9 | debug: true, 10 | 11 | devtool: 'cheap-module-eval-source-map', 12 | 13 | entry: [ 14 | `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`, 15 | './app/index' 16 | ], 17 | 18 | output: { 19 | publicPath: `http://localhost:${port}/dist/` 20 | }, 21 | 22 | module: { 23 | loaders: [ 24 | { 25 | test: /\.global\.css$/, 26 | loaders: [ 27 | 'style-loader', 28 | 'css-loader?sourceMap' 29 | ] 30 | }, 31 | 32 | { 33 | test: /^((?!\.global).)*\.css$/, 34 | loaders: [ 35 | 'style-loader', 36 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 37 | ] 38 | } 39 | ] 40 | }, 41 | 42 | plugins: [ 43 | new webpack.HotModuleReplacementPlugin(), 44 | new webpack.NoErrorsPlugin(), 45 | new webpack.DefinePlugin({ 46 | 'process.env.NODE_ENV': JSON.stringify('development') 47 | }) 48 | ], 49 | 50 | target: 'electron-renderer' 51 | }); 52 | -------------------------------------------------------------------------------- /webpack.config.electron.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import merge from 'webpack-merge'; 3 | import baseConfig from './webpack.config.base'; 4 | 5 | export default merge(baseConfig, { 6 | devtool: 'source-map', 7 | 8 | entry: ['babel-polyfill', './main.development'], 9 | 10 | output: { 11 | path: __dirname, 12 | filename: './main.js' 13 | }, 14 | 15 | plugins: [ 16 | new webpack.optimize.UglifyJsPlugin({ 17 | compressor: { 18 | warnings: false 19 | } 20 | }), 21 | new webpack.BannerPlugin( 22 | 'require("source-map-support").install();', 23 | { raw: true, entryOnly: false } 24 | ), 25 | new webpack.DefinePlugin({ 26 | 'process.env': { 27 | NODE_ENV: JSON.stringify('production') 28 | } 29 | }) 30 | ], 31 | 32 | target: 'electron-main', 33 | 34 | node: { 35 | __dirname: false, 36 | __filename: false 37 | }, 38 | 39 | externals: [ 40 | 'font-awesome', 41 | 'source-map-support' 42 | ] 43 | }); 44 | -------------------------------------------------------------------------------- /webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | 3 | module.exports = require('./webpack.config.development'); 4 | -------------------------------------------------------------------------------- /webpack.config.node.js: -------------------------------------------------------------------------------- 1 | // for babel-plugin-webpack-loaders 2 | require('babel-register'); 3 | const devConfigs = require('./webpack.config.development'); 4 | 5 | module.exports = { 6 | output: { 7 | libraryTarget: 'commonjs2' 8 | }, 9 | module: { 10 | loaders: devConfigs.module.loaders.slice(1) // remove babel-loader 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 3 | import merge from 'webpack-merge'; 4 | import baseConfig from './webpack.config.base'; 5 | 6 | const config = merge(baseConfig, { 7 | devtool: 'cheap-module-source-map', 8 | 9 | entry: './app/index', 10 | 11 | output: { 12 | publicPath: '../dist/' 13 | }, 14 | 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.global\.css$/, 19 | loader: ExtractTextPlugin.extract( 20 | 'style-loader', 21 | 'css-loader' 22 | ) 23 | }, 24 | 25 | { 26 | test: /^((?!\.global).)*\.css$/, 27 | loader: ExtractTextPlugin.extract( 28 | 'style-loader', 29 | 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 30 | ) 31 | } 32 | ] 33 | }, 34 | 35 | plugins: [ 36 | new webpack.optimize.OccurrenceOrderPlugin(), 37 | new webpack.DefinePlugin({ 38 | 'process.env.NODE_ENV': JSON.stringify('production') 39 | }), 40 | new webpack.optimize.UglifyJsPlugin({ 41 | compressor: { 42 | screw_ie8: true, 43 | warnings: false 44 | } 45 | }), 46 | new ExtractTextPlugin('style.css', { allChunks: true }) 47 | ], 48 | 49 | target: 'electron-renderer' 50 | }); 51 | 52 | export default config; 53 | --------------------------------------------------------------------------------