├── .babelrc ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── app ├── build │ ├── .gitignore │ └── README.md └── src │ └── main │ ├── app.js │ ├── index.html │ ├── js │ ├── actions │ │ └── list_actions.js │ ├── app.js │ ├── components │ │ ├── header.js │ │ ├── home.js │ │ ├── list_item_preview.js │ │ ├── list_item_view.js │ │ └── list_items.js │ ├── consts │ │ ├── action_types.js │ │ ├── default_state.js │ │ └── list_items.js │ ├── containers │ │ ├── list_item_preview.js │ │ ├── list_item_view.js │ │ └── list_items.js │ ├── index.js │ └── reducers │ │ ├── index.js │ │ └── list.js │ └── res │ ├── images │ └── logo.png │ └── scss │ ├── home.scss │ ├── main.scss │ ├── palette.scss │ └── view_item.scss ├── package-lock.json ├── package.json └── webpack ├── dev.config.js └── prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }], 4 | "react", 5 | "stage-1" 6 | ] 7 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "es6": true, 5 | "node": true, 6 | "browser": true 7 | }, 8 | "plugins": [ 9 | "react", 10 | "jsx-a11y", 11 | "import" 12 | ], 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "experimentalObjectRestSpread": true, 16 | "jsx": true 17 | }, 18 | "sourceType": "module", 19 | "indent": ["error", 2] 20 | }, 21 | "rules": { 22 | "react/sort-comp": [1, { 23 | "order": [ 24 | "constructor", 25 | "lifecycle", 26 | "everything-else", 27 | "render" 28 | ] 29 | }], 30 | "react/jsx-filename-extension": [1, { 31 | "extensions": [".js", ".jsx"] 32 | }], 33 | "jsx-a11y/no-static-element-interactions": 0, 34 | "jsx-a11y/no-noninteractive-element-interactions": 0, 35 | "jsx-a11y/href-no-hash": "off", 36 | "import/no-dynamic-require": 0, 37 | "import/no-extraneous-dependencies": "off", 38 | "linebreak-style": ["error", "unix"], 39 | "react/forbid-prop-types": 0, 40 | "react/require-default-props": 0, 41 | "function-paren-newline": ["error", "consistent"], 42 | "jsx-a11y/anchor-is-valid": [ "error", { 43 | "components": [ "Link" ], 44 | "specialLink": [ "to" ], 45 | "aspects": ["invalidHref"] 46 | }] 47 | }, 48 | "settings": { 49 | "import/resolver": { 50 | "webpack": { 51 | "config": "./webpack/dev.config.js" 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | yarn.lock 4 | release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tahnik Mustasin 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ElectronJS + ReactJS boilerplate 2 | 3 | A simple and clean boilerplate for Electron and react. It includes: 4 | 5 | - State management with [Redux] 6 | - Routing with [React-Router v4] 7 | - [React] and [Redux devtools] for debugging 8 | - .eslint for [ESlint] 9 | - [Webpack 3] for bundling 10 | - [Babel] for compiling 11 | 12 | ## Usage 13 | 14 | NodeJS v8.3.0 15 | 16 | - Clone the repo 17 | `git clone https://github.com/tahnik/electron-react.git` 18 | - Edit the package.json to change the `name` to your project name and `description` to your project description. Change the `author` as well. 19 | - Run `npm i` 20 | - Then run the project as described in the section below 21 | 22 | ## How to 23 | - To run in development mode with hot reloading, open a terminal inside your project and run 24 | ```bash 25 | npm run dev 26 | ``` 27 | 28 | This commands creates a webpack dev server which will watch and reload the bundle as you edit and will it available at https://localhost:8080. 29 | The command will also run the electron app in development mode. So it will open up devtools with React and Redux devtools initialized. 30 | - To build the app and test if it is working: 31 | 32 | ```bash 33 | npm start 34 | ``` 35 | 36 | This command will compile the app in production mode and start the app. Here is you still toggle the developer tools from the menu and see if there is any errors 37 | 38 | - To package the app and create a distributable: 39 | 40 | ```bash 41 | npm run dist 42 | ``` 43 | 44 | This will create a relevant distributable file. For example, if you are on Windows, it will create a .exe file in the release folder. 45 | 46 | 47 | # Contribute 48 | Awesome! Create a new issue or do a pull request. 49 | 50 | 51 | [Redux]: 52 | [React-Router v4]: 53 | [React]: 54 | [Redux devtools]: 55 | [Jest]: 56 | [ESlint]: 57 | [Webpack 3]: 58 | [Babel]: 59 | [Yeoman]: 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/build/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | !README.md -------------------------------------------------------------------------------- /app/build/README.md: -------------------------------------------------------------------------------- 1 | All the build files will be here -------------------------------------------------------------------------------- /app/src/main/app.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | // Module to control application life. 3 | const { app } = electron; 4 | // Module to create native browser window. 5 | const { BrowserWindow } = electron; 6 | 7 | const path = require('path'); 8 | const url = require('url'); 9 | 10 | 11 | // Keep a global reference of the window object, if you don't, the window will 12 | // be closed automatically when the JavaScript object is garbage collected. 13 | let mainWindow; 14 | 15 | /** This function will create the mainWindow */ 16 | function createWindow() { 17 | // Create the browser window. 18 | mainWindow = new BrowserWindow({ width: 1366, height: 768 }); 19 | 20 | // and load the index.html of the app. 21 | mainWindow.loadURL(url.format({ 22 | pathname: path.join(__dirname, 'index.html'), 23 | protocol: 'file:', 24 | slashes: true, 25 | })); 26 | 27 | if (process.env.NODE_ENV === 'development') { 28 | // Open the DevTools. 29 | mainWindow.webContents.openDevTools(); 30 | const { 31 | default: installExtension, 32 | REACT_DEVELOPER_TOOLS, 33 | REDUX_DEVTOOLS, 34 | } = require('electron-devtools-installer'); // eslint-disable-line 35 | installExtension(REACT_DEVELOPER_TOOLS) 36 | .then(name => console.log(`Added Extension: ${name}`)) 37 | .catch(err => console.log('An error occurred: ', err)); 38 | 39 | installExtension(REDUX_DEVTOOLS) 40 | .then(name => console.log(`Added Extension: ${name}`)) 41 | .catch(err => console.log('An error occurred: ', err)); 42 | } 43 | 44 | // Emitted when the window is closed. 45 | mainWindow.on('closed', () => { 46 | // Dereference the window object, usually you would store windows 47 | // in an array if your app supports multi windows, this is the time 48 | // when you should delete the corresponding element. 49 | mainWindow = null; 50 | }); 51 | } 52 | 53 | // This method will be called when Electron has finished 54 | // initialization and is ready to create browser windows. 55 | // Some APIs can only be used after this event occurs. 56 | app.on('ready', createWindow); 57 | 58 | // Quit when all windows are closed. 59 | app.on('window-all-closed', () => { 60 | // On OS X it is common for applications and their menu bar 61 | // to stay active until the user quits explicitly with Cmd + Q 62 | if (process.platform !== 'darwin') { 63 | app.quit(); 64 | } 65 | }); 66 | 67 | app.on('activate', () => { 68 | // On OS X it's common to re-create a window in the app when the 69 | // dock icon is clicked and there are no other windows open. 70 | if (mainWindow === null) { 71 | createWindow(); 72 | } 73 | }); 74 | 75 | // In this file you can include the rest of your app's specific main process 76 | // code. You can also put them in separate files and require them here. 77 | -------------------------------------------------------------------------------- /app/src/main/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | React Router Redux 10 | 11 | 12 | 13 |
14 | 36 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/js/actions/list_actions.js: -------------------------------------------------------------------------------- 1 | import { LIST_ACTIONS } from '../consts/action_types'; 2 | 3 | export const previewItem = name => ({ 4 | type: LIST_ACTIONS.ITEM_PREVIEW, 5 | name, // shorthand for name: name 6 | }); 7 | 8 | export const viewItem = name => ({ 9 | type: LIST_ACTIONS.ITEM_VIEW, 10 | name, 11 | }); 12 | 13 | export const addItem = item => ({ 14 | type: LIST_ACTIONS.ITEM_ADD, 15 | item, // shorthand for item: item 16 | }); 17 | 18 | export const clearItem = () => ({ 19 | type: LIST_ACTIONS.ITEM_CLEAR, 20 | }); 21 | -------------------------------------------------------------------------------- /app/src/main/js/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router-dom'; 3 | import Header from './components/header'; 4 | import Home from './components/home'; 5 | import ItemView from './containers/list_item_view'; 6 | 7 | const App = () => ( 8 |
9 |
10 | 11 | 12 |
13 | ); 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /app/src/main/js/components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from '../../res/images/logo.png'; 3 | 4 | const Header = () => ( 5 |
6 |
7 | react logo 8 |
9 |

React Redux Router

10 |
11 | ); 12 | 13 | export default Header; 14 | -------------------------------------------------------------------------------- /app/src/main/js/components/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ListItems from '../containers/list_items'; 3 | import ListItemPreview from '../containers/list_item_preview'; 4 | 5 | const Home = () => ( 6 |
7 | 8 | 9 |
10 | ); 11 | 12 | export default Home; 13 | -------------------------------------------------------------------------------- /app/src/main/js/components/list_item_preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const ListItemPreview = ({ item }) => { 6 | if (!item) { 7 | return ( 8 |
9 |

Select an item

10 |

Description will appear here

11 |
12 | ); 13 | } 14 | return ( 15 |
16 |

{ item.name }

17 |

{ item.description }

18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | ListItemPreview.propTypes = { 26 | item: PropTypes.object, 27 | }; 28 | 29 | ListItemPreview.defaultProps = { 30 | item: null, 31 | }; 32 | 33 | export default ListItemPreview; 34 | -------------------------------------------------------------------------------- /app/src/main/js/components/list_item_view.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class ListItemView extends Component { 6 | componentDidMount() { 7 | const { viewItem, match } = this.props; 8 | viewItem(match.params.name); 9 | } 10 | render() { 11 | const { item } = this.props; 12 | if (!item) { 13 | return (
Loading...
); 14 | } 15 | 16 | return ( 17 |
18 | 19 | 20 | 21 |

{ item.name }

22 |

{ item.description }

23 |
24 | ); 25 | } 26 | } 27 | 28 | ListItemView.propTypes = { 29 | viewItem: PropTypes.func.isRequired, 30 | match: PropTypes.object.isRequired, 31 | item: PropTypes.object, 32 | }; 33 | 34 | ListItemView.defaultProps = { 35 | item: null, 36 | }; 37 | 38 | export default ListItemView; 39 | -------------------------------------------------------------------------------- /app/src/main/js/components/list_items.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class ListView extends Component { 5 | renderList() { 6 | const { listItems, previewItem } = this.props; 7 | return Object.keys(listItems).map((key) => { 8 | const item = listItems[key]; 9 | return ( 10 |
  • 13 | 14 |
  • 15 | ); 16 | }); 17 | } 18 | render() { 19 | return ( 20 |
    21 |
      22 | { this.renderList() } 23 |
    24 |
    25 | ); 26 | } 27 | } 28 | 29 | ListView.propTypes = { 30 | listItems: PropTypes.object.isRequired, 31 | previewItem: PropTypes.func.isRequired, 32 | }; 33 | 34 | export default ListView; 35 | -------------------------------------------------------------------------------- /app/src/main/js/consts/action_types.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | export const LIST_ACTIONS = { 3 | ITEM_PREVIEW: 'ITEM_PREVIEW', 4 | ITEM_VIEW: 'ITEM_VIEW', 5 | ITEM_ADD: 'ITEM_ADD', 6 | ITEM_CLEAR: 'ITEM_CLEAR', 7 | }; 8 | -------------------------------------------------------------------------------- /app/src/main/js/consts/default_state.js: -------------------------------------------------------------------------------- 1 | import LIST_ITEMS from './list_items'; 2 | 3 | // eslint-disable-next-line 4 | export const LISTS = { items: LIST_ITEMS, itemPreview: null, itemView: null }; -------------------------------------------------------------------------------- /app/src/main/js/consts/list_items.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ACTIONS: { 3 | name: 'actions', 4 | description: 'Actions are payloads of information that send data from your application to your store. They are the only source of information for the store.', 5 | }, 6 | STORE: { 7 | name: 'store', 8 | description: 'The state of your whole application is stored in an object tree within a single store.', 9 | }, 10 | REDUCERS: { 11 | name: 'reducers', 12 | description: 'Actions describe the fact that something happened, but don\'t specify how the application\'s state changes in response. This is the job of reducers.', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /app/src/main/js/containers/list_item_preview.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import ListItemPreview from '../components/list_item_preview'; 3 | 4 | /* 5 | This is a redux specific function. 6 | What is does is: It gets the state specified in here from the global redux state. 7 | For example, here we are retrieving the list of items from the redux store. 8 | Whenever this list changes, any component that is using this list of item will re-render. 9 | */ 10 | function mapStateToProps(state) { 11 | return { 12 | item: state.list.itemPreview, 13 | }; 14 | } 15 | 16 | /* 17 | Here we are creating a Higher order component 18 | https://facebook.github.io/react/docs/higher-order-components.html 19 | */ 20 | export default connect(mapStateToProps)(ListItemPreview); 21 | -------------------------------------------------------------------------------- /app/src/main/js/containers/list_item_view.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { viewItem } from '../actions/list_actions'; 3 | import ItemView from '../components/list_item_view'; 4 | 5 | const mapStateToProps = state => ({ 6 | item: state.list.itemView, 7 | }); 8 | 9 | const mapDispatchToProps = dispatch => ({ 10 | viewItem: (name) => { 11 | dispatch(viewItem(name)); 12 | }, 13 | }); 14 | 15 | /* 16 | Here we are creating a Higher order component 17 | https://facebook.github.io/react/docs/higher-order-components.html 18 | */ 19 | export default connect(mapStateToProps, mapDispatchToProps)(ItemView); 20 | -------------------------------------------------------------------------------- /app/src/main/js/containers/list_items.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { previewItem } from '../actions/list_actions'; 3 | import ListItems from '../components/list_items'; 4 | 5 | /* 6 | This is a redux specific function. 7 | What it does is: It gets the state specified in here from the global redux state. 8 | For example, here we are retrieving the list of items from the redux store. 9 | Whenever this list changes, any component that is using this list of item will re-render. 10 | */ 11 | const mapStateToProps = state => ({ 12 | listItems: state.list.items, 13 | }); 14 | 15 | /* 16 | This is a redux specific function. 17 | http://redux.js.org/docs/api/bindActionCreators.html 18 | */ 19 | const mapDispatchToProps = dispatch => ({ 20 | previewItem: (name) => { 21 | dispatch(previewItem(name)); 22 | }, 23 | }); 24 | 25 | 26 | /* 27 | Here we are creating a Higher order component 28 | https://facebook.github.io/react/docs/higher-order-components.html 29 | */ 30 | export default connect(mapStateToProps, mapDispatchToProps)(ListItems); 31 | -------------------------------------------------------------------------------- /app/src/main/js/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore } from 'redux'; 5 | import { HashRouter } from 'react-router-dom'; 6 | import { AppContainer } from 'react-hot-loader'; 7 | import reducers from './reducers/index'; 8 | import App from './app'; 9 | 10 | 11 | /* 12 | Here we are getting the initial state injected by the server. See routes/index.js for more details 13 | */ 14 | const initialState = window.__INITIAL_STATE__; // eslint-disable-line 15 | 16 | const store = createStore(reducers, initialState); 17 | 18 | /* 19 | While creating a store, we will inject the initial state we received from the server to our app. 20 | */ 21 | const render = (Component) => { 22 | ReactDOM.render( 23 | 24 | 25 | 26 | 27 | 28 | 29 | , 30 | document.getElementById('reactbody'), 31 | ); 32 | }; 33 | 34 | render(App); 35 | 36 | if (module.hot) { 37 | module.hot.accept('./app', () => { 38 | // eslint-disable-next-line 39 | const nextApp = require('./app').default; 40 | render(nextApp); 41 | }); 42 | } 43 | 44 | // module.hot.accept('./reducers', () => { 45 | // // eslint-disable-next-line 46 | // const nextRootReducer = require('./reducers/index'); 47 | // store.replaceReducer(nextRootReducer); 48 | // }); 49 | -------------------------------------------------------------------------------- /app/src/main/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import list from './list'; 3 | 4 | const rootReducer = combineReducers({ 5 | list, // shorthand for lists: lists 6 | }); 7 | 8 | export default rootReducer; 9 | -------------------------------------------------------------------------------- /app/src/main/js/reducers/list.js: -------------------------------------------------------------------------------- 1 | import { LIST_ACTIONS } from '../consts/action_types'; 2 | import { LISTS } from '../consts/default_state'; 3 | 4 | export default (state = LISTS, action) => { 5 | switch (action.type) { 6 | case LIST_ACTIONS.ITEM_PREVIEW: 7 | return { ...state, itemPreview: state.items[action.name.toUpperCase()] }; 8 | case LIST_ACTIONS.ITEM_VIEW: 9 | return { ...state, itemView: state.items[action.name.toUpperCase()] }; 10 | case LIST_ACTIONS.ITEM_CLEAR: 11 | return { ...state, itemView: null }; 12 | case LIST_ACTIONS.ITEM_ADD: { 13 | const nextItems = { ...state.items }; 14 | const itemToAdd = action.item; 15 | nextItems[itemToAdd.name.toUpperCase()] = itemToAdd; 16 | const returnVal = { ...state, items: nextItems }; 17 | return returnVal; 18 | } 19 | default: 20 | return state; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /app/src/main/res/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tahnik/electron-react/a1b0029e721dbb7aa556864cb5b46ca5a6de2f13/app/src/main/res/images/logo.png -------------------------------------------------------------------------------- /app/src/main/res/scss/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | display: flex; 3 | .list_items { 4 | flex: 1; 5 | padding: 1rem; 6 | } 7 | .preview { 8 | flex: 2; 9 | padding: 1rem; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | justify-content: center; 14 | h2 { 15 | text-transform: capitalize; 16 | margin-top: 0; 17 | } 18 | p { 19 | text-align: center; 20 | } 21 | button { 22 | font-size: 1rem; 23 | } 24 | } 25 | ul { 26 | list-style-type: none; 27 | padding: 0; 28 | li { 29 | margin: 0.5rem 0; 30 | button { 31 | width: 100%; 32 | font-size: 1.5rem; 33 | transition: all 0.3s; 34 | text-transform: capitalize; 35 | &:hover { 36 | cursor: pointer; 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/res/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import './palette.scss'; 2 | @import './home.scss'; 3 | @import './view_item.scss'; 4 | 5 | @import url('https://fonts.googleapis.com/css?family=Raleway:400,600|Roboto:100,300'); 6 | 7 | .reactbody { 8 | background-color: $color5; 9 | } 10 | 11 | h1, h2, h3, h4, h5, h6 { 12 | font-family: 'Roboto', sans-serif; 13 | } 14 | 15 | h1 { 16 | font-weight: 300; 17 | } 18 | 19 | button { 20 | font-family: 'Roboto', sans-serif; 21 | background-color: $color1; 22 | font-weight: 300; 23 | border: none; 24 | outline:none; 25 | padding: 0.5rem 1rem; 26 | border-radius: 3px; 27 | color: white; 28 | transition: all 0.3s; 29 | &:hover { 30 | cursor: pointer; 31 | background-color: $color2; 32 | } 33 | } 34 | 35 | div, p, span { 36 | font-family: 'Raleway', sans-serif; 37 | } 38 | 39 | .header { 40 | display: flex; 41 | flex-direction: column; 42 | align-items: center; 43 | h1 { 44 | text-align: center; 45 | } 46 | .logo { 47 | width: 100%; 48 | img { 49 | width: 100%; 50 | } 51 | } 52 | border-bottom: 2px solid gray; 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/res/scss/palette.scss: -------------------------------------------------------------------------------- 1 | /* Coolors Exported Palette - coolors.co/03b5aa-037971-023436-00bfb3-049a8f */ 2 | 3 | /* HSL */ 4 | $color1: hsla(176%, 97%, 36%, 1); 5 | $color2: hsla(176%, 95%, 24%, 1); 6 | $color3: hsla(182%, 93%, 11%, 1); 7 | $color4: hsla(176%, 100%, 37%, 1); 8 | $color5: hsla(176%, 95%, 31%, 1); 9 | 10 | /* RGB */ 11 | $color1: rgba(3, 181, 170, 1); 12 | $color2: rgba(3, 121, 113, 1); 13 | $color3: rgba(2, 52, 54, 1); 14 | $color4: rgba(0, 191, 179, 1); 15 | $color5: rgba(4, 154, 143, 1); -------------------------------------------------------------------------------- /app/src/main/res/scss/view_item.scss: -------------------------------------------------------------------------------- 1 | .view_item { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | padding: 2rem; 6 | h2 { 7 | margin-top: 3rem; 8 | text-transform: capitalize; 9 | } 10 | p { 11 | text-align: center; 12 | } 13 | button { 14 | font-size: 1rem; 15 | } 16 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react", 3 | "version": "1.0.0", 4 | "description": "Boilerplate for Electron and React", 5 | "main": "./app/build/app.js", 6 | "scripts": { 7 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --config webpack/dev.config.js\" \"cross-env NODE_ENV=development electron ./app/src/main/app.js\"", 8 | "prod": "cross-env NODE_ENV=production webpack --progress --color --config webpack/prod.config.js", 9 | "start": "npm run prod && electron app/build/app.js", 10 | "lint": "eslint --ext .jsx,.js app/src/main --fix", 11 | "pack": "electron-builder --dir", 12 | "dist": "npm run prod && electron-builder", 13 | "postinstall": "electron-builder install-app-deps" 14 | }, 15 | "author": "Tahnik Mustasin", 16 | "license": "MIT", 17 | "build": { 18 | "productName": "electron-react", 19 | "win": { 20 | "target": "NSIS" 21 | }, 22 | "linux": { 23 | "target": [ 24 | "deb", 25 | "AppImage" 26 | ] 27 | }, 28 | "mac": { 29 | "category": "public.app-category.developer-tools" 30 | }, 31 | "directories": { 32 | "output": "release" 33 | } 34 | }, 35 | "keywords": [ 36 | "electron", 37 | "react", 38 | "react router v4", 39 | "redux", 40 | "webpack 3" 41 | ], 42 | "devDependencies": { 43 | "babel-core": "^6.26.3", 44 | "babel-loader": "^7.1.4", 45 | "babel-minify-webpack-plugin": "^0.3.1", 46 | "babel-preset-es2015": "^6.24.1", 47 | "babel-preset-react": "^6.24.1", 48 | "babel-preset-stage-1": "^6.24.1", 49 | "concurrently": "^3.5.1", 50 | "copy-webpack-plugin": "^4.5.1", 51 | "cross-env": "^5.1.4", 52 | "css-loader": "^0.28.11", 53 | "electron": "^1.8.6", 54 | "electron-builder": "^20.10.0", 55 | "electron-devtools-installer": "^2.2.3", 56 | "eslint": "^4.19.1", 57 | "eslint-config-airbnb": "^16.1.0", 58 | "eslint-config-google": "^0.9.1", 59 | "eslint-plugin-import": "^2.11.0", 60 | "eslint-plugin-jsx-a11y": "^6.0.3", 61 | "eslint-plugin-react": "^7.7.0", 62 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 63 | "file-loader": "^1.1.11", 64 | "node-sass": "^4.9.0", 65 | "react-hot-loader": "^4.1.2", 66 | "sass-loader": "^7.0.1", 67 | "style-loader": "^0.21.0", 68 | "webpack": "^4.6.0", 69 | "webpack-cli": "^2.0.15", 70 | "webpack-dev-server": "^3.1.3", 71 | "webpack-node-externals": "^1.7.2" 72 | }, 73 | "dependencies": { 74 | "react": "^16.3.2", 75 | "react-dom": "^16.3.2", 76 | "react-redux": "^5.0.7", 77 | "react-router": "4.2.0", 78 | "react-router-dom": "4.2.2", 79 | "redux": "^4.0.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | context: path.join(__dirname, '../app'), 7 | devtool: 'inline-source-map', 8 | entry: { 9 | app: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-dev-server/client?http://localhost:8080', 12 | 'webpack/hot/only-dev-server', 13 | './src/main/js/index.js', 14 | './src/main/res/scss/main.scss', 15 | ], 16 | }, 17 | mode: 'development', 18 | output: { 19 | path: path.resolve(__dirname, './app/build'), 20 | filename: 'app.bundle.js', 21 | publicPath: 'http://localhost:8080/', 22 | }, 23 | devServer: { 24 | hot: true, 25 | publicPath: 'http://localhost:8080/', 26 | historyApiFallback: true, 27 | }, 28 | module: { 29 | rules: [{ 30 | test: /\.js$/, 31 | exclude: /node_modules/, 32 | use: { 33 | loader: 'babel-loader', 34 | }, 35 | }, 36 | { 37 | test: /\.scss$/, 38 | use: ['style-loader', 'css-loader', 'sass-loader'], 39 | }, 40 | { 41 | test: /\.(png|jpg|gif)$/, 42 | use: [{ 43 | loader: 'file-loader', 44 | options: {}, 45 | }], 46 | }, 47 | ], 48 | }, 49 | plugins: [ 50 | new webpack.HotModuleReplacementPlugin(), 51 | new webpack.NamedModulesPlugin(), 52 | new CopyWebpackPlugin([{ 53 | from: './src/main/app.js', 54 | }, 55 | { 56 | from: './src/main/index.html', 57 | }, 58 | ]), 59 | ], 60 | }; 61 | -------------------------------------------------------------------------------- /webpack/prod.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const MinifyPlugin = require('babel-minify-webpack-plugin'); 5 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | 7 | module.exports = { 8 | context: path.join(__dirname, '../app'), 9 | devtool: 'source-map', 10 | entry: { 11 | app: [ 12 | './src/main/js/index.js', 13 | './src/main/res/scss/main.scss', 14 | ], 15 | }, 16 | mode: 'production', 17 | output: { 18 | path: path.resolve(__dirname, '../app/build'), 19 | filename: 'app.bundle.js', 20 | }, 21 | module: { 22 | rules: [{ 23 | test: /\.js$/, 24 | exclude: /node_modules/, 25 | use: { 26 | loader: 'babel-loader', 27 | }, 28 | }, 29 | { 30 | test: /\.scss$/, 31 | use: ExtractTextPlugin.extract({ 32 | fallback: 'style-loader', 33 | use: ['css-loader', 'sass-loader'], 34 | }), 35 | }, 36 | { 37 | test: /\.(png|jpg|gif)$/, 38 | use: [{ 39 | loader: 'file-loader', 40 | options: { 41 | outputPath: 'images/', 42 | }, 43 | }], 44 | }, 45 | ], 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': { 50 | NODE_ENV: JSON.stringify('production'), 51 | }, 52 | }), 53 | new webpack.optimize.ModuleConcatenationPlugin(), 54 | new webpack.optimize.AggressiveMergingPlugin(), 55 | new webpack.optimize.OccurrenceOrderPlugin(), 56 | new MinifyPlugin(), 57 | new ExtractTextPlugin('css/main.css'), 58 | new CopyWebpackPlugin([ 59 | { 60 | from: './src/main/app.js', 61 | to: path.join(__dirname, '../app/build'), 62 | }, 63 | { 64 | from: './src/main/index.html', 65 | to: path.join(__dirname, '../app/build'), 66 | }, 67 | ]), 68 | ], 69 | }; 70 | --------------------------------------------------------------------------------