├── .babelrc ├── assets └── icon.png ├── src ├── App.css ├── components │ └── App.js └── index.js ├── .gitignore ├── README.md ├── webpack.build.config.js ├── webpack.dev.config.js ├── package.json └── main.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/simple-electron-react/HEAD/assets/icon.png -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const App = () => { 4 | return ( 5 |
6 |

React Electron Boilerplate

7 |

This is a simple boilerplate for using React with Electron

8 |
9 | ) 10 | } 11 | 12 | export default App 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import App from './components/App' 4 | import './App.css' 5 | 6 | // Since we are using HtmlWebpackPlugin WITHOUT a template, we should create our own root node in the body element before rendering into it 7 | let root = document.createElement('div') 8 | 9 | root.id = 'root' 10 | document.body.appendChild(root) 11 | 12 | // Now we can render our application into it 13 | render(, document.getElementById('root')) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build folder and files # 2 | ########################## 3 | release-builds/ 4 | 5 | # Development folders and files # 6 | ################################# 7 | .tmp/ 8 | dist/ 9 | node_modules/ 10 | *.compiled.* 11 | 12 | # Folder config file # 13 | ###################### 14 | Desktop.ini 15 | 16 | # Folder notes # 17 | ################ 18 | _ignore/ 19 | 20 | # Log files & folders # 21 | ####################### 22 | logs/ 23 | *.log 24 | npm-debug.log* 25 | .npm 26 | 27 | # Packages # 28 | ############ 29 | # it's better to unpack these files and commit the raw source 30 | # git has its own built in compression methods 31 | *.7z 32 | *.dmg 33 | *.gz 34 | *.iso 35 | *.jar 36 | *.rar 37 | *.tar 38 | *.zip 39 | 40 | # Photoshop & Illustrator files # 41 | ################################# 42 | *.ai 43 | *.eps 44 | *.psd 45 | 46 | # Windows & Mac file caches # 47 | ############################# 48 | .DS_Store 49 | Thumbs.db 50 | ehthumbs.db 51 | 52 | # Windows shortcuts # 53 | ##################### 54 | *.lnk 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Simple Electron React Boilerplate 2 | 3 | This is a simple boilerplate to get up and running with Electron and React. It is a customized version of [Alex Devero's](https://github.com/alexdevero/electron-react-webpack-boilerplate) repo and is used in my Electron course 4 | 5 | ### Install 6 | 7 | #### Clone this repo 8 | 9 | ``` 10 | git clone https://github.com/bradtraversy/simple-electron-react.git 11 | ``` 12 | 13 | #### Install dependencies 14 | 15 | ``` 16 | npm install 17 | ``` 18 | 19 | or 20 | 21 | ``` 22 | yarn 23 | ``` 24 | 25 | ### Usage 26 | 27 | #### Run the app 28 | 29 | ``` 30 | npm run start 31 | ``` 32 | 33 | or 34 | 35 | ``` 36 | yarn start 37 | ``` 38 | 39 | #### Build the app (automatic) 40 | 41 | ``` 42 | npm run package 43 | ``` 44 | 45 | or 46 | 47 | ``` 48 | yarn package 49 | ``` 50 | 51 | #### Build the app (manual) 52 | 53 | ``` 54 | npm run build 55 | ``` 56 | 57 | or 58 | 59 | ``` 60 | yarn build 61 | ``` 62 | 63 | #### Test the app (after `npm run build` || `yarn run build`) 64 | 65 | ``` 66 | npm run prod 67 | ``` 68 | 69 | ``` 70 | yarn prod 71 | ``` 72 | 73 | ### Change app title 74 | 75 | Change the app title in the **webpack.build.config.js** and the **webpack.dev.config.js** files 76 | -------------------------------------------------------------------------------- /webpack.build.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const BabiliPlugin = require('babili-webpack-plugin') 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 6 | 7 | module.exports = { 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.css$/, 12 | use: [MiniCssExtractPlugin.loader, 'css-loader'], 13 | }, 14 | { 15 | test: /\.jsx?$/, 16 | use: [{ loader: 'babel-loader', query: { compact: false } }], 17 | }, 18 | { 19 | test: /\.(jpe?g|png|gif)$/, 20 | use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }], 21 | }, 22 | { 23 | test: /\.(eot|svg|ttf|woff|woff2)$/, 24 | use: [ 25 | { loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }, 26 | ], 27 | }, 28 | ], 29 | }, 30 | target: 'electron-renderer', 31 | plugins: [ 32 | new HtmlWebpackPlugin({ title: 'React Electron App' }), 33 | new MiniCssExtractPlugin({ 34 | // Options similar to the same options in webpackOptions.output 35 | // both options are optional 36 | filename: 'bundle.css', 37 | chunkFilename: '[id].css', 38 | }), 39 | new webpack.DefinePlugin({ 40 | 'process.env.NODE_ENV': JSON.stringify('production'), 41 | }), 42 | new BabiliPlugin(), 43 | ], 44 | stats: { 45 | colors: true, 46 | children: false, 47 | chunks: false, 48 | modules: false, 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const { spawn } = require('child_process') 5 | 6 | module.exports = { 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.css$/, 11 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], 12 | }, 13 | { 14 | test: /\.jsx?$/, 15 | use: [{ loader: 'babel-loader', query: { compact: false } }], 16 | }, 17 | { 18 | test: /\.(jpe?g|png|gif)$/, 19 | use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }], 20 | }, 21 | { 22 | test: /\.(eot|svg|ttf|woff|woff2)$/, 23 | use: [ 24 | { loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }, 25 | ], 26 | }, 27 | ], 28 | }, 29 | target: 'electron-renderer', 30 | plugins: [ 31 | new HtmlWebpackPlugin({ title: 'React Electron App' }), 32 | new webpack.DefinePlugin({ 33 | 'process.env.NODE_ENV': JSON.stringify('development'), 34 | }), 35 | ], 36 | devtool: 'cheap-source-map', 37 | devServer: { 38 | contentBase: path.resolve(__dirname, 'dist'), 39 | stats: { 40 | colors: true, 41 | chunks: false, 42 | children: false, 43 | }, 44 | before() { 45 | spawn('electron', ['.'], { 46 | shell: true, 47 | env: process.env, 48 | stdio: 'inherit', 49 | }) 50 | .on('close', (code) => process.exit(0)) 51 | .on('error', (spawnError) => console.error(spawnError)) 52 | }, 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-electron-react", 3 | "productName": "React Electron App", 4 | "version": "1.0.0", 5 | "description": "Simple boilerplate for building Electron apps with React", 6 | "license": "MIT", 7 | "engines": { 8 | "node": ">=9.0.0", 9 | "npm": ">=5.0.0", 10 | "yarn": ">=1.0.0" 11 | }, 12 | "browserslist": [ 13 | "last 4 versions" 14 | ], 15 | "main": "main.js", 16 | "scripts": { 17 | "prod": "cross-env NODE_ENV=production webpack --mode production --config webpack.build.config.js && electron --noDevServer .", 18 | "start": "cross-env NODE_ENV=development webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js --mode development", 19 | "build": "cross-env NODE_ENV=production webpack --config webpack.build.config.js --mode production", 20 | "package": "npm run build", 21 | "postpackage": "electron-packager ./ --out=./release-builds" 22 | }, 23 | "dependencies": { 24 | "react": "^16.13.1", 25 | "react-dom": "^16.13.1" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.9.6", 29 | "@babel/preset-env": "^7.9.6", 30 | "@babel/preset-react": "^7.9.4", 31 | "babel-loader": "^8.1.0", 32 | "babili-webpack-plugin": "^0.1.2", 33 | "cross-env": "^7.0.2", 34 | "css-loader": "^3.5.3", 35 | "electron": "^15.5.5", 36 | "electron-devtools-installer": "^3.0.0", 37 | "electron-packager": "^14.2.1", 38 | "file-loader": "^6.0.0", 39 | "html-webpack-plugin": "^4.3.0", 40 | "mini-css-extract-plugin": "^0.9.0", 41 | "style-loader": "^1.2.0", 42 | "webpack": "^4.43.0", 43 | "webpack-cli": "^3.3.11", 44 | "webpack-dev-server": "^3.10.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const url = require('url') 3 | const { app, BrowserWindow } = require('electron') 4 | 5 | let mainWindow 6 | 7 | let isDev = false 8 | 9 | if ( 10 | process.env.NODE_ENV !== undefined && 11 | process.env.NODE_ENV === 'development' 12 | ) { 13 | isDev = true 14 | } 15 | 16 | function createMainWindow() { 17 | mainWindow = new BrowserWindow({ 18 | width: 1100, 19 | height: 800, 20 | show: false, 21 | icon: `${__dirname}/assets/icon.png`, 22 | webPreferences: { 23 | nodeIntegration: true, 24 | }, 25 | }) 26 | 27 | let indexPath 28 | 29 | if (isDev && process.argv.indexOf('--noDevServer') === -1) { 30 | indexPath = url.format({ 31 | protocol: 'http:', 32 | host: 'localhost:8080', 33 | pathname: 'index.html', 34 | slashes: true, 35 | }) 36 | } else { 37 | indexPath = url.format({ 38 | protocol: 'file:', 39 | pathname: path.join(__dirname, 'dist', 'index.html'), 40 | slashes: true, 41 | }) 42 | } 43 | 44 | mainWindow.loadURL(indexPath) 45 | 46 | // Don't show until we are ready and loaded 47 | mainWindow.once('ready-to-show', () => { 48 | mainWindow.show() 49 | 50 | // Open devtools if dev 51 | if (isDev) { 52 | const { 53 | default: installExtension, 54 | REACT_DEVELOPER_TOOLS, 55 | } = require('electron-devtools-installer') 56 | 57 | installExtension(REACT_DEVELOPER_TOOLS).catch((err) => 58 | console.log('Error loading React DevTools: ', err) 59 | ) 60 | mainWindow.webContents.openDevTools() 61 | } 62 | }) 63 | 64 | mainWindow.on('closed', () => (mainWindow = null)) 65 | } 66 | 67 | app.on('ready', createMainWindow) 68 | 69 | app.on('window-all-closed', () => { 70 | if (process.platform !== 'darwin') { 71 | app.quit() 72 | } 73 | }) 74 | 75 | app.on('activate', () => { 76 | if (mainWindow === null) { 77 | createMainWindow() 78 | } 79 | }) 80 | 81 | // Stop error 82 | app.allowRendererProcessReuse = true 83 | --------------------------------------------------------------------------------