├── .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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------