├── app ├── utils │ └── .gitkeep ├── app.icns ├── yarn.lock ├── components │ ├── Home.css │ ├── Home.js │ ├── Counter.css │ └── Counter.js ├── store │ ├── configureStore.js │ ├── configureStore.prod.js │ └── configureStore.dev.js ├── reducers │ ├── index.js │ └── counter.js ├── containers │ ├── HomePage.js │ ├── App.js │ ├── CounterPage.js │ └── Root.js ├── routes.js ├── package.json ├── index.js ├── app.global.css ├── .eslintrc ├── actions │ └── counter.js ├── app.html ├── main.dev.js └── menu.js ├── internals ├── mocks │ └── fileMock.js ├── flow │ ├── WebpackAsset.js.flow │ └── CSSModule.js.flow ├── img │ ├── js.png │ ├── npm.png │ ├── flow.png │ ├── jest.png │ ├── react.png │ ├── redux.png │ ├── yarn.png │ ├── eslint.png │ ├── webpack.png │ ├── js-padded.png │ ├── eslint-padded.png │ ├── flow-padded.png │ ├── jest-padded.png │ ├── react-padded.png │ ├── react-router.png │ ├── redux-padded.png │ ├── yarn-padded.png │ ├── flow-padded-90.png │ ├── jest-padded-90.png │ ├── react-padded-90.png │ ├── redux-padded-90.png │ ├── webpack-padded.png │ ├── yarn-padded-90.png │ ├── eslint-padded-90.png │ ├── webpack-padded-90.png │ ├── react-router-padded.png │ └── react-router-padded-90.png └── scripts │ ├── RunTests.js │ ├── CheckNodeEnv.js │ ├── CheckPortInUse.js │ ├── ElectronRebuild.js │ ├── CheckBuiltsExist.js │ └── CheckNativeDep.js ├── .stylelintrc ├── README.md ├── resources ├── icon.ico ├── icon.png ├── icon.icns └── icons │ ├── 16x16.png │ ├── 24x24.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 96x96.png │ ├── 128x128.png │ ├── 256x256.png │ ├── 512x512.png │ └── 1024x1024.png ├── flow-typed └── module_vx.x.x.js ├── .gitattributes ├── webpack.config.eslint.js ├── test ├── example.js ├── .eslintrc ├── actions │ ├── __snapshots__ │ │ └── counter.spec.js.snap │ └── counter.spec.js ├── reducers │ ├── __snapshots__ │ │ └── counter.spec.js.snap │ └── counter.spec.js ├── components │ ├── __snapshots__ │ │ └── Counter.spec.js.snap │ └── Counter.spec.js ├── containers │ └── CounterPage.spec.js └── e2e │ └── e2e.spec.js ├── .editorconfig ├── .vscode └── settings.json ├── .babelrc ├── appveyor.yml ├── .travis.yml ├── .flowconfig ├── .gitignore ├── .dockerignore ├── .eslintignore ├── LICENSE ├── webpack.config.base.js ├── .eslintrc ├── webpack.config.main.prod.js ├── webpack.config.renderer.prod.js ├── webpack.config.renderer.dev.dll.js ├── webpack.config.renderer.dev.js ├── package.json └── CHANGELOG.md /app/utils/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internals/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flow 2 | A Kanban interface for managing debate flows 3 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/app/app.icns -------------------------------------------------------------------------------- /internals/flow/WebpackAsset.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare export default string 3 | -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icon.png -------------------------------------------------------------------------------- /internals/img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/js.png -------------------------------------------------------------------------------- /internals/img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/npm.png -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icon.icns -------------------------------------------------------------------------------- /internals/img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/flow.png -------------------------------------------------------------------------------- /internals/img/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/jest.png -------------------------------------------------------------------------------- /internals/img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/react.png -------------------------------------------------------------------------------- /internals/img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/redux.png -------------------------------------------------------------------------------- /internals/img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/yarn.png -------------------------------------------------------------------------------- /flow-typed/module_vx.x.x.js: -------------------------------------------------------------------------------- 1 | declare module 'module' { 2 | declare module.exports: any; 3 | } 4 | -------------------------------------------------------------------------------- /internals/flow/CSSModule.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare export default { [key: string]: string } -------------------------------------------------------------------------------- /internals/img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/eslint.png -------------------------------------------------------------------------------- /internals/img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/webpack.png -------------------------------------------------------------------------------- /resources/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/16x16.png -------------------------------------------------------------------------------- /resources/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/24x24.png -------------------------------------------------------------------------------- /resources/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/32x32.png -------------------------------------------------------------------------------- /resources/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/48x48.png -------------------------------------------------------------------------------- /resources/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/64x64.png -------------------------------------------------------------------------------- /resources/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/96x96.png -------------------------------------------------------------------------------- /internals/img/js-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/js-padded.png -------------------------------------------------------------------------------- /resources/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/128x128.png -------------------------------------------------------------------------------- /resources/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/256x256.png -------------------------------------------------------------------------------- /resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/512x512.png -------------------------------------------------------------------------------- /internals/img/eslint-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/eslint-padded.png -------------------------------------------------------------------------------- /internals/img/flow-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/flow-padded.png -------------------------------------------------------------------------------- /internals/img/jest-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/jest-padded.png -------------------------------------------------------------------------------- /internals/img/react-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/react-padded.png -------------------------------------------------------------------------------- /internals/img/react-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/react-router.png -------------------------------------------------------------------------------- /internals/img/redux-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/redux-padded.png -------------------------------------------------------------------------------- /internals/img/yarn-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/yarn-padded.png -------------------------------------------------------------------------------- /resources/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/resources/icons/1024x1024.png -------------------------------------------------------------------------------- /internals/img/flow-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/flow-padded-90.png -------------------------------------------------------------------------------- /internals/img/jest-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/jest-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/react-padded-90.png -------------------------------------------------------------------------------- /internals/img/redux-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/redux-padded-90.png -------------------------------------------------------------------------------- /internals/img/webpack-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/webpack-padded.png -------------------------------------------------------------------------------- /internals/img/yarn-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/yarn-padded-90.png -------------------------------------------------------------------------------- /app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /internals/img/eslint-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/eslint-padded-90.png -------------------------------------------------------------------------------- /internals/img/webpack-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/webpack-padded-90.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.jpeg binary 5 | *.ico binary 6 | *.icns binary 7 | -------------------------------------------------------------------------------- /internals/img/react-router-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/react-router-padded.png -------------------------------------------------------------------------------- /webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | 3 | module.exports = require('./webpack.config.renderer.dev'); 4 | -------------------------------------------------------------------------------- /internals/img/react-router-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sup/flow/master/internals/img/react-router-padded-90.png -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | describe('description', () => { 2 | it('should have description', () => { 3 | expect(1 + 2).toBe(3); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /app/components/Home.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | top: 30%; 4 | left: 10px; 5 | text-align: center; 6 | } 7 | 8 | .container h2 { 9 | font-size: 5rem; 10 | } 11 | 12 | .container a { 13 | font-size: 1.4rem; 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest/globals": true 4 | }, 5 | "plugins": [ 6 | "jest" 7 | ], 8 | "rules": { 9 | "jest/no-disabled-tests": "warn", 10 | "jest/no-focused-tests": "error", 11 | "jest/no-identical-title": "error" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | if (process.env.NODE_ENV === 'production') { 3 | module.exports = require('./configureStore.prod'); // eslint-disable-line global-require 4 | } else { 5 | module.exports = require('./configureStore.dev'); // eslint-disable-line global-require 6 | } 7 | -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { combineReducers } from 'redux'; 3 | import { routerReducer as router } from 'react-router-redux'; 4 | import counter from './counter'; 5 | 6 | const rootReducer = combineReducers({ 7 | counter, 8 | router, 9 | }); 10 | 11 | export default rootReducer; 12 | -------------------------------------------------------------------------------- /app/containers/HomePage.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import Home from '../components/Home'; 4 | 5 | type Props = {}; 6 | 7 | export default class HomePage extends Component { 8 | props: Props; 9 | 10 | render() { 11 | return ( 12 | 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/containers/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | 4 | type Props = { 5 | children: React.Node 6 | }; 7 | 8 | export default class App extends React.Component { 9 | props: Props; 10 | 11 | render() { 12 | return ( 13 |
14 | {this.props.children} 15 |
16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/actions/__snapshots__/counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`actions should decrement should create decrement action 1`] = ` 4 | Object { 5 | "type": "DECREMENT_COUNTER", 6 | } 7 | `; 8 | 9 | exports[`actions should increment should create increment action 1`] = ` 10 | Object { 11 | "type": "INCREMENT_COUNTER", 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /test/reducers/__snapshots__/counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`reducers counter should handle DECREMENT_COUNTER 1`] = `0`; 4 | 5 | exports[`reducers counter should handle INCREMENT_COUNTER 1`] = `2`; 6 | 7 | exports[`reducers counter should handle initial state 1`] = `0`; 8 | 9 | exports[`reducers counter should handle unknown action type 1`] = `1`; 10 | -------------------------------------------------------------------------------- /internals/scripts/RunTests.js: -------------------------------------------------------------------------------- 1 | import spawn from 'cross-spawn'; 2 | import path from 'path'; 3 | 4 | const pattern = process.argv[2] === 'e2e' 5 | ? 'test/e2e/.+\\.spec\\.js' 6 | : 'test/(?!e2e/)[^/]+/.+\\.spec\\.js$'; 7 | 8 | const result = spawn.sync( 9 | path.normalize('./node_modules/.bin/jest'), 10 | [pattern, ...process.argv.slice(2)], 11 | { stdio: 'inherit' } 12 | ); 13 | 14 | process.exit(result.status); 15 | -------------------------------------------------------------------------------- /internals/scripts/CheckNodeEnv.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk'; 3 | 4 | export default function CheckNodeEnv(expectedEnv: string) { 5 | if (!expectedEnv) { 6 | throw new Error('"expectedEnv" not set'); 7 | } 8 | 9 | if (process.env.NODE_ENV !== expectedEnv) { 10 | console.log(chalk.whiteBright.bgRed.bold(`"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`)); 11 | process.exit(2); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | /* eslint flowtype-errors/show-errors: 0 */ 2 | import React from 'react'; 3 | import { Switch, Route } from 'react-router'; 4 | import App from './containers/App'; 5 | import HomePage from './containers/HomePage'; 6 | import CounterPage from './containers/CounterPage'; 7 | 8 | export default () => ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /app/containers/CounterPage.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import Counter from '../components/Counter'; 4 | import * as CounterActions from '../actions/counter'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | counter: state.counter 9 | }; 10 | } 11 | 12 | function mapDispatchToProps(dispatch) { 13 | return bindActionCreators(CounterActions, dispatch); 14 | } 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(Counter); 17 | -------------------------------------------------------------------------------- /app/reducers/counter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter'; 3 | 4 | export type counterStateType = { 5 | +counter: number 6 | }; 7 | 8 | type actionType = { 9 | +type: string 10 | }; 11 | 12 | export default function counter(state: number = 0, action: actionType) { 13 | switch (action.type) { 14 | case INCREMENT_COUNTER: 15 | return state + 1; 16 | case DECREMENT_COUNTER: 17 | return state - 1; 18 | default: 19 | return state; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | "flow.useNPMPackagedFlow": true, 4 | "search.exclude": { 5 | ".git": true, 6 | ".eslintcache": true, 7 | "app/dist": true, 8 | "app/main.prod.js": true, 9 | "app/main.prod.js.map": true, 10 | "bower_components": true, 11 | "dll": true, 12 | "flow-typed": true, 13 | "release": true, 14 | "node_modules": true, 15 | "npm-debug.log.*": true, 16 | "test/**/__snapshots__": true, 17 | "yarn.lock": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/components/Home.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | import styles from './Home.css'; 5 | 6 | type Props = {}; 7 | 8 | export default class Home extends Component { 9 | props: Props; 10 | 11 | render() { 12 | return ( 13 |
14 |
15 |

Home

16 | to Counter 17 |
18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internals/scripts/CheckPortInUse.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk'; 3 | import detectPort from 'detect-port'; 4 | 5 | (function CheckPortInUse() { 6 | const port: string = process.env.PORT || '1212'; 7 | 8 | detectPort(port, (err: ?Error, availablePort: number) => { 9 | if (port !== String(availablePort)) { 10 | throw new Error(chalk.whiteBright.bgRed.bold(`Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm run dev`)); 11 | } else { 12 | process.exit(0); 13 | } 14 | }); 15 | }()); 16 | -------------------------------------------------------------------------------- /app/containers/Root.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { Provider } from 'react-redux'; 4 | import { ConnectedRouter } from 'react-router-redux'; 5 | import Routes from '../routes'; 6 | 7 | type Props = { 8 | store: {}, 9 | history: {} 10 | }; 11 | 12 | export default class Root extends Component { 13 | render() { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { "node": 7 }, 5 | "useBuiltIns": true 6 | }], 7 | "stage-0", 8 | "react" 9 | ], 10 | "plugins": ["add-module-exports"], 11 | "env": { 12 | "production": { 13 | "presets": ["react-optimize"], 14 | "plugins": ["dev-expression"] 15 | }, 16 | "development": { 17 | "plugins": [ 18 | "transform-class-properties", 19 | "transform-es2015-classes", 20 | ["flow-runtime", { 21 | "assert": true, 22 | "annotate": true 23 | }] 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: unstable 2 | 3 | environment: 4 | matrix: 5 | - nodejs_version: 8 6 | - nodejs_version: 7 7 | 8 | cache: 9 | - "%LOCALAPPDATA%/Yarn" 10 | - node_modules -> package.json 11 | - app/node_modules -> app/package.json 12 | 13 | matrix: 14 | fast_finish: true 15 | 16 | build: off 17 | 18 | version: '{build}' 19 | 20 | shallow_clone: true 21 | 22 | clone_depth: 1 23 | 24 | install: 25 | - ps: Install-Product node $env:nodejs_version 26 | - set CI=true 27 | - yarn 28 | - cd app && yarn 29 | 30 | test_script: 31 | - node --version 32 | - yarn lint 33 | - yarn package 34 | - yarn test 35 | - yarn test-e2e 36 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "productName": "electron-react-boilerplate", 4 | "version": "1.0.0", 5 | "description": "Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development", 6 | "main": "./main.prod.js", 7 | "author": { 8 | "name": "C. T. Lin", 9 | "email": "chentsulin@gmail.com", 10 | "url": "https://github.com/chentsulin" 11 | }, 12 | "scripts": { 13 | "electron-rebuild": "node -r babel-register ../internals/scripts/ElectronRebuild.js", 14 | "postinstall": "npm run electron-rebuild" 15 | }, 16 | "license": "MIT", 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /app/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import thunk from 'redux-thunk'; 4 | import { createBrowserHistory } from 'history'; 5 | import { routerMiddleware } from 'react-router-redux'; 6 | import rootReducer from '../reducers'; 7 | import type { counterStateType } from '../reducers/counter'; 8 | 9 | const history = createBrowserHistory(); 10 | const router = routerMiddleware(history); 11 | const enhancer = applyMiddleware(thunk, router); 12 | 13 | function configureStore(initialState?: counterStateType) { 14 | return createStore(rootReducer, initialState, enhancer); 15 | } 16 | 17 | export default { configureStore, history }; 18 | -------------------------------------------------------------------------------- /app/components/Counter.css: -------------------------------------------------------------------------------- 1 | .backButton { 2 | position: absolute; 3 | } 4 | 5 | .counter { 6 | position: absolute; 7 | top: 30%; 8 | left: 45%; 9 | font-size: 10rem; 10 | font-weight: bold; 11 | letter-spacing: -0.025em; 12 | } 13 | 14 | .btnGroup { 15 | position: relative; 16 | top: 500px; 17 | width: 480px; 18 | margin: 0 auto; 19 | } 20 | 21 | .btn { 22 | font-size: 1.6rem; 23 | font-weight: bold; 24 | background-color: #fff; 25 | border-radius: 50%; 26 | margin: 10px; 27 | width: 100px; 28 | height: 100px; 29 | opacity: 0.7; 30 | cursor: pointer; 31 | font-family: Arial, Helvetica, Helvetica Neue; 32 | } 33 | 34 | .btn:hover { 35 | color: white; 36 | background-color: rgba(0, 0, 0, 0.5); 37 | } 38 | -------------------------------------------------------------------------------- /internals/scripts/ElectronRebuild.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import path from 'path'; 3 | import { execSync } from 'child_process'; 4 | import fs from 'fs'; 5 | import dependencies from '../../app/package.json'; 6 | 7 | const nodeModulesPath = 8 | path.join(__dirname, '..', '..', 'app', 'node_modules'); 9 | 10 | if (Object.keys(dependencies || {}).length > 0 && fs.existsSync(nodeModulesPath)) { 11 | const electronRebuildCmd = 12 | '../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .'; 13 | 14 | const cmd = process.platform === 'win32' 15 | ? electronRebuildCmd.replace(/\//g, '\\') 16 | : electronRebuildCmd; 17 | 18 | execSync(cmd, { 19 | cwd: path.join(__dirname, '..', '..', 'app') 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test/reducers/counter.spec.js: -------------------------------------------------------------------------------- 1 | import counter from '../../app/reducers/counter'; 2 | import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../../app/actions/counter'; 3 | 4 | describe('reducers', () => { 5 | describe('counter', () => { 6 | it('should handle initial state', () => { 7 | expect(counter(undefined, {})).toMatchSnapshot(); 8 | }); 9 | 10 | it('should handle INCREMENT_COUNTER', () => { 11 | expect(counter(1, { type: INCREMENT_COUNTER })).toMatchSnapshot(); 12 | }); 13 | 14 | it('should handle DECREMENT_COUNTER', () => { 15 | expect(counter(1, { type: DECREMENT_COUNTER })).toMatchSnapshot(); 16 | }); 17 | 18 | it('should handle unknown action type', () => { 19 | expect(counter(1, { type: 'unknown' })).toMatchSnapshot(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /internals/scripts/CheckBuiltsExist.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // Check if the renderer and main bundles are built 3 | import path from 'path'; 4 | import chalk from 'chalk'; 5 | import fs from 'fs'; 6 | 7 | function CheckBuildsExist() { 8 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js'); 9 | const rendererPath = path.join(__dirname, '..', '..', 'app', 'dist', 'renderer.prod.js'); 10 | 11 | if (!fs.existsSync(mainPath)) { 12 | throw new Error(chalk.whiteBright.bgRed.bold('The main process is not built yet. Build it by running "npm run build-main"')); 13 | } 14 | 15 | if (!fs.existsSync(rendererPath)) { 16 | throw new Error(chalk.whiteBright.bgRed.bold('The renderer process is not built yet. Build it by running "npm run build-renderer"')); 17 | } 18 | } 19 | 20 | CheckBuildsExist(); 21 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import Root from './containers/Root'; 5 | import { configureStore, history } from './store/configureStore'; 6 | import './app.global.css'; 7 | 8 | const store = configureStore(); 9 | 10 | render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | if (module.hot) { 18 | module.hot.accept('./containers/Root', () => { 19 | const NextRoot = require('./containers/Root'); // eslint-disable-line global-require 20 | render( 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 8 7 | - 7 8 | 9 | cache: 10 | yarn: true 11 | directories: 12 | - node_modules 13 | - app/node_modules 14 | 15 | addons: 16 | apt: 17 | sources: 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - g++-4.8 21 | - icnsutils 22 | - graphicsmagick 23 | - xz-utils 24 | - xorriso 25 | 26 | install: 27 | - export CXX="g++-4.8" 28 | - yarn 29 | - cd app && yarn && cd .. 30 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 31 | 32 | before_script: 33 | - export DISPLAY=:99.0 34 | - sh -e /etc/init.d/xvfb start & 35 | - sleep 3 36 | 37 | script: 38 | - node --version 39 | - yarn lint 40 | - yarn package 41 | - yarn test 42 | - yarn test-e2e 43 | -------------------------------------------------------------------------------- /app/app.global.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules 3 | * See https://github.com/webpack-contrib/sass-loader#imports 4 | */ 5 | @import "~font-awesome/css/font-awesome.css"; 6 | 7 | body { 8 | position: relative; 9 | color: white; 10 | height: 100vh; 11 | background-color: #232c39; 12 | background-image: linear-gradient(45deg, rgba(0, 216, 255, 0.5) 10%, rgba(0, 1, 127, 0.7)); 13 | font-family: Arial, Helvetica, Helvetica Neue, serif; 14 | overflow-y: hidden; 15 | } 16 | 17 | h2 { 18 | margin: 0; 19 | font-size: 2.25rem; 20 | font-weight: bold; 21 | letter-spacing: -0.025em; 22 | color: #fff; 23 | } 24 | 25 | p { 26 | font-size: 24px; 27 | } 28 | 29 | li { 30 | list-style: none; 31 | } 32 | 33 | a { 34 | color: white; 35 | opacity: 0.75; 36 | text-decoration: none; 37 | } 38 | 39 | a:hover { 40 | opacity: 1; 41 | text-decoration: none; 42 | cursor: pointer; 43 | } 44 | -------------------------------------------------------------------------------- /app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "flowtype/boolean-style": ["error", "boolean"], 4 | "flowtype/define-flow-type": "warn", 5 | "flowtype/delimiter-dangle": ["error", "never"], 6 | "flowtype/generic-spacing": ["error", "never"], 7 | "flowtype/no-primitive-constructor-types": "error", 8 | "flowtype/no-weak-types": "warn", 9 | "flowtype/object-type-delimiter": ["error", "comma"], 10 | "flowtype/require-parameter-type": "off", 11 | "flowtype/require-return-type": "off", 12 | "flowtype/require-valid-file-annotation": "off", 13 | "flowtype/semi": ["error", "always"], 14 | "flowtype/space-after-type-colon": ["error", "always"], 15 | "flowtype/space-before-generic-bracket": ["error", "never"], 16 | "flowtype/space-before-type-colon": ["error", "never"], 17 | "flowtype/union-intersection-spacing": ["error", "always"], 18 | "flowtype/use-flow-type": "error", 19 | "flowtype/valid-syntax": "error" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/actions/counter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { counterStateType } from '../reducers/counter'; 3 | 4 | type actionType = { 5 | +type: string 6 | }; 7 | 8 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 9 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; 10 | 11 | export function increment() { 12 | return { 13 | type: INCREMENT_COUNTER 14 | }; 15 | } 16 | 17 | export function decrement() { 18 | return { 19 | type: DECREMENT_COUNTER 20 | }; 21 | } 22 | 23 | export function incrementIfOdd() { 24 | return (dispatch: (action: actionType) => void, getState: () => counterStateType) => { 25 | const { counter } = getState(); 26 | 27 | if (counter % 2 === 0) { 28 | return; 29 | } 30 | 31 | dispatch(increment()); 32 | }; 33 | } 34 | 35 | export function incrementAsync(delay: number = 1000) { 36 | return (dispatch: (action: actionType) => void) => { 37 | setTimeout(() => { 38 | dispatch(increment()); 39 | }, delay); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/* 3 | /app/main.prod.js 4 | /app/main.prod.js.map 5 | /app/dist/.* 6 | /resources/.* 7 | /release/.* 8 | /dll/.* 9 | /release/.* 10 | /git/.* 11 | 12 | [include] 13 | 14 | [libs] 15 | 16 | [options] 17 | esproposal.class_static_fields=enable 18 | esproposal.class_instance_fields=enable 19 | esproposal.export_star_as=enable 20 | module.name_mapper.extension='css' -> '/internals/flow/CSSModule.js.flow' 21 | module.name_mapper.extension='styl' -> '/internals/flow/CSSModule.js.flow' 22 | module.name_mapper.extension='scss' -> '/internals/flow/CSSModule.js.flow' 23 | module.name_mapper.extension='png' -> '/internals/flow/WebpackAsset.js.flow' 24 | module.name_mapper.extension='jpg' -> '/internals/flow/WebpackAsset.js.flow' 25 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe 26 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue 27 | -------------------------------------------------------------------------------- /.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 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 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 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | .*.dockerfile -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 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 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | __snapshots__ 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Charles Lai 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 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { dependencies as externals } from './app/package.json'; 8 | 9 | export default { 10 | externals: Object.keys(externals || {}), 11 | 12 | module: { 13 | rules: [{ 14 | test: /\.jsx?$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: 'babel-loader', 18 | options: { 19 | cacheDirectory: true 20 | } 21 | } 22 | }] 23 | }, 24 | 25 | output: { 26 | path: path.join(__dirname, 'app'), 27 | // https://github.com/webpack/webpack/issues/1114 28 | libraryTarget: 'commonjs2' 29 | }, 30 | 31 | /** 32 | * Determine the array of extensions that should be used to resolve modules. 33 | */ 34 | resolve: { 35 | extensions: ['.js', '.jsx', '.json'], 36 | modules: [ 37 | path.join(__dirname, 'app'), 38 | 'node_modules', 39 | ], 40 | }, 41 | 42 | plugins: [ 43 | new webpack.EnvironmentPlugin({ 44 | NODE_ENV: 'production' 45 | }), 46 | 47 | new webpack.NamedModulesPlugin(), 48 | ], 49 | }; 50 | -------------------------------------------------------------------------------- /test/components/__snapshots__/Counter.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Counter component should match exact snapshot 1`] = ` 4 |
5 |
6 |
10 | 14 | 17 | 18 |
19 |
23 | 1 24 |
25 |
28 | 37 | 46 | 53 | 60 |
61 |
62 |
63 | `; 64 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "allowImportExportEverywhere": true 6 | }, 7 | "extends": "airbnb", 8 | "env": { 9 | "browser": true, 10 | "node": true 11 | }, 12 | "rules": { 13 | "arrow-parens": ["off"], 14 | "compat/compat": "error", 15 | "consistent-return": "off", 16 | "comma-dangle": "off", 17 | "generator-star-spacing": "off", 18 | "import/no-unresolved": "error", 19 | "import/no-extraneous-dependencies": "off", 20 | "jsx-a11y/anchor-is-valid": "off", 21 | "no-console": "off", 22 | "no-use-before-define": "off", 23 | "no-multi-assign": "off", 24 | "promise/param-names": "error", 25 | "promise/always-return": "error", 26 | "promise/catch-or-return": "error", 27 | "promise/no-native": "off", 28 | "react/sort-comp": ["error", { 29 | "order": ["type-annotations", "static-methods", "lifecycle", "everything-else", "render"] 30 | }], 31 | "react/jsx-no-bind": "off", 32 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }], 33 | "react/prefer-stateless-function": "off" 34 | }, 35 | "plugins": [ 36 | "flowtype", 37 | "import", 38 | "promise", 39 | "compat", 40 | "react" 41 | ], 42 | "settings": { 43 | "import/resolver": { 44 | "webpack": { 45 | "config": "webpack.config.eslint.js" 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Electron React! 6 | 17 | 18 | 19 |
20 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /test/actions/counter.spec.js: -------------------------------------------------------------------------------- 1 | import { spy } from 'sinon'; 2 | import * as actions from '../../app/actions/counter'; 3 | 4 | describe('actions', () => { 5 | it('should increment should create increment action', () => { 6 | expect(actions.increment()).toMatchSnapshot(); 7 | }); 8 | 9 | it('should decrement should create decrement action', () => { 10 | expect(actions.decrement()).toMatchSnapshot(); 11 | }); 12 | 13 | it('should incrementIfOdd should create increment action', () => { 14 | const fn = actions.incrementIfOdd(); 15 | expect(fn).toBeInstanceOf(Function); 16 | const dispatch = spy(); 17 | const getState = () => ({ counter: 1 }); 18 | fn(dispatch, getState); 19 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true); 20 | }); 21 | 22 | it('should incrementIfOdd shouldnt create increment action if counter is even', () => { 23 | const fn = actions.incrementIfOdd(); 24 | const dispatch = spy(); 25 | const getState = () => ({ counter: 2 }); 26 | fn(dispatch, getState); 27 | expect(dispatch.called).toBe(false); 28 | }); 29 | 30 | // There's no nice way to test this at the moment... 31 | it('should incrementAsync', done => { 32 | const fn = actions.incrementAsync(1); 33 | expect(fn).toBeInstanceOf(Function); 34 | const dispatch = spy(); 35 | fn(dispatch); 36 | setTimeout(() => { 37 | expect(dispatch.calledWith({ type: actions.INCREMENT_COUNTER })).toBe(true); 38 | done(); 39 | }, 5); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /app/components/Counter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | import styles from './Counter.css'; 5 | 6 | type Props = { 7 | increment: () => void, 8 | incrementIfOdd: () => void, 9 | incrementAsync: () => void, 10 | decrement: () => void, 11 | counter: number 12 | }; 13 | 14 | export default class Counter extends Component { 15 | props: Props; 16 | 17 | render() { 18 | const { 19 | increment, incrementIfOdd, incrementAsync, decrement, counter 20 | } = this.props; 21 | return ( 22 |
23 |
24 | 25 | 26 | 27 |
28 |
29 | {counter} 30 |
31 |
32 | 35 | 38 | 39 | 40 |
41 |
42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /webpack.config.main.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import merge from 'webpack-merge'; 7 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import baseConfig from './webpack.config.base'; 10 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 11 | 12 | CheckNodeEnv('production'); 13 | 14 | export default merge.smart(baseConfig, { 15 | devtool: 'source-map', 16 | 17 | target: 'electron-main', 18 | 19 | entry: './app/main.dev', 20 | 21 | output: { 22 | path: __dirname, 23 | filename: './app/main.prod.js' 24 | }, 25 | 26 | plugins: [ 27 | new UglifyJSPlugin({ 28 | parallel: true, 29 | sourceMap: true 30 | }), 31 | 32 | new BundleAnalyzerPlugin({ 33 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 34 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | */ 46 | new webpack.EnvironmentPlugin({ 47 | NODE_ENV: 'production', 48 | DEBUG_PROD: 'false' 49 | }) 50 | ], 51 | 52 | /** 53 | * Disables webpack processing of __dirname and __filename. 54 | * If you run the bundle in node.js it falls back to these values of node.js. 55 | * https://github.com/webpack/webpack/issues/2010 56 | */ 57 | node: { 58 | __dirname: false, 59 | __filename: false 60 | }, 61 | }); 62 | -------------------------------------------------------------------------------- /test/containers/CounterPage.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { mount } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import { Provider } from 'react-redux'; 5 | import { createBrowserHistory } from 'history'; 6 | import { ConnectedRouter } from 'react-router-redux'; 7 | import CounterPage from '../../app/containers/CounterPage'; 8 | import { configureStore } from '../../app/store/configureStore'; 9 | 10 | Enzyme.configure({ adapter: new Adapter() }); 11 | 12 | function setup(initialState) { 13 | const store = configureStore(initialState); 14 | const history = createBrowserHistory(); 15 | const provider = ( 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | const app = mount(provider); 23 | return { 24 | app, 25 | buttons: app.find('button'), 26 | p: app.find('.counter') 27 | }; 28 | } 29 | 30 | describe('containers', () => { 31 | describe('App', () => { 32 | it('should display initial count', () => { 33 | const { p } = setup(); 34 | expect(p.text()).toMatch(/^0$/); 35 | }); 36 | 37 | it('should display updated count after increment button click', () => { 38 | const { buttons, p } = setup(); 39 | buttons.at(0).simulate('click'); 40 | expect(p.text()).toMatch(/^1$/); 41 | }); 42 | 43 | it('should display updated count after descrement button click', () => { 44 | const { buttons, p } = setup(); 45 | buttons.at(1).simulate('click'); 46 | expect(p.text()).toMatch(/^-1$/); 47 | }); 48 | 49 | it('shouldnt change if even and if odd button clicked', () => { 50 | const { buttons, p } = setup(); 51 | buttons.at(2).simulate('click'); 52 | expect(p.text()).toMatch(/^0$/); 53 | }); 54 | 55 | it('should change if odd and if odd button clicked', () => { 56 | const { buttons, p } = setup({ counter: 1 }); 57 | buttons.at(2).simulate('click'); 58 | expect(p.text()).toMatch(/^2$/); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /internals/scripts/CheckNativeDep.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import fs from 'fs'; 3 | import chalk from 'chalk'; 4 | import { execSync } from 'child_process'; 5 | import { dependencies } from '../../package.json'; 6 | 7 | (() => { 8 | if (!dependencies) return; 9 | 10 | const dependenciesKeys = Object.keys(dependencies); 11 | const nativeDeps = 12 | fs.readdirSync('node_modules') 13 | .filter(folder => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 14 | 15 | try { 16 | // Find the reason for why the dependency is installed. If it is installed 17 | // because of a devDependency then that is okay. Warn when it is installed 18 | // because of a dependency 19 | const dependenciesObject = JSON.parse(execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()); 20 | const rootDependencies = Object.keys(dependenciesObject.dependencies); 21 | const filteredRootDependencies = rootDependencies 22 | .filter(rootDependency => dependenciesKeys.includes(rootDependency)); 23 | 24 | if (filteredRootDependencies.length > 0) { 25 | const plural = filteredRootDependencies.length > 1; 26 | console.log(` 27 | 28 | ${chalk.whiteBright.bgYellow.bold('Webpack does not work with native dependencies.')} 29 | ${chalk.bold(filteredRootDependencies.join(', '))} ${plural ? 'are native dependencies' : 'is a native dependency'} and should be installed inside of the "./app" folder. 30 | 31 | 32 | First uninstall the packages from "./package.json": 33 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 34 | 35 | ${chalk.bold('Then, instead of installing the package to the root "./package.json":')} 36 | ${chalk.whiteBright.bgRed.bold('npm install your-package --save')} 37 | 38 | ${chalk.bold('Install the package to "./app/package.json"')} 39 | ${chalk.whiteBright.bgGreen.bold('cd ./app && npm install your-package --save')} 40 | 41 | 42 | Read more about native dependencies at: 43 | ${chalk.bold('https://github.com/chentsulin/electron-react-boilerplate/wiki/Module-Structure----Two-package.json-Structure')} 44 | 45 | 46 | `); 47 | 48 | process.exit(1); 49 | } 50 | } catch (e) { 51 | console.log('Native dependencies could not be checked'); 52 | } 53 | })(); 54 | -------------------------------------------------------------------------------- /app/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { createHashHistory } from 'history'; 4 | import { routerMiddleware, routerActions } from 'react-router-redux'; 5 | import { createLogger } from 'redux-logger'; 6 | import rootReducer from '../reducers'; 7 | import * as counterActions from '../actions/counter'; 8 | import type { counterStateType } from '../reducers/counter'; 9 | 10 | const history = createHashHistory(); 11 | 12 | const configureStore = (initialState?: counterStateType) => { 13 | // Redux Configuration 14 | const middleware = []; 15 | const enhancers = []; 16 | 17 | // Thunk Middleware 18 | middleware.push(thunk); 19 | 20 | // Logging Middleware 21 | const logger = createLogger({ 22 | level: 'info', 23 | collapsed: true 24 | }); 25 | 26 | // Skip redux logs in console during the tests 27 | if (process.env.NODE_ENV !== 'test') { 28 | middleware.push(logger); 29 | } 30 | 31 | // Router Middleware 32 | const router = routerMiddleware(history); 33 | middleware.push(router); 34 | 35 | // Redux DevTools Configuration 36 | const actionCreators = { 37 | ...counterActions, 38 | ...routerActions, 39 | }; 40 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose 41 | /* eslint-disable no-underscore-dangle */ 42 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 43 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 44 | // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html 45 | actionCreators, 46 | }) 47 | : compose; 48 | /* eslint-enable no-underscore-dangle */ 49 | 50 | // Apply Middleware & Compose Enhancers 51 | enhancers.push(applyMiddleware(...middleware)); 52 | const enhancer = composeEnhancers(...enhancers); 53 | 54 | // Create Store 55 | const store = createStore(rootReducer, initialState, enhancer); 56 | 57 | if (module.hot) { 58 | module.hot.accept('../reducers', () => 59 | store.replaceReducer(require('../reducers'))); // eslint-disable-line global-require 60 | } 61 | 62 | return store; 63 | }; 64 | 65 | export default { configureStore, history }; 66 | -------------------------------------------------------------------------------- /test/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | import { spy } from 'sinon'; 2 | import React from 'react'; 3 | import Enzyme, { shallow } from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import { BrowserRouter as Router } from 'react-router-dom'; 6 | import renderer from 'react-test-renderer'; 7 | import Counter from '../../app/components/Counter'; 8 | 9 | Enzyme.configure({ adapter: new Adapter() }); 10 | 11 | function setup() { 12 | const actions = { 13 | increment: spy(), 14 | incrementIfOdd: spy(), 15 | incrementAsync: spy(), 16 | decrement: spy() 17 | }; 18 | const component = shallow(); 19 | return { 20 | component, 21 | actions, 22 | buttons: component.find('button'), 23 | p: component.find('.counter') 24 | }; 25 | } 26 | 27 | describe('Counter component', () => { 28 | it('should should display count', () => { 29 | const { p } = setup(); 30 | expect(p.text()).toMatch(/^1$/); 31 | }); 32 | 33 | it('should first button should call increment', () => { 34 | const { buttons, actions } = setup(); 35 | buttons.at(0).simulate('click'); 36 | expect(actions.increment.called).toBe(true); 37 | }); 38 | 39 | it('should match exact snapshot', () => { 40 | const { actions } = setup(); 41 | const counter = ( 42 |
43 | 44 | 45 | 46 |
47 | ); 48 | const tree = renderer 49 | .create(counter) 50 | .toJSON(); 51 | 52 | expect(tree).toMatchSnapshot(); 53 | }); 54 | 55 | it('should second button should call decrement', () => { 56 | const { buttons, actions } = setup(); 57 | buttons.at(1).simulate('click'); 58 | expect(actions.decrement.called).toBe(true); 59 | }); 60 | 61 | it('should third button should call incrementIfOdd', () => { 62 | const { buttons, actions } = setup(); 63 | buttons.at(2).simulate('click'); 64 | expect(actions.incrementIfOdd.called).toBe(true); 65 | }); 66 | 67 | it('should fourth button should call incrementAsync', () => { 68 | const { buttons, actions } = setup(); 69 | buttons.at(3).simulate('click'); 70 | expect(actions.incrementAsync.called).toBe(true); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /app/main.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, flowtype-errors/show-errors: 0 */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build-main`, this file is compiled to 9 | * `./app/main.prod.js` using webpack. This gives us some performance wins. 10 | * 11 | * @flow 12 | */ 13 | import { app, BrowserWindow } from 'electron'; 14 | import MenuBuilder from './menu'; 15 | 16 | let mainWindow = null; 17 | 18 | if (process.env.NODE_ENV === 'production') { 19 | const sourceMapSupport = require('source-map-support'); 20 | sourceMapSupport.install(); 21 | } 22 | 23 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { 24 | require('electron-debug')(); 25 | const path = require('path'); 26 | const p = path.join(__dirname, '..', 'app', 'node_modules'); 27 | require('module').globalPaths.push(p); 28 | } 29 | 30 | const installExtensions = async () => { 31 | const installer = require('electron-devtools-installer'); 32 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 33 | const extensions = [ 34 | 'REACT_DEVELOPER_TOOLS', 35 | 'REDUX_DEVTOOLS' 36 | ]; 37 | 38 | return Promise 39 | .all(extensions.map(name => installer.default(installer[name], forceDownload))) 40 | .catch(console.log); 41 | }; 42 | 43 | 44 | /** 45 | * Add event listeners... 46 | */ 47 | 48 | app.on('window-all-closed', () => { 49 | // Respect the OSX convention of having the application in memory even 50 | // after all windows have been closed 51 | if (process.platform !== 'darwin') { 52 | app.quit(); 53 | } 54 | }); 55 | 56 | 57 | app.on('ready', async () => { 58 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { 59 | await installExtensions(); 60 | } 61 | 62 | mainWindow = new BrowserWindow({ 63 | show: false, 64 | width: 1024, 65 | height: 728 66 | }); 67 | 68 | mainWindow.loadURL(`file://${__dirname}/app.html`); 69 | 70 | // @TODO: Use 'ready-to-show' event 71 | // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event 72 | mainWindow.webContents.on('did-finish-load', () => { 73 | if (!mainWindow) { 74 | throw new Error('"mainWindow" is not defined'); 75 | } 76 | mainWindow.show(); 77 | mainWindow.focus(); 78 | }); 79 | 80 | mainWindow.on('closed', () => { 81 | mainWindow = null; 82 | }); 83 | 84 | const menuBuilder = new MenuBuilder(mainWindow); 85 | menuBuilder.buildMenu(); 86 | }); 87 | -------------------------------------------------------------------------------- /test/e2e/e2e.spec.js: -------------------------------------------------------------------------------- 1 | import { Application } from 'spectron'; 2 | import electronPath from 'electron'; 3 | import path from 'path'; 4 | 5 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; 6 | 7 | const delay = time => new Promise(resolve => setTimeout(resolve, time)); 8 | 9 | describe('main window', function spec() { 10 | beforeAll(async () => { 11 | this.app = new Application({ 12 | path: electronPath, 13 | args: [path.join(__dirname, '..', '..', 'app')], 14 | }); 15 | 16 | return this.app.start(); 17 | }); 18 | 19 | afterAll(() => { 20 | if (this.app && this.app.isRunning()) { 21 | return this.app.stop(); 22 | } 23 | }); 24 | 25 | const findCounter = () => this.app.client.element('[data-tid="counter"]'); 26 | 27 | const findButtons = async () => { 28 | const { value } = await this.app.client.elements('[data-tclass="btn"]'); 29 | return value.map(btn => btn.ELEMENT); 30 | }; 31 | 32 | it('should open window', async () => { 33 | const { client, browserWindow } = this.app; 34 | 35 | await client.waitUntilWindowLoaded(); 36 | await delay(500); 37 | const title = await browserWindow.getTitle(); 38 | expect(title).toBe('Hello Electron React!'); 39 | }); 40 | 41 | it('should haven\'t any logs in console of main window', async () => { 42 | const { client } = this.app; 43 | const logs = await client.getRenderProcessLogs(); 44 | // Print renderer process logs 45 | logs.forEach(log => { 46 | console.log(log.message); 47 | console.log(log.source); 48 | console.log(log.level); 49 | }); 50 | expect(logs).toHaveLength(0); 51 | }); 52 | 53 | it('should to Counter with click "to Counter" link', async () => { 54 | const { client } = this.app; 55 | 56 | await client.click('[data-tid=container] > a'); 57 | expect(await findCounter().getText()).toBe('0'); 58 | }); 59 | 60 | it('should display updated count after increment button click', async () => { 61 | const { client } = this.app; 62 | 63 | const buttons = await findButtons(); 64 | await client.elementIdClick(buttons[0]); // + 65 | expect(await findCounter().getText()).toBe('1'); 66 | }); 67 | 68 | it('should display updated count after descrement button click', async () => { 69 | const { client } = this.app; 70 | 71 | const buttons = await findButtons(); 72 | await client.elementIdClick(buttons[1]); // - 73 | expect(await findCounter().getText()).toBe('0'); 74 | }); 75 | 76 | it('shouldnt change if even and if odd button clicked', async () => { 77 | const { client } = this.app; 78 | 79 | const buttons = await findButtons(); 80 | await client.elementIdClick(buttons[2]); // odd 81 | expect(await findCounter().getText()).toBe('0'); 82 | }); 83 | 84 | it('should change if odd and if odd button clicked', async () => { 85 | const { client } = this.app; 86 | 87 | const buttons = await findButtons(); 88 | await client.elementIdClick(buttons[0]); // + 89 | await client.elementIdClick(buttons[2]); // odd 90 | expect(await findCounter().getText()).toBe('2'); 91 | }); 92 | 93 | it('should change if async button clicked and a second later', async () => { 94 | const { client } = this.app; 95 | 96 | const buttons = await findButtons(); 97 | await client.elementIdClick(buttons[3]); // async 98 | expect(await findCounter().getText()).toBe('2'); 99 | await delay(1500); 100 | expect(await findCounter().getText()).toBe('3'); 101 | }); 102 | 103 | it('should back to home if back button clicked', async () => { 104 | const { client } = this.app; 105 | await client.element('[data-tid="backButton"] > a').click(); 106 | 107 | expect(await client.isExisting('[data-tid="container"]')).toBe(true); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /webpack.config.renderer.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import merge from 'webpack-merge'; 10 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin'; 11 | import baseConfig from './webpack.config.base'; 12 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 13 | 14 | CheckNodeEnv('production'); 15 | 16 | export default merge.smart(baseConfig, { 17 | devtool: 'source-map', 18 | 19 | target: 'electron-renderer', 20 | 21 | entry: './app/index', 22 | 23 | output: { 24 | path: path.join(__dirname, 'app/dist'), 25 | publicPath: './dist/', 26 | filename: 'renderer.prod.js' 27 | }, 28 | 29 | module: { 30 | rules: [ 31 | // Extract all .global.css to style.css as is 32 | { 33 | test: /\.global\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | publicPath: './', 36 | use: { 37 | loader: 'css-loader', 38 | options: { 39 | minimize: true, 40 | } 41 | }, 42 | fallback: 'style-loader', 43 | }) 44 | }, 45 | // Pipe other styles through css modules and append to style.css 46 | { 47 | test: /^((?!\.global).)*\.css$/, 48 | use: ExtractTextPlugin.extract({ 49 | use: { 50 | loader: 'css-loader', 51 | options: { 52 | modules: true, 53 | minimize: true, 54 | importLoaders: 1, 55 | localIdentName: '[name]__[local]__[hash:base64:5]', 56 | } 57 | } 58 | }), 59 | }, 60 | // Add SASS support - compile all .global.scss files and pipe it to style.css 61 | { 62 | test: /\.global\.(scss|sass)$/, 63 | use: ExtractTextPlugin.extract({ 64 | use: [ 65 | { 66 | loader: 'css-loader', 67 | options: { 68 | minimize: true, 69 | } 70 | }, 71 | { 72 | loader: 'sass-loader' 73 | } 74 | ], 75 | fallback: 'style-loader', 76 | }) 77 | }, 78 | // Add SASS support - compile all other .scss files and pipe it to style.css 79 | { 80 | test: /^((?!\.global).)*\.(scss|sass)$/, 81 | use: ExtractTextPlugin.extract({ 82 | use: [{ 83 | loader: 'css-loader', 84 | options: { 85 | modules: true, 86 | minimize: true, 87 | importLoaders: 1, 88 | localIdentName: '[name]__[local]__[hash:base64:5]', 89 | } 90 | }, 91 | { 92 | loader: 'sass-loader' 93 | }] 94 | }), 95 | }, 96 | // WOFF Font 97 | { 98 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 99 | use: { 100 | loader: 'url-loader', 101 | options: { 102 | limit: 10000, 103 | mimetype: 'application/font-woff', 104 | } 105 | }, 106 | }, 107 | // WOFF2 Font 108 | { 109 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 110 | use: { 111 | loader: 'url-loader', 112 | options: { 113 | limit: 10000, 114 | mimetype: 'application/font-woff', 115 | } 116 | } 117 | }, 118 | // TTF Font 119 | { 120 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 121 | use: { 122 | loader: 'url-loader', 123 | options: { 124 | limit: 10000, 125 | mimetype: 'application/octet-stream' 126 | } 127 | } 128 | }, 129 | // EOT Font 130 | { 131 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 132 | use: 'file-loader', 133 | }, 134 | // SVG Font 135 | { 136 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 137 | use: { 138 | loader: 'url-loader', 139 | options: { 140 | limit: 10000, 141 | mimetype: 'image/svg+xml', 142 | } 143 | } 144 | }, 145 | // Common Image Formats 146 | { 147 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 148 | use: 'url-loader', 149 | } 150 | ] 151 | }, 152 | 153 | plugins: [ 154 | /** 155 | * Create global constants which can be configured at compile time. 156 | * 157 | * Useful for allowing different behaviour between development builds and 158 | * release builds 159 | * 160 | * NODE_ENV should be production so that modules do not perform certain 161 | * development checks 162 | */ 163 | new webpack.EnvironmentPlugin({ 164 | NODE_ENV: 'production' 165 | }), 166 | 167 | new UglifyJSPlugin({ 168 | parallel: true, 169 | sourceMap: true 170 | }), 171 | 172 | new ExtractTextPlugin('style.css'), 173 | 174 | new BundleAnalyzerPlugin({ 175 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 176 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 177 | }), 178 | ], 179 | }); 180 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.dll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import merge from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import { dependencies } from './package.json'; 10 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 11 | 12 | CheckNodeEnv('development'); 13 | 14 | const dist = path.resolve(process.cwd(), 'dll'); 15 | 16 | export default merge.smart(baseConfig, { 17 | context: process.cwd(), 18 | 19 | devtool: 'eval', 20 | 21 | target: 'electron-renderer', 22 | 23 | externals: ['fsevents', 'crypto-browserify'], 24 | 25 | /** 26 | * Use `module` from `webpack.config.renderer.dev.js` 27 | */ 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.jsx?$/, 32 | exclude: /node_modules/, 33 | use: { 34 | loader: 'babel-loader', 35 | options: { 36 | cacheDirectory: true, 37 | plugins: [ 38 | // Here, we include babel plugins that are only required for the 39 | // renderer process. The 'transform-*' plugins must be included 40 | // before react-hot-loader/babel 41 | 'transform-class-properties', 42 | 'transform-es2015-classes', 43 | 'react-hot-loader/babel' 44 | ], 45 | } 46 | } 47 | }, 48 | { 49 | test: /\.global\.css$/, 50 | use: [ 51 | { 52 | loader: 'style-loader' 53 | }, 54 | { 55 | loader: 'css-loader', 56 | options: { 57 | sourceMap: true, 58 | }, 59 | } 60 | ] 61 | }, 62 | { 63 | test: /^((?!\.global).)*\.css$/, 64 | use: [ 65 | { 66 | loader: 'style-loader' 67 | }, 68 | { 69 | loader: 'css-loader', 70 | options: { 71 | modules: true, 72 | sourceMap: true, 73 | importLoaders: 1, 74 | localIdentName: '[name]__[local]__[hash:base64:5]', 75 | } 76 | }, 77 | ] 78 | }, 79 | // SASS support - compile all .global.scss files and pipe it to style.css 80 | { 81 | test: /\.global\.(scss|sass)$/, 82 | use: [ 83 | { 84 | loader: 'style-loader' 85 | }, 86 | { 87 | loader: 'css-loader', 88 | options: { 89 | sourceMap: true, 90 | }, 91 | }, 92 | { 93 | loader: 'sass-loader' 94 | } 95 | ] 96 | }, 97 | // SASS support - compile all other .scss files and pipe it to style.css 98 | { 99 | test: /^((?!\.global).)*\.(scss|sass)$/, 100 | use: [ 101 | { 102 | loader: 'style-loader' 103 | }, 104 | { 105 | loader: 'css-loader', 106 | options: { 107 | modules: true, 108 | sourceMap: true, 109 | importLoaders: 1, 110 | localIdentName: '[name]__[local]__[hash:base64:5]', 111 | } 112 | }, 113 | { 114 | loader: 'sass-loader' 115 | } 116 | ] 117 | }, 118 | // WOFF Font 119 | { 120 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 121 | use: { 122 | loader: 'url-loader', 123 | options: { 124 | limit: 10000, 125 | mimetype: 'application/font-woff', 126 | } 127 | }, 128 | }, 129 | // WOFF2 Font 130 | { 131 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 132 | use: { 133 | loader: 'url-loader', 134 | options: { 135 | limit: 10000, 136 | mimetype: 'application/font-woff', 137 | } 138 | } 139 | }, 140 | // TTF Font 141 | { 142 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 143 | use: { 144 | loader: 'url-loader', 145 | options: { 146 | limit: 10000, 147 | mimetype: 'application/octet-stream' 148 | } 149 | } 150 | }, 151 | // EOT Font 152 | { 153 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 154 | use: 'file-loader', 155 | }, 156 | // SVG Font 157 | { 158 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 159 | use: { 160 | loader: 'url-loader', 161 | options: { 162 | limit: 10000, 163 | mimetype: 'image/svg+xml', 164 | } 165 | } 166 | }, 167 | // Common Image Formats 168 | { 169 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 170 | use: 'url-loader', 171 | } 172 | ] 173 | }, 174 | 175 | entry: { 176 | renderer: ( 177 | Object 178 | .keys(dependencies || {}) 179 | .filter(dependency => dependency !== 'font-awesome') 180 | ) 181 | }, 182 | 183 | output: { 184 | library: 'renderer', 185 | path: dist, 186 | filename: '[name].dev.dll.js', 187 | libraryTarget: 'var' 188 | }, 189 | 190 | plugins: [ 191 | new webpack.DllPlugin({ 192 | path: path.join(dist, '[name].json'), 193 | name: '[name]', 194 | }), 195 | 196 | /** 197 | * Create global constants which can be configured at compile time. 198 | * 199 | * Useful for allowing different behaviour between development builds and 200 | * release builds 201 | * 202 | * NODE_ENV should be production so that modules do not perform certain 203 | * development checks 204 | */ 205 | new webpack.EnvironmentPlugin({ 206 | NODE_ENV: 'development' 207 | }), 208 | 209 | new webpack.LoaderOptionsPlugin({ 210 | debug: true, 211 | options: { 212 | context: path.resolve(process.cwd(), 'app'), 213 | output: { 214 | path: path.resolve(process.cwd(), 'dll'), 215 | }, 216 | }, 217 | }) 218 | ], 219 | }); 220 | -------------------------------------------------------------------------------- /app/menu.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { app, Menu, shell, BrowserWindow } from 'electron'; 3 | 4 | export default class MenuBuilder { 5 | mainWindow: BrowserWindow; 6 | 7 | constructor(mainWindow: BrowserWindow) { 8 | this.mainWindow = mainWindow; 9 | } 10 | 11 | buildMenu() { 12 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { 13 | this.setupDevelopmentEnvironment(); 14 | } 15 | 16 | const template = process.platform === 'darwin' 17 | ? this.buildDarwinTemplate() 18 | : this.buildDefaultTemplate(); 19 | 20 | const menu = Menu.buildFromTemplate(template); 21 | Menu.setApplicationMenu(menu); 22 | 23 | return menu; 24 | } 25 | 26 | setupDevelopmentEnvironment() { 27 | this.mainWindow.openDevTools(); 28 | this.mainWindow.webContents.on('context-menu', (e, props) => { 29 | const { x, y } = props; 30 | 31 | Menu 32 | .buildFromTemplate([{ 33 | label: 'Inspect element', 34 | click: () => { 35 | this.mainWindow.inspectElement(x, y); 36 | } 37 | }]) 38 | .popup(this.mainWindow); 39 | }); 40 | } 41 | 42 | buildDarwinTemplate() { 43 | const subMenuAbout = { 44 | label: 'Electron', 45 | submenu: [ 46 | { label: 'About ElectronReact', selector: 'orderFrontStandardAboutPanel:' }, 47 | { type: 'separator' }, 48 | { label: 'Services', submenu: [] }, 49 | { type: 'separator' }, 50 | { label: 'Hide ElectronReact', accelerator: 'Command+H', selector: 'hide:' }, 51 | { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }, 52 | { label: 'Show All', selector: 'unhideAllApplications:' }, 53 | { type: 'separator' }, 54 | { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit(); } } 55 | ] 56 | }; 57 | const subMenuEdit = { 58 | label: 'Edit', 59 | submenu: [ 60 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }, 61 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' }, 62 | { type: 'separator' }, 63 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }, 64 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }, 65 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }, 66 | { label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' } 67 | ] 68 | }; 69 | const subMenuViewDev = { 70 | label: 'View', 71 | submenu: [ 72 | { label: 'Reload', accelerator: 'Command+R', click: () => { this.mainWindow.webContents.reload(); } }, 73 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } }, 74 | { label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: () => { this.mainWindow.toggleDevTools(); } } 75 | ] 76 | }; 77 | const subMenuViewProd = { 78 | label: 'View', 79 | submenu: [ 80 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } } 81 | ] 82 | }; 83 | const subMenuWindow = { 84 | label: 'Window', 85 | submenu: [ 86 | { label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' }, 87 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 88 | { type: 'separator' }, 89 | { label: 'Bring All to Front', selector: 'arrangeInFront:' } 90 | ] 91 | }; 92 | const subMenuHelp = { 93 | label: 'Help', 94 | submenu: [ 95 | { label: 'Learn More', click() { shell.openExternal('http://electron.atom.io'); } }, 96 | { label: 'Documentation', click() { shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); } }, 97 | { label: 'Community Discussions', click() { shell.openExternal('https://discuss.atom.io/c/electron'); } }, 98 | { label: 'Search Issues', click() { shell.openExternal('https://github.com/atom/electron/issues'); } } 99 | ] 100 | }; 101 | 102 | const subMenuView = process.env.NODE_ENV === 'development' 103 | ? subMenuViewDev 104 | : subMenuViewProd; 105 | 106 | return [ 107 | subMenuAbout, 108 | subMenuEdit, 109 | subMenuView, 110 | subMenuWindow, 111 | subMenuHelp 112 | ]; 113 | } 114 | 115 | buildDefaultTemplate() { 116 | const templateDefault = [{ 117 | label: '&File', 118 | submenu: [{ 119 | label: '&Open', 120 | accelerator: 'Ctrl+O' 121 | }, { 122 | label: '&Close', 123 | accelerator: 'Ctrl+W', 124 | click: () => { 125 | this.mainWindow.close(); 126 | } 127 | }] 128 | }, { 129 | label: '&View', 130 | submenu: (process.env.NODE_ENV === 'development') ? [{ 131 | label: '&Reload', 132 | accelerator: 'Ctrl+R', 133 | click: () => { 134 | this.mainWindow.webContents.reload(); 135 | } 136 | }, { 137 | label: 'Toggle &Full Screen', 138 | accelerator: 'F11', 139 | click: () => { 140 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 141 | } 142 | }, { 143 | label: 'Toggle &Developer Tools', 144 | accelerator: 'Alt+Ctrl+I', 145 | click: () => { 146 | this.mainWindow.toggleDevTools(); 147 | } 148 | }] : [{ 149 | label: 'Toggle &Full Screen', 150 | accelerator: 'F11', 151 | click: () => { 152 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 153 | } 154 | }] 155 | }, { 156 | label: 'Help', 157 | submenu: [{ 158 | label: 'Learn More', 159 | click() { 160 | shell.openExternal('http://electron.atom.io'); 161 | } 162 | }, { 163 | label: 'Documentation', 164 | click() { 165 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 166 | } 167 | }, { 168 | label: 'Community Discussions', 169 | click() { 170 | shell.openExternal('https://discuss.atom.io/c/electron'); 171 | } 172 | }, { 173 | label: 'Search Issues', 174 | click() { 175 | shell.openExternal('https://github.com/atom/electron/issues'); 176 | } 177 | }] 178 | }]; 179 | 180 | return templateDefault; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, import/no-dynamic-require: 0 */ 2 | 3 | /** 4 | * Build config for development electron renderer process that uses 5 | * Hot-Module-Replacement 6 | * 7 | * https://webpack.js.org/concepts/hot-module-replacement/ 8 | */ 9 | 10 | import path from 'path'; 11 | import fs from 'fs'; 12 | import webpack from 'webpack'; 13 | import chalk from 'chalk'; 14 | import merge from 'webpack-merge'; 15 | import { spawn, execSync } from 'child_process'; 16 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 17 | import baseConfig from './webpack.config.base'; 18 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv'; 19 | 20 | CheckNodeEnv('development'); 21 | 22 | const port = process.env.PORT || 1212; 23 | const publicPath = `http://localhost:${port}/dist`; 24 | const dll = path.resolve(process.cwd(), 'dll'); 25 | const manifest = path.resolve(dll, 'renderer.json'); 26 | 27 | /** 28 | * Warn if the DLL is not built 29 | */ 30 | if (!(fs.existsSync(dll) && fs.existsSync(manifest))) { 31 | console.log(chalk.black.bgYellow.bold('The DLL files are missing. Sit back while we build them for you with "npm run build-dll"')); 32 | execSync('npm run build-dll'); 33 | } 34 | 35 | export default merge.smart(baseConfig, { 36 | devtool: 'inline-source-map', 37 | 38 | target: 'electron-renderer', 39 | 40 | entry: [ 41 | 'react-hot-loader/patch', 42 | `webpack-dev-server/client?http://localhost:${port}/`, 43 | 'webpack/hot/only-dev-server', 44 | path.join(__dirname, 'app/index.js'), 45 | ], 46 | 47 | output: { 48 | publicPath: `http://localhost:${port}/dist/`, 49 | filename: 'renderer.dev.js' 50 | }, 51 | 52 | module: { 53 | rules: [ 54 | { 55 | test: /\.jsx?$/, 56 | exclude: /node_modules/, 57 | use: { 58 | loader: 'babel-loader', 59 | options: { 60 | cacheDirectory: true, 61 | plugins: [ 62 | // Here, we include babel plugins that are only required for the 63 | // renderer process. The 'transform-*' plugins must be included 64 | // before react-hot-loader/babel 65 | 'transform-class-properties', 66 | 'transform-es2015-classes', 67 | 'react-hot-loader/babel' 68 | ], 69 | } 70 | } 71 | }, 72 | { 73 | test: /\.global\.css$/, 74 | use: [ 75 | { 76 | loader: 'style-loader' 77 | }, 78 | { 79 | loader: 'css-loader', 80 | options: { 81 | sourceMap: true, 82 | }, 83 | } 84 | ] 85 | }, 86 | { 87 | test: /^((?!\.global).)*\.css$/, 88 | use: [ 89 | { 90 | loader: 'style-loader' 91 | }, 92 | { 93 | loader: 'css-loader', 94 | options: { 95 | modules: true, 96 | sourceMap: true, 97 | importLoaders: 1, 98 | localIdentName: '[name]__[local]__[hash:base64:5]', 99 | } 100 | }, 101 | ] 102 | }, 103 | // SASS support - compile all .global.scss files and pipe it to style.css 104 | { 105 | test: /\.global\.(scss|sass)$/, 106 | use: [ 107 | { 108 | loader: 'style-loader' 109 | }, 110 | { 111 | loader: 'css-loader', 112 | options: { 113 | sourceMap: true, 114 | }, 115 | }, 116 | { 117 | loader: 'sass-loader' 118 | } 119 | ] 120 | }, 121 | // SASS support - compile all other .scss files and pipe it to style.css 122 | { 123 | test: /^((?!\.global).)*\.(scss|sass)$/, 124 | use: [ 125 | { 126 | loader: 'style-loader' 127 | }, 128 | { 129 | loader: 'css-loader', 130 | options: { 131 | modules: true, 132 | sourceMap: true, 133 | importLoaders: 1, 134 | localIdentName: '[name]__[local]__[hash:base64:5]', 135 | } 136 | }, 137 | { 138 | loader: 'sass-loader' 139 | } 140 | ] 141 | }, 142 | // WOFF Font 143 | { 144 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 145 | use: { 146 | loader: 'url-loader', 147 | options: { 148 | limit: 10000, 149 | mimetype: 'application/font-woff', 150 | } 151 | }, 152 | }, 153 | // WOFF2 Font 154 | { 155 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 156 | use: { 157 | loader: 'url-loader', 158 | options: { 159 | limit: 10000, 160 | mimetype: 'application/font-woff', 161 | } 162 | } 163 | }, 164 | // TTF Font 165 | { 166 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 167 | use: { 168 | loader: 'url-loader', 169 | options: { 170 | limit: 10000, 171 | mimetype: 'application/octet-stream' 172 | } 173 | } 174 | }, 175 | // EOT Font 176 | { 177 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 178 | use: 'file-loader', 179 | }, 180 | // SVG Font 181 | { 182 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 183 | use: { 184 | loader: 'url-loader', 185 | options: { 186 | limit: 10000, 187 | mimetype: 'image/svg+xml', 188 | } 189 | } 190 | }, 191 | // Common Image Formats 192 | { 193 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 194 | use: 'url-loader', 195 | } 196 | ] 197 | }, 198 | 199 | plugins: [ 200 | new webpack.DllReferencePlugin({ 201 | context: process.cwd(), 202 | manifest: require(manifest), 203 | sourceType: 'var', 204 | }), 205 | 206 | new webpack.HotModuleReplacementPlugin({ 207 | multiStep: true 208 | }), 209 | 210 | new webpack.NoEmitOnErrorsPlugin(), 211 | 212 | /** 213 | * Create global constants which can be configured at compile time. 214 | * 215 | * Useful for allowing different behaviour between development builds and 216 | * release builds 217 | * 218 | * NODE_ENV should be production so that modules do not perform certain 219 | * development checks 220 | * 221 | * By default, use 'development' as NODE_ENV. This can be overriden with 222 | * 'staging', for example, by changing the ENV variables in the npm scripts 223 | */ 224 | new webpack.EnvironmentPlugin({ 225 | NODE_ENV: 'development' 226 | }), 227 | 228 | new webpack.LoaderOptionsPlugin({ 229 | debug: true 230 | }), 231 | 232 | new ExtractTextPlugin({ 233 | filename: '[name].css' 234 | }), 235 | ], 236 | 237 | node: { 238 | __dirname: false, 239 | __filename: false 240 | }, 241 | 242 | devServer: { 243 | port, 244 | publicPath, 245 | compress: true, 246 | noInfo: true, 247 | stats: 'errors-only', 248 | inline: true, 249 | lazy: false, 250 | hot: true, 251 | headers: { 'Access-Control-Allow-Origin': '*' }, 252 | contentBase: path.join(__dirname, 'dist'), 253 | watchOptions: { 254 | aggregateTimeout: 300, 255 | ignored: /node_modules/, 256 | poll: 100 257 | }, 258 | historyApiFallback: { 259 | verbose: true, 260 | disableDotRule: false, 261 | }, 262 | before() { 263 | if (process.env.START_HOT) { 264 | console.log('Starting Main Process...'); 265 | spawn( 266 | 'npm', 267 | ['run', 'start-main-dev'], 268 | { shell: true, env: process.env, stdio: 'inherit' } 269 | ) 270 | .on('close', code => process.exit(code)) 271 | .on('error', spawnError => console.error(spawnError)); 272 | } 273 | } 274 | }, 275 | }); 276 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-boilerplate", 3 | "productName": "ElectronReact", 4 | "version": "0.13.2", 5 | "description": "Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development", 6 | "scripts": { 7 | "build": "concurrently \"npm run build-main\" \"npm run build-renderer\"", 8 | "build-dll": "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors", 9 | "build-main": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors", 10 | "build-renderer": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors", 11 | "dev": "cross-env START_HOT=1 node -r babel-register ./internals/scripts/CheckPortInUse.js && cross-env START_HOT=1 npm run start-renderer-dev", 12 | "electron-rebuild": "electron-rebuild --parallel --force --types prod,dev,optional --module-dir app", 13 | "flow": "flow", 14 | "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true", 15 | "lint": "cross-env NODE_ENV=development eslint --cache --format=node_modules/eslint-formatter-pretty .", 16 | "lint-fix": "npm run lint -- --fix", 17 | "lint-styles": "stylelint app/*.css app/components/*.css --syntax scss", 18 | "lint-styles-fix": "stylefmt -r app/*.css app/components/*.css", 19 | "package": "npm run build && build --publish never", 20 | "package-all": "npm run build && build -mwl", 21 | "package-linux": "npm run build && build --linux", 22 | "package-win": "npm run build && build --win --x64", 23 | "postinstall": "node -r babel-register internals/scripts/CheckNativeDep.js && npm run flow-typed && npm run build-dll && electron-builder install-app-deps && node node_modules/fbjs-scripts/node/check-dev-engines.js package.json", 24 | "prestart": "npm run build", 25 | "start": "cross-env NODE_ENV=production electron ./app/", 26 | "start-main-dev": "cross-env HOT=1 NODE_ENV=development electron -r babel-register ./app/main.dev", 27 | "start-renderer-dev": "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.renderer.dev.js", 28 | "test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings -r babel-register ./internals/scripts/RunTests.js", 29 | "test-all": "npm run lint && npm run flow && npm run build && npm run test && npm run test-e2e", 30 | "test-e2e": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings -r babel-register ./internals/scripts/RunTests.js e2e", 31 | "test-watch": "npm test -- --watch" 32 | }, 33 | "browserslist": "electron 1.6", 34 | "build": { 35 | "productName": "ElectronReact", 36 | "appId": "org.develar.ElectronReact", 37 | "files": [ 38 | "dist/", 39 | "node_modules/", 40 | "app.html", 41 | "main.prod.js", 42 | "main.prod.js.map", 43 | "package.json" 44 | ], 45 | "dmg": { 46 | "contents": [ 47 | { 48 | "x": 130, 49 | "y": 220 50 | }, 51 | { 52 | "x": 410, 53 | "y": 220, 54 | "type": "link", 55 | "path": "/Applications" 56 | } 57 | ] 58 | }, 59 | "win": { 60 | "target": [ 61 | "nsis" 62 | ] 63 | }, 64 | "linux": { 65 | "target": [ 66 | "deb", 67 | "AppImage" 68 | ], 69 | "category": "Development" 70 | }, 71 | "directories": { 72 | "buildResources": "resources", 73 | "output": "release" 74 | } 75 | }, 76 | "repository": { 77 | "type": "git", 78 | "url": "git+https://github.com/chentsulin/electron-react-boilerplate.git" 79 | }, 80 | "author": { 81 | "name": "C. T. Lin", 82 | "email": "chentsulin@gmail.com", 83 | "url": "https://github.com/chentsulin" 84 | }, 85 | "license": "MIT", 86 | "bugs": { 87 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues" 88 | }, 89 | "keywords": [ 90 | "electron", 91 | "boilerplate", 92 | "react", 93 | "redux", 94 | "flow", 95 | "sass", 96 | "webpack", 97 | "hot", 98 | "reload" 99 | ], 100 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme", 101 | "jest": { 102 | "moduleNameMapper": { 103 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/internals/mocks/fileMock.js", 104 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 105 | }, 106 | "moduleFileExtensions": [ 107 | "js" 108 | ], 109 | "moduleDirectories": [ 110 | "node_modules", 111 | "app/node_modules" 112 | ], 113 | "transform": { 114 | "^.+\\.js$": "babel-jest" 115 | }, 116 | "setupFiles": [ 117 | "./internals/scripts/CheckBuiltsExist.js" 118 | ] 119 | }, 120 | "devDependencies": { 121 | "babel-core": "^6.26.0", 122 | "babel-eslint": "^8.2.1", 123 | "babel-jest": "^22.1.0", 124 | "babel-loader": "^7.1.2", 125 | "babel-plugin-add-module-exports": "^0.2.1", 126 | "babel-plugin-dev-expression": "^0.2.1", 127 | "babel-plugin-flow-runtime": "^0.15.0", 128 | "babel-plugin-transform-class-properties": "^6.24.1", 129 | "babel-plugin-transform-es2015-classes": "^6.24.1", 130 | "babel-preset-env": "^1.6.1", 131 | "babel-preset-react": "^6.24.1", 132 | "babel-preset-react-hmre": "^1.1.1", 133 | "babel-preset-react-optimize": "^1.0.1", 134 | "babel-preset-stage-0": "^6.24.1", 135 | "babel-register": "^6.26.0", 136 | "chalk": "^2.3.0", 137 | "concurrently": "^3.5.1", 138 | "cross-env": "^5.1.3", 139 | "cross-spawn": "^6.0.4", 140 | "css-loader": "^0.28.9", 141 | "detect-port": "^1.2.2", 142 | "electron": "^1.7.11", 143 | "electron-builder": "^19.55.3", 144 | "electron-devtools-installer": "^2.2.3", 145 | "electron-rebuild": "^1.7.3", 146 | "enzyme": "^3.3.0", 147 | "enzyme-adapter-react-16": "^1.1.1", 148 | "enzyme-to-json": "^3.3.1", 149 | "eslint": "^4.16.0", 150 | "eslint-config-airbnb": "^16.1.0", 151 | "eslint-formatter-pretty": "^1.3.0", 152 | "eslint-import-resolver-webpack": "^0.8.4", 153 | "eslint-plugin-compat": "^2.2.0", 154 | "eslint-plugin-flowtype": "^2.42.0", 155 | "eslint-plugin-import": "^2.8.0", 156 | "eslint-plugin-jest": "^21.7.0", 157 | "eslint-plugin-jsx-a11y": "6.0.3", 158 | "eslint-plugin-promise": "^3.6.0", 159 | "eslint-plugin-react": "^7.6.1", 160 | "express": "^4.16.2", 161 | "extract-text-webpack-plugin": "^3.0.2", 162 | "fbjs-scripts": "^0.8.1", 163 | "file-loader": "^1.1.6", 164 | "flow-bin": "^0.64.0", 165 | "flow-runtime": "^0.16.0", 166 | "flow-typed": "^2.3.0", 167 | "identity-obj-proxy": "^3.0.0", 168 | "jest": "^22.1.4", 169 | "minimist": "^1.2.0", 170 | "node-sass": "^4.7.2", 171 | "npm-logical-tree": "^1.2.1", 172 | "react-test-renderer": "^16.2.0", 173 | "redux-logger": "^3.0.6", 174 | "rimraf": "^2.6.2", 175 | "sass-loader": "^6.0.6", 176 | "sinon": "^4.2.2", 177 | "spectron": "^3.8.0", 178 | "style-loader": "^0.20.1", 179 | "stylefmt": "^6.0.0", 180 | "stylelint": "^8.4.0", 181 | "stylelint-config-standard": "^18.0.0", 182 | "uglifyjs-webpack-plugin": "1.1.8", 183 | "url-loader": "^0.6.2", 184 | "webpack": "^3.10.0", 185 | "webpack-bundle-analyzer": "^2.9.2", 186 | "webpack-dev-server": "^2.11.1", 187 | "webpack-merge": "^4.1.1" 188 | }, 189 | "dependencies": { 190 | "devtron": "^1.4.0", 191 | "electron-debug": "^1.5.0", 192 | "font-awesome": "^4.7.0", 193 | "history": "^4.7.2", 194 | "react": "^16.2.0", 195 | "react-dom": "^16.2.0", 196 | "react-hot-loader": "^4.0.0-beta.13", 197 | "react-redux": "^5.0.6", 198 | "react-router": "^4.2.0", 199 | "react-router-dom": "^4.2.2", 200 | "react-router-redux": "^5.0.0-alpha.6", 201 | "redux": "^3.7.2", 202 | "redux-thunk": "^2.2.0", 203 | "source-map-support": "^0.5.3" 204 | }, 205 | "devEngines": { 206 | "node": ">=7.x", 207 | "npm": ">=4.x", 208 | "yarn": ">=0.21.3" 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.13.2 (2018.1.31) 2 | - Hot Module Reload (HMR) fixes 3 | - Bumped all dependencies to latest semver 4 | - Prevent error propagation of `CheckNativeDeps` script 5 | 6 | # 0.13.1 (2018.1.13) 7 | - Hot Module Reload (HMR) fixes 8 | - Bumped all dependencies to latest semver 9 | - Fixed electron-rebuild script 10 | - Fixed tests scripts to run on all platforms 11 | - Skip redux logs in console in test ENV 12 | 13 | # 0.13.0 (2018.1.6) 14 | 15 | #### Additions 16 | - Add native dependencies check on postinstall 17 | - Updated all dependencies to latest semver 18 | 19 | # 0.12.0 (2017.7.8) 20 | 21 | #### Misc 22 | - Removed `babel-polyfill` 23 | - Renamed and alphabetized npm scripts 24 | 25 | #### Breaking 26 | - Changed node dev `__dirname` and `__filename` to node built in fn's (https://github.com/chentsulin/electron-react-boilerplate/pull/1035) 27 | - Renamed `app/bundle.js` to `app/renderer.prod.js` for consistency 28 | - Renamed `dll/vendor.js` to `dll/renderer.dev.dll.js` for consistency 29 | 30 | #### Additions 31 | - Enable node_modules cache on CI 32 | 33 | # 0.11.2 (2017.5.1) 34 | 35 | Yay! Another patch release. This release mostly includes refactorings and router bug fixes. Huge thanks to @anthonyraymond! 36 | 37 | ⚠️ Windows electron builds are failing because of [this issue](https://github.com/electron/electron/issues/9321). This is not an issue with the boilerplate ⚠️ 38 | 39 | #### Breaking 40 | - **Renamed `./app/main.development.js` => `./app/main.{dev,prod}.js`:** [#963](https://github.com/chentsulin/electron-react-boilerplate/pull/963) 41 | 42 | #### Fixes 43 | - **Fixed reloading when not on `/` path:** [#958](https://github.com/chentsulin/electron-react-boilerplate/pull/958) [#949](https://github.com/chentsulin/electron-react-boilerplate/pull/949) 44 | 45 | #### Additions 46 | - **Added support for stylefmt:** [#960](https://github.com/chentsulin/electron-react-boilerplate/pull/960) 47 | 48 | # 0.11.1 (2017.4.23) 49 | 50 | You can now debug the production build with devtools like so: 51 | ``` 52 | DEBUG_PROD=true npm run package 53 | ``` 54 | 55 | 🎉🎉🎉 56 | 57 | #### Additions 58 | - **Added support for debugging production build:** [#fab245a](https://github.com/chentsulin/electron-react-boilerplate/pull/941/commits/fab245a077d02a09630f74270806c0c534a4ff95) 59 | 60 | #### Bug Fixes 61 | - **Fixed bug related to importing native dependencies:** [#933](https://github.com/chentsulin/electron-react-boilerplate/pull/933) 62 | 63 | #### Improvements 64 | - **Updated all deps to latest semver** 65 | 66 | # 0.11.0 (2017.4.19) 67 | 68 | Here's the most notable changes since `v0.10.0`. Its been about a year since a release has been pushed. Expect a new release to be published every 3-4 weeks. 69 | 70 | #### Breaking Changes 71 | 72 | - **Dropped support for node < 6** 73 | - **Refactored webpack config files** 74 | - **Migrate to two-package.json project structure** 75 | - **Updated all devDeps to latest semver** 76 | - **Migrated to Jest:** [#768](https://github.com/chentsulin/electron-react-boilerplate/pull/768) 77 | - **Migrated to `react-router@4`** 78 | - **Migrated to `electron-builder@4`** 79 | - **Migrated to `webpack@2`** 80 | - **Migrated to `react-hot-loader@3`** 81 | - **Changed default live reload server PORT to `1212` from `3000`** 82 | 83 | #### Additions 84 | 85 | - **Added support for Yarn:** [#451](https://github.com/chentsulin/electron-react-boilerplate/pull/451) 86 | - **Added support for Flow:** [#425](https://github.com/chentsulin/electron-react-boilerplate/pull/425) 87 | - **Added support for stylelint:** [#911](https://github.com/chentsulin/electron-react-boilerplate/pull/911) 88 | - **Added support for electron-builder:** [#876](https://github.com/chentsulin/electron-react-boilerplate/pull/876) 89 | - **Added optional support for SASS:** [#880](https://github.com/chentsulin/electron-react-boilerplate/pull/880) 90 | - **Added support for eslint-plugin-flowtype:** [#911](https://github.com/chentsulin/electron-react-boilerplate/pull/911) 91 | - **Added support for appveyor:** [#280](https://github.com/chentsulin/electron-react-boilerplate/pull/280) 92 | - **Added support for webpack dlls:** [#860](https://github.com/chentsulin/electron-react-boilerplate/pull/860) 93 | - **Route based code splitting:** [#884](https://github.com/chentsulin/electron-react-boilerplate/pull/884) 94 | - **Added support for Webpack Bundle Analyzer:** [#922](https://github.com/chentsulin/electron-react-boilerplate/pull/922) 95 | 96 | #### Improvements 97 | 98 | - **Parallelize renderer and main build processes when running `npm run build`** 99 | - **Dynamically generate electron app menu** 100 | - **Improved vscode integration:** [#856](https://github.com/chentsulin/electron-react-boilerplate/pull/856) 101 | 102 | #### Bug Fixes 103 | 104 | - **Fixed hot module replacement race condition bug:** [#917](https://github.com/chentsulin/electron-react-boilerplate/pull/917) [#920](https://github.com/chentsulin/electron-react-boilerplate/pull/920) 105 | 106 | # 0.10.0 (2016.4.18) 107 | 108 | #### Improvements 109 | 110 | - **Use Babel in main process with Webpack build:** [#201](https://github.com/chentsulin/electron-react-boilerplate/pull/201) 111 | - **Change targets to built-in support by webpack:** [#197](https://github.com/chentsulin/electron-react-boilerplate/pull/197) 112 | - **use es2015 syntax for webpack configs:** [#195](https://github.com/chentsulin/electron-react-boilerplate/pull/195) 113 | - **Open application when webcontent is loaded:** [#192](https://github.com/chentsulin/electron-react-boilerplate/pull/192) 114 | - **Upgraded dependencies** 115 | 116 | #### Bug fixed 117 | 118 | - **Fix `npm list electron-prebuilt` in package.js:** [#188](https://github.com/chentsulin/electron-react-boilerplate/pull/188) 119 | 120 | 121 | # 0.9.0 (2016.3.23) 122 | 123 | #### Improvements 124 | 125 | - **Added [redux-logger](https://github.com/fcomb/redux-logger)** 126 | - **Upgraded [react-router-redux](https://github.com/reactjs/react-router-redux) to v4** 127 | - **Upgraded dependencies** 128 | - **Added `npm run dev` command:** [#162](https://github.com/chentsulin/electron-react-boilerplate/pull/162) 129 | - **electron to v0.37.2** 130 | 131 | #### Breaking Changes 132 | 133 | - **css module as default:** [#154](https://github.com/chentsulin/electron-react-boilerplate/pull/154). 134 | - **set default NODE_ENV to production:** [#140](https://github.com/chentsulin/electron-react-boilerplate/issues/140) 135 | 136 | 137 | # 0.8.0 (2016.2.17) 138 | 139 | #### Bug fixed 140 | 141 | - **Fix lint errors** 142 | - **Fix Webpack publicPath for production builds**: [#119](https://github.com/chentsulin/electron-react-boilerplate/issues/119). 143 | - **package script now chooses correct OS icon extension** 144 | 145 | #### Improvements 146 | 147 | - **babel 6** 148 | - **Upgrade Dependencies** 149 | - **Enable CSS source maps** 150 | - **Add json-loader**: [#128](https://github.com/chentsulin/electron-react-boilerplate/issues/128). 151 | - **react-router 2.0 and react-router-redux 3.0** 152 | 153 | 154 | # 0.7.1 (2015.12.27) 155 | 156 | #### Bug fixed 157 | 158 | - **Fixed npm script on windows 10:** [#103](https://github.com/chentsulin/electron-react-boilerplate/issues/103). 159 | - **history and react-router version bump**: [#109](https://github.com/chentsulin/electron-react-boilerplate/issues/109), [#110](https://github.com/chentsulin/electron-react-boilerplate/pull/110). 160 | 161 | #### Improvements 162 | 163 | - **electron 0.36** 164 | 165 | 166 | 167 | # 0.7.0 (2015.12.16) 168 | 169 | #### Bug fixed 170 | 171 | - **Fixed process.env.NODE_ENV variable in webpack:** [#74](https://github.com/chentsulin/electron-react-boilerplate/pull/74). 172 | - **add missing object-assign**: [#76](https://github.com/chentsulin/electron-react-boilerplate/pull/76). 173 | - **packaging in npm@3:** [#77](https://github.com/chentsulin/electron-react-boilerplate/pull/77). 174 | - **compatibility in windows:** [#100](https://github.com/chentsulin/electron-react-boilerplate/pull/100). 175 | - **disable chrome debugger in production env:** [#102](https://github.com/chentsulin/electron-react-boilerplate/pull/102). 176 | 177 | #### Improvements 178 | 179 | - **redux** 180 | - **css-modules** 181 | - **upgrade to react-router 1.x** 182 | - **unit tests** 183 | - **e2e tests** 184 | - **travis-ci** 185 | - **upgrade to electron 0.35.x** 186 | - **use es2015** 187 | - **check dev engine for node and npm** 188 | 189 | 190 | # 0.6.5 (2015.11.7) 191 | 192 | #### Improvements 193 | 194 | - **Bump style-loader to 0.13** 195 | - **Bump css-loader to 0.22** 196 | 197 | 198 | # 0.6.4 (2015.10.27) 199 | 200 | #### Improvements 201 | 202 | - **Bump electron-debug to 0.3** 203 | 204 | 205 | # 0.6.3 (2015.10.26) 206 | 207 | #### Improvements 208 | 209 | - **Initialize ExtractTextPlugin once:** [#64](https://github.com/chentsulin/electron-react-boilerplate/issues/64). 210 | 211 | 212 | # 0.6.2 (2015.10.18) 213 | 214 | #### Bug fixed 215 | 216 | - **Babel plugins production env not be set properly:** [#57](https://github.com/chentsulin/electron-react-boilerplate/issues/57). 217 | 218 | 219 | # 0.6.1 (2015.10.17) 220 | 221 | #### Improvements 222 | 223 | - **Bump electron to v0.34.0** 224 | 225 | 226 | # 0.6.0 (2015.10.16) 227 | 228 | #### Breaking Changes 229 | 230 | - **From react-hot-loader to react-transform** 231 | 232 | 233 | # 0.5.2 (2015.10.15) 234 | 235 | #### Improvements 236 | 237 | - **Run tests with babel-register:** [#29](https://github.com/chentsulin/electron-react-boilerplate/issues/29). 238 | 239 | 240 | # 0.5.1 (2015.10.12) 241 | 242 | #### Bug fixed 243 | 244 | - **Fix #51:** use `path.join(__dirname` instead of `./`. 245 | 246 | 247 | # 0.5.0 (2015.10.11) 248 | 249 | #### Improvements 250 | 251 | - **Simplify webpack config** see [#50](https://github.com/chentsulin/electron-react-boilerplate/pull/50). 252 | 253 | #### Breaking Changes 254 | 255 | - **webpack configs** 256 | - **port changed:** changed default port from 2992 to 3000. 257 | - **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`. 258 | 259 | 260 | # 0.4.3 (2015.9.22) 261 | 262 | #### Bug fixed 263 | 264 | - **Fix #45 zeromq crash:** bump version of `electron-prebuilt`. 265 | 266 | 267 | # 0.4.2 (2015.9.15) 268 | 269 | #### Bug fixed 270 | 271 | - **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1` 272 | 273 | 274 | # 0.4.1 (2015.9.11) 275 | 276 | #### Improvements 277 | 278 | - **use electron-prebuilt version for packaging (#33)** 279 | 280 | 281 | # 0.4.0 (2015.9.5) 282 | 283 | #### Improvements 284 | 285 | - **update dependencies** 286 | 287 | 288 | # 0.3.0 (2015.8.31) 289 | 290 | #### Improvements 291 | 292 | - **eslint-config-airbnb** 293 | 294 | 295 | # 0.2.10 (2015.8.27) 296 | 297 | #### Features 298 | 299 | - **custom placeholder icon** 300 | 301 | #### Improvements 302 | 303 | - **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer) 304 | 305 | 306 | # 0.2.9 (2015.8.18) 307 | 308 | #### Bug fixed 309 | 310 | - **Fix hot-reload** 311 | 312 | 313 | # 0.2.8 (2015.8.13) 314 | 315 | #### Improvements 316 | 317 | - **bump electron-debug** 318 | - **babelrc** 319 | - **organize webpack scripts** 320 | 321 | 322 | # 0.2.7 (2015.7.9) 323 | 324 | #### Bug fixed 325 | 326 | - **defaultProps:** fix typos. 327 | 328 | 329 | # 0.2.6 (2015.7.3) 330 | 331 | #### Features 332 | 333 | - **menu** 334 | 335 | #### Bug fixed 336 | 337 | - **package.js:** include webpack build. 338 | 339 | 340 | # 0.2.5 (2015.7.1) 341 | 342 | #### Features 343 | 344 | - **NPM Script:** support multi-platform 345 | - **package:** `--all` option 346 | 347 | 348 | # 0.2.4 (2015.6.9) 349 | 350 | #### Bug fixed 351 | 352 | - **Eslint:** typo, [#17](https://github.com/chentsulin/electron-react-boilerplate/issues/17) and improve `.eslintrc` 353 | 354 | 355 | # 0.2.3 (2015.6.3) 356 | 357 | #### Features 358 | 359 | - **Package Version:** use latest release electron version as default 360 | - **Ignore Large peerDependencies** 361 | 362 | #### Bug fixed 363 | 364 | - **Npm Script:** typo, [#6](https://github.com/chentsulin/electron-react-boilerplate/pull/6) 365 | - **Missing css:** [#7](https://github.com/chentsulin/electron-react-boilerplate/pull/7) 366 | 367 | 368 | # 0.2.2 (2015.6.2) 369 | 370 | #### Features 371 | 372 | - **electron-debug** 373 | 374 | #### Bug fixed 375 | 376 | - **Webpack:** add `.json` and `.node` to extensions for imitating node require. 377 | - **Webpack:** set `node_modules` to externals for native module support. 378 | 379 | 380 | # 0.2.1 (2015.5.30) 381 | 382 | #### Bug fixed 383 | 384 | - **Webpack:** #1, change build target to `atom`. 385 | 386 | 387 | # 0.2.0 (2015.5.30) 388 | 389 | #### Features 390 | 391 | - **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`. 392 | - **Support asar** 393 | - **Support icon** 394 | 395 | 396 | # 0.1.0 (2015.5.27) 397 | 398 | #### Features 399 | 400 | - **Webpack:** babel, react-hot, ... 401 | - **Flux:** actions, api, components, containers, stores.. 402 | - **Package:** darwin (osx), linux and win32 (windows) platform. 403 | --------------------------------------------------------------------------------