├── .gitignore ├── README.md ├── generators └── app │ ├── index.js │ └── templates │ └── electron-react │ ├── README.md │ ├── electron-builder.yml │ ├── gitignore │ ├── package.json │ ├── public │ ├── electron.js │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── index.js │ └── utilities │ └── registerServiceWorker.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # yarn 25 | package-lock.json 26 | yarn.lock 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > lightweight yeoman generator to get you started with the beautiful react-electron combo. 2 | 3 | ## Getting started 4 | The usage is very simple: 5 | - installign yeoman, if you haven't done that yet 6 | ```bash 7 | npm install -g yo 8 | ``` 9 | - installing the generator 10 | ```bash 11 | npm install generator-react-electron -g 12 | ``` 13 | - call this from inside the folder you want to bootstrap your application 14 | ```bash 15 | yo react-electron 16 | ``` 17 | 18 | And you are now good to go! 19 | 20 | ### Development and deployment 21 | - to start the development environment with hot reloading 22 | ```bash 23 | npm run electron-dev 24 | ``` 25 | - to create an optimized version of you app (output in the dist folder) 26 | ```bash 27 | npm run electron-pack 28 | ``` 29 | ### Autoupdates 30 | If you chose to have autoupdates, change the package version, run the following line, then go to the repository's releases and publish the new release. 31 | ```bash 32 | npm run ship 33 | ``` 34 | 35 | ### Linting 36 | If you opted-in prettier, your files will automatically be linted on commit, or when you run 37 | ```bash 38 | npm run pretty 39 | ``` 40 | 41 | ### Sass 42 | If you chose to use sass, it will be converted into css automatically when building and is also hot-reloaded during development 43 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | var Generator = require("yeoman-generator") 2 | 3 | module.exports = class extends Generator { 4 | // The name `constructor` is important here 5 | constructor(args, opts) { 6 | // Calling the super constructor is important so our generator is correctly set up 7 | super(args, opts) 8 | this.answers={} 9 | // Next, add your custom code 10 | } 11 | prompting() { 12 | function createApp(answers) { 13 | answers.name = answers.name 14 | .split(" ") 15 | .join("") 16 | .toLowerCase() // remove whitespace and lowercase 17 | if (answers.sass){ 18 | answers.electrondev = /^win/.test(process.platform) 19 | ? `concurrently \\"yarn watch-css\\" \\"SET BROWSER=none&&yarn start\\" \\"wait-on http://localhost:3000 && electron .\\"` 20 | : `concurrently \\"yarn watch-css\\" \\"export BROWSER=none && yarn start\\" \\"wait-on http://localhost:3000 && electron .\\"` 21 | } 22 | else{ 23 | answers.electrondev = /^win/.test(process.platform) 24 | ? `concurrently \\"SET BROWSER=none&&yarn start\\" \\"wait-on http://localhost:3000 && electron .\\"` 25 | : `concurrently \\"export BROWSER=none && yarn start\\" \\"wait-on http://localhost:3000 && electron .\\"` 26 | } 27 | 28 | this.fs.copyTpl( 29 | this.templatePath("electron-react/README.md"), 30 | this.destinationPath("README.md"), 31 | answers 32 | ) 33 | this.fs.copyTpl( 34 | this.templatePath("electron-react/package.json"), 35 | this.destinationPath("package.json"), 36 | answers 37 | ) 38 | this.fs.copyTpl( 39 | this.templatePath("electron-react/gitignore"), 40 | this.destinationPath(".gitignore"), 41 | answers 42 | ) 43 | this.fs.copyTpl( 44 | this.templatePath("electron-react/src/App.js"), 45 | this.destinationPath("src/App.js"), 46 | answers 47 | ) 48 | this.fs.copy( 49 | this.templatePath("electron-react/src/App.test.js"), 50 | this.destinationPath("src/App.test.js") 51 | ) 52 | this.fs.copy( 53 | this.templatePath("electron-react/src/index.js"), 54 | this.destinationPath("src/index.js") 55 | ) 56 | this.fs.copy( 57 | this.templatePath( 58 | "electron-react/src/utilities/registerServiceWorker.js" 59 | ), 60 | this.destinationPath("src/utilities/registerServiceWorker.js") 61 | ) 62 | this.fs.copyTpl( 63 | this.templatePath("electron-react/public/electron.js"), 64 | this.destinationPath("public/electron.js"), 65 | answers 66 | ) 67 | this.fs.copy( 68 | this.templatePath("electron-react/public/favicon.ico"), 69 | this.destinationPath("public/favicon.ico") 70 | ) 71 | this.fs.copy( 72 | this.templatePath("electron-react/public/index.html"), 73 | this.destinationPath("public/index.html") 74 | ) 75 | this.fs.copyTpl( 76 | this.templatePath("electron-react/public/manifest.json"), 77 | this.destinationPath("public/manifest.json"), 78 | answers 79 | ) 80 | if (answers.autoupdate) { 81 | this.fs.copyTpl( 82 | this.templatePath("electron-react/electron-builder.yml"), 83 | this.destinationPath("electron-builder.yml"), 84 | answers 85 | ) 86 | } 87 | this.answers=answers 88 | this.npmInstall() 89 | } 90 | 91 | this.prompt([ 92 | { 93 | type: "input", 94 | name: "name", 95 | message: "Your project name", 96 | default: this.appname 97 | .split(" ") 98 | .join("") 99 | .toLowerCase(), // Default to current folder name 100 | }, 101 | { 102 | type: "input", 103 | name: "description", 104 | message: "Your project description", 105 | }, 106 | { 107 | type: "input", 108 | name: "author", 109 | message: "Who are you?", 110 | default: this.user.git.name, 111 | store: true, 112 | }, 113 | { 114 | type: "confirm", 115 | name: "sass", 116 | message: "Do you want to use sass?" 117 | }, 118 | { 119 | type: "confirm", 120 | name: "prettier", 121 | message: "Do you want to use Prettier linter?" 122 | }, 123 | { 124 | type: "confirm", 125 | name: "autoupdate", 126 | message: "Do you want to enable automatic update delivery?", 127 | }, 128 | ]).then(answers => { 129 | createApp = createApp.bind(this) 130 | if (!answers.autoupdate) { 131 | createApp(answers) 132 | } else { 133 | this.prompt([ 134 | { 135 | type: "input", 136 | name: "repository", 137 | message: "Github repository link", 138 | default: 139 | "https://github.com/" + 140 | this.user.git.name() + 141 | "/" + 142 | this.appname 143 | .split(" ") 144 | .join("") 145 | .toLowerCase(), 146 | }, 147 | { 148 | type: "input", 149 | name: "token", 150 | message: "Enter your github access token", 151 | store: true, 152 | }, 153 | ]).then(answers2 => { 154 | createApp({...answers, ...answers2}) 155 | }) 156 | } 157 | }) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/README.md: -------------------------------------------------------------------------------- 1 | # <%= name %> 2 | <%= description %> 3 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/electron-builder.yml: -------------------------------------------------------------------------------- 1 | publish: 2 | provider: github 3 | token: <%= token %> 4 | appId: com.<%= name %> 5 | files: 6 | - build/**/* 7 | - node_modules/**/*" 8 | directories: 9 | buildResources: assets 10 | productName: <%= name %> 11 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | # yarn 25 | package-lock.json 26 | yarn.lock 27 | 28 | <% if (autoupdate) { %> 29 | electron-builder.yml 30 | <% } %> 31 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= name %>", 3 | "version": "0.1.0", 4 | "author": "<%= author %>", 5 | "homepage": "./", 6 | "description": "<%= description %>", 7 | "main": "public/electron.js",<% if (autoupdate) { %> 8 | "repository": "<%= repository %>",<% } %> 9 | "build": { 10 | "appId": "com.<%= name %>", 11 | "files": ["build/**/*", "node_modules/**/*"], 12 | "directories": { 13 | "buildResources": "assets" 14 | } 15 | }, 16 | "dependencies": { 17 | <% if (sass) { %>"node-sass-chokidar": "0.0.3", 18 | <% } %><% if (autoupdate) { %>"electron-updater": "^2.10.1",<% } %> 19 | "react": "^15.6.1", 20 | "react-dom": "^15.6.1", 21 | "react-scripts": "1.0.10", 22 | "electron-is-dev": "^0.3.0" 23 | 24 | },<% if (prettier) { %> 25 | "lint-staged": { 26 | "{{public,src}/**/*.{scss,js,json},package.json}": [ 27 | "prettier --write \"{{public,src}/**/*.{scss,js,json},package.json}\"", 28 | "git add" 29 | ] 30 | },<% } %> 31 | "scripts": {<% if (autoupdate) { %> 32 | <% if (sass) { %>"preship":"yarn build && yarn build-css",<% } %><% if (!sass) { %>"preship":"yarn build",<% } %> 33 | "ship": 34 | "build --em.main=build/electron.js --win --ia32 -p always -c electron-builder.yml",<% } %> 35 | <% if (sass) { %>"build-css": "node-sass-chokidar src/ -o src/", 36 | "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",<% } %> 37 | "start": "react-scripts start", 38 | "build": "react-scripts build", 39 | "test": "react-scripts test --env=jsdom", 40 | "eject": "react-scripts eject",<% if (prettier) { %> 41 | "pretty": 42 | "prettier --write \"{{public,src}/**/*.{css,scss,js,json},package.json}\"", 43 | "precommit": "lint-staged",<% } %> 44 | "electron-dev": "<%- electrondev %>", 45 | "electron-pack": "build --em.main=build/electron.js --win --ia32 -c electron-builder.yml", 46 | <% if (sass) { %>"preelectron-pack": "yarn build && yarn build-css"<% } %><% if (!sass) { %>"preelectron-pack": "yarn build"<% } %> 47 | }, 48 | "devDependencies": {<% if (prettier) { %> 49 | "prettier": "^1.7.4", 50 | "husky": "^0.14.3", 51 | "lint-staged": "^4.2.3",<% } %> 52 | "concurrently": "^3.5.0", 53 | "electron": "^1.7.5", 54 | "electron-builder": "^19.20.1", 55 | "wait-on": "^2.0.2" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/public/electron.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow, ipcMain} = require('electron'); 2 | const path = require('path'); 3 | const isDev = require('electron-is-dev'); 4 | <% if (autoupdate) { %> 5 | const {autoUpdater} = require("electron-updater"); 6 | <% } %> 7 | 8 | let mainWindow; 9 | 10 | function createWindow() { 11 | mainWindow = new BrowserWindow({width: 900, height: 680}); 12 | mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`); // load the react app 13 | mainWindow.on('closed', () => mainWindow = null); 14 | } 15 | <% if (autoupdate) { %> 16 | // when the app is loaded create a BrowserWindow and check for updates 17 | app.on('ready', function() { 18 | createWindow() 19 | if (!isDev) autoUpdater.checkForUpdates(); 20 | }); 21 | <% } %> 22 | <% if (!autoupdate) { %> 23 | app.on('ready', createWindow); 24 | <% } %> 25 | 26 | // on MacOS leave process running also with no windows 27 | app.on('window-all-closed', () => { 28 | if (process.platform !== 'darwin') { 29 | app.quit(); 30 | } 31 | }); 32 | 33 | // if there are no windows create one 34 | app.on('activate', () => { 35 | if (mainWindow === null) { 36 | createWindow(); 37 | } 38 | }); 39 | 40 | <% if (autoupdate) { %> 41 | // when the update has been downloaded and is ready to be installed, notify the BrowserWindow 42 | autoUpdater.on('update-downloaded', (info) => { 43 | mainWindow.webContents.send('updateReady') 44 | }); 45 | 46 | // when receiving a quitAndInstall signal, quit and install the new version ;) 47 | ipcMain.on("quitAndInstall", (event, arg) => { 48 | autoUpdater.quitAndInstall(); 49 | }) 50 | <% } %> 51 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaninAndrea/generator-react-electron/3d6cd704d1d8ce7fc4930305317ffd33e4b2f355/generators/app/templates/electron-react/public/favicon.ico -------------------------------------------------------------------------------- /generators/app/templates/electron-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "<%= name %>", 3 | "name": "<%= name %>", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | <% if (autoupdate) { %> 3 | const electron = window.require("electron") // little trick to import electron in react 4 | const ipcRenderer = electron.ipcRenderer 5 | <% } %> 6 | 7 | class App extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | <% if (autoupdate) { %> 12 | this.state = { 13 | updateReady:false 14 | } 15 | ipcRenderer.on('updateReady', (event, text) => { 16 | this.setState({updateReady:true}) 17 | }) 18 | <% } %> 19 | 20 | } 21 | 22 | render() { 23 | return
24 | Foobar 25 | <% if (autoupdate) { %> 26 | 27 | <% } %> 28 |
29 | } 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import registerServiceWorker from './utilities/registerServiceWorker'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | registerServiceWorker(); 8 | -------------------------------------------------------------------------------- /generators/app/templates/electron-react/src/utilities/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-react-electron", 3 | "version": "1.3.0", 4 | "description": "Creates an new electron+react app with all the development and testing already wired for you", 5 | "files": [ 6 | "generators" 7 | ], 8 | "keywords": [ 9 | "yeoman-generator", 10 | "electron", 11 | "react" 12 | ], 13 | "dependencies": { 14 | "yeoman-generator": "^1.0.0" 15 | }, 16 | "author": "ZaninAndrea", 17 | "repository": "https://github.com/ZaninAndrea/generator-react-electron", 18 | "bugs": { 19 | "url": "https://github.com/ZaninAndrea/generator-react-electron/issues" 20 | }, 21 | "homepage": "https://github.com/ZaninAndrea/generator-react-electron#readme", 22 | "license": "ISC" 23 | } 24 | --------------------------------------------------------------------------------