├── .gitignore ├── .npmrc ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── app ├── actions │ ├── counter.ts │ └── helpers.ts ├── api │ └── .gitkeep ├── app.global.scss ├── app.html ├── app.icns ├── components │ ├── Counter.scss │ ├── Counter.tsx │ ├── Home.scss │ └── Home.tsx ├── containers │ ├── App.tsx │ ├── CounterPage.tsx │ ├── HomePage.tsx │ └── Root.tsx ├── index.tsx ├── main.development.js ├── package.json ├── reducers │ ├── counter.ts │ └── index.ts ├── routes.tsx ├── store │ ├── configureStore.development.ts │ ├── configureStore.production.ts │ └── configureStore.ts └── utils │ └── .gitkeep ├── erb-logo.png ├── internals └── mocks │ └── fileMock.js ├── package.json ├── resources ├── icon.icns ├── icon.ico ├── icon.png └── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png ├── server.js ├── setup.js ├── test ├── actions │ ├── __snapshots__ │ │ └── counter.spec.ts.snap │ └── counter.spec.ts ├── components │ └── Counter.spec.tsx ├── containers │ └── CounterPage.spec.tsx ├── e2e │ └── e2e.spec.ts ├── example.ts ├── preprocessor.js ├── reducers │ └── counter.spec.ts ├── runTests.js └── utils │ └── enzymeConfig.ts ├── tsconfig.json ├── webpack.config.base.js ├── webpack.config.development.js ├── webpack.config.electron.js ├── webpack.config.eslint.js ├── webpack.config.production.js ├── webpack.config.test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | app/node_modules 29 | 30 | # OSX 31 | .DS_Store 32 | 33 | # App packaged 34 | release 35 | app/main.js 36 | app/main.js.map 37 | app/bundle.js 38 | app/bundle.js.map 39 | app/style.css 40 | app/style.css.map 41 | dist 42 | main.js 43 | main.js.map 44 | 45 | .idea 46 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 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 | before_install: yarn global add greenkeeper-lockfile@1 27 | 28 | install: 29 | - export CXX="g++-4.8" 30 | - yarn 31 | - cd app && yarn && cd .. 32 | - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile 33 | --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 34 | 35 | before_script: 36 | - greenkeeper-lockfile-update 37 | - export DISPLAY=:99.0 38 | - sh -e /etc/init.d/xvfb start & 39 | - sleep 3 40 | 41 | script: 42 | - node --version 43 | - yarn package 44 | - yarn test 45 | - yarn test-e2e 46 | 47 | after_script: greenkeeper-lockfile-upload 48 | 49 | env: 50 | global: 51 | secure: hH+MmHE3WhyyBflZoOZLqqr501OOnvA1VtaqF3WU9rJCpjnKb4fZvHejYtVw65Ho2C40G2a34oBGxnT6INF1BfClYz4IMijL689xuR8dgStQzvcpMTwEQkrLsaCTSqXJR6nzqbRRGmaRfjSLdF6vhd3bGmtVNMEtoEky5EsXeBVjn8D5WMrme17m4F4D/qjHty6Xr5PjZL6eInVKlPZEbALYBhnIWqE5Xw6LcGa8JkqsWw80sLNtz4yWQSwkjrZ1lzCNd/GSUDN5K/BFK6kUZB5HpJZUzUoIQyXmEft+ALZFE4p4tX/H044JguPN9sKsP4SNkz2+3tOMevz/x5of3ssLMqB7gFtd5SGKwjdn6sWYNHKeM0MALyXAARhilyzY0v+xf226ZLRb++8Ih/vAZNymvQjn0idBfwC8ffUSXEx786Zysot1AqWrxaktKFHkBHkiQ4BGkAXVT4FLCgLtdMta+NLYYmuNrmXoU2iEhSzwvZmw94wcBxZie+TvuXE76TgrOiopRPbGXw/0iDm8J1MVWpoPmqSLUG5Vbq9fSNZnR/6j89j7Ws83fdnfZby+qOcV5WMJetQs/EHTxfWJu/AM6bxCYSSmXEJUb8dHpk8el8T+MGLI9pCTiiyy1ozOeo1WKpcOdmF6GU64EOxRWn2837+a9W0kXXAj6tPCW6A= 52 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present C. T. Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-react-typescript-boilerplate 2 | 3 | [![Build Status][travis-image]][travis-url] 4 | [![Greenkeeper badge](https://badges.greenkeeper.io/iRath96/electron-react-typescript-boilerplate.svg)](https://greenkeeper.io/) 5 | 6 | This is a slight modificiation of the great [electron-react-boilerplate](https://github.com/chentsulin/electron-react-boilerplate) by chentsulin. 7 | Instead of [Babel](https://babeljs.io) and [flow](https://flowtype.org) this version uses [TypeScript](https://www.typescriptlang.org). Support for [Sass](http://sass-lang.com) has also been added. 8 | 9 | # electron-react-boilerplate 10 | 11 | ![](./erb-logo.png) 12 | 13 | > Live editing development on desktop app 14 | 15 | [Electron](http://electron.atom.io/) application boilerplate based on [React](https://facebook.github.io/react/), [Redux](https://github.com/reactjs/redux), [React Router](https://github.com/reactjs/react-router), [Webpack](http://webpack.github.io/docs/), [React Transform HMR](https://github.com/gaearon/react-transform-hmr) for rapid application development 16 | 17 | ## Screenshot 18 | 19 | ![Electron Boilerplate Demo](https://cloud.githubusercontent.com/assets/3382565/10557547/b1f07a4e-74e3-11e5-8d27-79ab6947d429.gif) 20 | 21 | ## Install 22 | 23 | * **Note: requires a node version >= 6 and an npm version >= 3.** 24 | * **If you have installation or compilation issues with this project, please see [our debugging guide](https://github.com/chentsulin/electron-react-boilerplate/issues/400)** 25 | 26 | First, clone the repo via git: 27 | 28 | ```bash 29 | git clone https://github.com/iRath96/electron-react-typescript-boilerplate.git your-project-name 30 | ``` 31 | 32 | And then install dependencies. 33 | **ProTip**: Install with [yarn](https://github.com/yarnpkg/yarn) for faster and safer installation 34 | 35 | ```bash 36 | $ cd your-project-name && npm install 37 | ``` 38 | 39 | :bulb: *In order to remove boilerplate sample code, simply run `npm run cleanup`. After this is run, the initial sample boilerplate code will be removed in order for a clean project for starting custom dev* 40 | 41 | ## Run 42 | 43 | Run these two commands __simultaneously__ in different console tabs. 44 | 45 | ```bash 46 | $ npm run hot-server 47 | $ npm run start-hot 48 | ``` 49 | 50 | or run two servers with one command 51 | 52 | ```bash 53 | $ npm run dev 54 | ``` 55 | 56 | ## Editor Configuration 57 | **Atom** 58 | ```bash 59 | apm install editorconfig es6-javascript atom-ternjs javascript-snippets linter linter-eslint language-babel autocomplete-modules 60 | ``` 61 | 62 | **Sublime** 63 | * https://github.com/sindresorhus/editorconfig-sublime#readme 64 | * https://github.com/SublimeLinter/SublimeLinter3 65 | * https://github.com/roadhump/SublimeLinter-eslint 66 | * https://github.com/babel/babel-sublime 67 | 68 | **Others** 69 | * [Editorconfig](http://editorconfig.org/#download) 70 | * [ESLint](http://eslint.org/docs/user-guide/integrations#editors) 71 | * Babel Syntax Plugin 72 | 73 | ## DevTools 74 | 75 | #### Toggle Chrome DevTools 76 | 77 | - OS X: Cmd Alt I or F12 78 | - Linux: Ctrl Shift I or F12 79 | - Windows: Ctrl Shift I or F12 80 | 81 | *See [electron-debug](https://github.com/sindresorhus/electron-debug) for more information.* 82 | 83 | #### DevTools extension 84 | 85 | This boilerplate is included following DevTools extensions: 86 | 87 | * [Devtron](https://github.com/electron/devtron) - Install via [electron-debug](https://github.com/sindresorhus/electron-debug). 88 | * [React Developer Tools](https://github.com/facebook/react-devtools) - Install via [electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer). 89 | * [Redux DevTools](https://github.com/zalmoxisus/redux-devtools-extension) - Install via [electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer). 90 | 91 | You can find the tabs on Chrome DevTools. 92 | 93 | If you want to update extensions version, please set `UPGRADE_EXTENSIONS` env, just run: 94 | 95 | ```bash 96 | $ UPGRADE_EXTENSIONS=1 npm run dev 97 | 98 | # For Windows 99 | $ set UPGRADE_EXTENSIONS=1 && npm run dev 100 | ``` 101 | 102 | 103 | 104 | ## CSS Modules 105 | 106 | This boilerplate out of the box is configured to use [css-modules](https://github.com/css-modules/css-modules). 107 | 108 | All `.css` file extensions will use css-modules unless it has `.global.css`. 109 | 110 | If you need global styles, stylesheets with `.global.css` will not go through the 111 | css-modules loader. e.g. `app.global.css` 112 | 113 | If you want to import global css libraries (like `bootstrap`), you can just write the following code in `.global.css`: 114 | 115 | ```css 116 | @import "~bootstrap/dist/css/bootstrap.css"; 117 | ``` 118 | 119 | 120 | ## Packaging 121 | 122 | To package apps for the local platform: 123 | 124 | ```bash 125 | $ npm run package 126 | ``` 127 | 128 | To package apps for all platforms: 129 | 130 | First, refer to [Multi Platform Build](https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build) for dependencies. 131 | 132 | Then, 133 | ```bash 134 | $ npm run package-all 135 | ``` 136 | 137 | To package apps with options: 138 | 139 | ```bash 140 | $ npm run package -- --[option] 141 | ``` 142 | 143 | ## Further commands 144 | 145 | To run the application without packaging run 146 | 147 | ```bash 148 | $ npm run build 149 | $ npm start 150 | ``` 151 | 152 | To run End-to-End Test 153 | 154 | ```bash 155 | $ npm run build 156 | $ npm run test-e2e 157 | ``` 158 | 159 | #### Options 160 | 161 | See [electron-builder CLI Usage](https://github.com/electron-userland/electron-builder#cli-usage) 162 | 163 | #### Module Structure 164 | 165 | This boilerplate uses a [two package.json structure](https://github.com/electron-userland/electron-builder#two-packagejson-structure). 166 | 167 | 1. If the module is native to a platform or otherwise should be included with the published package (i.e. bcrypt, openbci), it should be listed under `dependencies` in `./app/package.json`. 168 | 2. If a module is `import`ed by another module, include it in `dependencies` in `./package.json`. See [this ESLint rule](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md). 169 | 3. Otherwise, modules used for building, testing and debugging should be included in `devDependencies` in `./package.json`. 170 | 171 | ## Static Type Checking 172 | This project comes with Flow support out of the box! You can annotate your code with types, [get Flow errors as ESLint errors](https://github.com/amilajack/eslint-plugin-flowtype-errors), and get [type errors during runtime](https://github.com/gcanti/babel-plugin-tcomb-boilerplate) during development. Types are completely optional. 173 | 174 | ## Native-like UI 175 | 176 | If you want to have native-like User Interface (OS X El Capitan and Windows 10), [react-desktop](https://github.com/gabrielbull/react-desktop) may perfect suit for you. 177 | 178 | ## Dispatching redux actions from main process 179 | 180 | see discusses in [#118](https://github.com/chentsulin/electron-react-boilerplate/issues/118) and [#108](https://github.com/chentsulin/electron-react-boilerplate/issues/108) 181 | 182 | ## How to keep the boilerplate updated 183 | 184 | If your application is a fork from this repo, you can add this repo to another git remote: 185 | 186 | ```sh 187 | git remote add upstream https://github.com/chentsulin/electron-react-boilerplate.git 188 | ``` 189 | 190 | Then, use git to merge some latest commits: 191 | 192 | ```sh 193 | git pull upstream master 194 | ``` 195 | 196 | ## Maintainers 197 | 198 | - [C. T. Lin](https://github.com/chentsulin) 199 | - [Jhen-Jie Hong](https://github.com/jhen0409) 200 | - [Amila Welihinda](https://github.com/amilajack) 201 | 202 | 203 | ## License 204 | MIT © [C. T. Lin](https://github.com/chentsulin) 205 | 206 | [npm-image]: https://img.shields.io/npm/v/electron-react-boilerplate.svg?style=flat-square 207 | [npm-url]: https://npmjs.org/package/electron-react-boilerplate 208 | [travis-image]: https://travis-ci.org/iRath96/electron-react-typescript-boilerplate.svg?branch=master 209 | [travis-url]: https://travis-ci.org/iRath96/electron-react-typescript-boilerplate 210 | [appveyor-image]: https://ci.appveyor.com/api/projects/status/github/chentsulin/electron-react-boilerplate?svg=true 211 | [appveyor-url]: https://ci.appveyor.com/project/chentsulin/electron-react-boilerplate/branch/master 212 | [david_img]: https://img.shields.io/david/chentsulin/electron-react-boilerplate.svg 213 | [david_site]: https://david-dm.org/chentsulin/electron-react-boilerplate 214 | -------------------------------------------------------------------------------- /app/actions/counter.ts: -------------------------------------------------------------------------------- 1 | import { actionCreatorVoid } from './helpers'; 2 | 3 | export const increment = actionCreatorVoid('INCREMENT_COUNTER'); 4 | export const decrement = actionCreatorVoid('DECREMENT_COUNTER'); 5 | 6 | export function incrementIfOdd() { 7 | return (dispatch: Function, getState: Function) => { 8 | const { counter } = getState(); 9 | 10 | if (counter % 2 === 0) { 11 | return; 12 | } 13 | 14 | dispatch(increment()); 15 | }; 16 | } 17 | 18 | export function incrementAsync(delay: number = 1000) { 19 | return (dispatch: Function) => { 20 | setTimeout(() => { 21 | dispatch(increment()); 22 | }, delay); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /app/actions/helpers.ts: -------------------------------------------------------------------------------- 1 | import { Action } from 'redux'; 2 | 3 | export interface IAction extends Action {} 4 | export interface IActionWithPayload extends IAction { 5 | readonly payload: T; 6 | } 7 | 8 | interface IActionCreator { 9 | readonly type: string; 10 | (payload: T): IActionWithPayload; 11 | 12 | test(action: IAction): action is IActionWithPayload; 13 | } 14 | 15 | interface IActionCreatorVoid { 16 | readonly type: string; 17 | (): IAction; 18 | 19 | test(action: IAction): action is IAction; 20 | } 21 | 22 | export const actionCreator = (type: string): IActionCreator => 23 | Object.assign((payload: T): any => ({ type, payload }), { 24 | type, 25 | test(action: IAction): action is IActionWithPayload { 26 | return action.type === type; 27 | } 28 | }); 29 | 30 | export const actionCreatorVoid = (type: string): IActionCreatorVoid => 31 | Object.assign((): any => ({ type }), { 32 | type, 33 | test(action: IAction): action is IAction { 34 | return action.type === type; 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /app/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/app/api/.gitkeep -------------------------------------------------------------------------------- /app/app.global.scss: -------------------------------------------------------------------------------- 1 | @import "~font-awesome/css/font-awesome.css"; 2 | 3 | body { 4 | position: relative; 5 | color: white; 6 | height: 100vh; 7 | background-color: #232C39; 8 | background-image: linear-gradient(45deg, rgba(0, 216, 255, .5) 10%, rgba(0, 1, 127, .7)); 9 | font-family: Arial, Helvetica, Helvetica Neue, serif; 10 | overflow-y: hidden; 11 | } 12 | 13 | h2 { 14 | margin: 0; 15 | font-size: 2.25rem; 16 | font-weight: bold; 17 | letter-spacing: -.025em; 18 | color: #fff; 19 | } 20 | 21 | p { 22 | font-size: 24px; 23 | } 24 | 25 | li { 26 | list-style: none; 27 | } 28 | 29 | a { 30 | color: white; 31 | opacity: .75; 32 | text-decoration: none; 33 | } 34 | 35 | a:hover { 36 | opacity: 1; 37 | text-decoration: none; 38 | cursor: pointer; 39 | } 40 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Electron React! 6 | 17 | 18 | 19 |
20 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/app/app.icns -------------------------------------------------------------------------------- /app/components/Counter.scss: -------------------------------------------------------------------------------- 1 | :local { 2 | .backButton { 3 | position: absolute; 4 | } 5 | 6 | .counter { 7 | position: absolute; 8 | top: 30%; 9 | left: 45%; 10 | font-size: 10rem; 11 | font-weight: bold; 12 | letter-spacing: -.025em; 13 | } 14 | 15 | .btnGroup { 16 | position: relative; 17 | top: 500px; 18 | width: 480px; 19 | margin: 0 auto; 20 | } 21 | 22 | .btn { 23 | font-size: 1.6rem; 24 | font-weight: bold; 25 | background-color: #fff; 26 | border-radius: 50%; 27 | margin: 10px; 28 | width: 100px; 29 | height: 100px; 30 | opacity: .7; 31 | cursor: pointer; 32 | font-family: Arial, Helvetica, Helvetica Neue; 33 | } 34 | 35 | .btn:hover { 36 | color: white; 37 | background-color: rgba(0, 0, 0, 0.5); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RouteComponentProps } from 'react-router'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | let styles = require('./Counter.scss'); 6 | 7 | export interface IProps extends RouteComponentProps { 8 | increment(): void, 9 | incrementIfOdd(): void, 10 | incrementAsync(): void, 11 | decrement(): void, 12 | counter: number 13 | } 14 | 15 | export class Counter extends React.Component { 16 | render() { 17 | const { increment, incrementIfOdd, incrementAsync, decrement, counter } = this.props; 18 | return ( 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | {counter} 27 |
28 |
29 | 32 | 35 | 36 | 37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | export default Counter; 44 | -------------------------------------------------------------------------------- /app/components/Home.scss: -------------------------------------------------------------------------------- 1 | :local { 2 | .container { 3 | position: absolute; 4 | top: 30%; 5 | left: 10px; 6 | text-align: center; 7 | } 8 | 9 | .container h2 { 10 | font-size: 5rem; 11 | } 12 | 13 | .container a { 14 | font-size: 1.4rem; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | let styles = require('./Home.scss'); 5 | 6 | export default class Home extends React.Component { 7 | render() { 8 | return ( 9 |
10 |
11 |

Home

12 | to Counter 13 |
14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/containers/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default class App extends React.Component { 4 | render() { 5 | return ( 6 |
7 | {this.props.children} 8 |
9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/containers/CounterPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect, Dispatch } from 'react-redux'; 4 | import { Counter, IProps } from '../components/Counter'; 5 | import * as CounterActions from '../actions/counter'; 6 | import { IState } from '../reducers'; 7 | 8 | function mapStateToProps(state: IState): Partial { 9 | return { 10 | counter: state.counter 11 | }; 12 | } 13 | 14 | function mapDispatchToProps(dispatch: Dispatch): Partial { 15 | return bindActionCreators(CounterActions as any, dispatch); 16 | } 17 | 18 | export default (connect(mapStateToProps, mapDispatchToProps)(Counter) as any as React.StatelessComponent); 19 | -------------------------------------------------------------------------------- /app/containers/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RouteComponentProps } from 'react-router'; 3 | import Home from '../components/Home'; 4 | 5 | export class HomePage extends React.Component, void> { 6 | render() { 7 | return ( 8 | 9 | ); 10 | } 11 | } 12 | 13 | export default (HomePage as any as React.StatelessComponent>); 14 | -------------------------------------------------------------------------------- /app/containers/Root.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as Redux from 'react-redux'; 3 | import { History } from 'history'; 4 | 5 | import { Provider } from 'react-redux'; 6 | import { ConnectedRouter } from 'react-router-redux'; 7 | import Routes from '../routes'; 8 | 9 | interface IRootType { 10 | store: Redux.Store; 11 | history: History 12 | }; 13 | 14 | export default function Root({ store, history }: IRootType) { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import Root from './containers/Root'; 5 | import './app.global.scss'; 6 | 7 | const { configureStore, history } = require('./store/configureStore'); 8 | const store = configureStore(); 9 | 10 | render( 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | if ((module as any).hot) { 18 | (module as any).hot.accept('./containers/Root', () => { 19 | const NextRoot = require('./containers/Root').default; 20 | render( 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /app/main.development.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, Menu, shell } = require('electron'); 2 | 3 | let menu; 4 | let template; 5 | let mainWindow = null; 6 | 7 | if (process.env.NODE_ENV === 'production') { 8 | const sourceMapSupport = require('source-map-support'); // eslint-disable-line 9 | sourceMapSupport.install(); 10 | } 11 | 12 | if (process.env.NODE_ENV === 'development') { 13 | require('electron-debug')(); // eslint-disable-line global-require 14 | const path = require('path'); // eslint-disable-line 15 | const p = path.join(__dirname, '..', 'app', 'node_modules'); // eslint-disable-line 16 | require('module').globalPaths.push(p); // eslint-disable-line 17 | } 18 | 19 | app.on('window-all-closed', () => { 20 | if (process.platform !== 'darwin') app.quit(); 21 | }); 22 | 23 | 24 | const installExtensions = () => { 25 | if (process.env.NODE_ENV === 'development') { 26 | const installer = require('electron-devtools-installer'); // eslint-disable-line global-require 27 | 28 | const extensions = [ 29 | 'REACT_DEVELOPER_TOOLS', 30 | 'REDUX_DEVTOOLS' 31 | ]; 32 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 33 | return Promise.all(extensions.map(name => installer.default(installer[name], forceDownload))); 34 | } 35 | 36 | return Promise.resolve([]); 37 | }; 38 | 39 | app.on('ready', () => 40 | installExtensions() 41 | .then(() => { 42 | mainWindow = new BrowserWindow({ 43 | show: false, 44 | width: 1024, 45 | height: 728 46 | }); 47 | 48 | mainWindow.loadURL(`file://${__dirname}/app.html`); 49 | 50 | mainWindow.webContents.on('did-finish-load', () => { 51 | mainWindow.show(); 52 | mainWindow.focus(); 53 | }); 54 | 55 | mainWindow.on('closed', () => { 56 | mainWindow = null; 57 | }); 58 | 59 | if (process.env.NODE_ENV === 'development') { 60 | mainWindow.openDevTools(); 61 | mainWindow.webContents.on('context-menu', (e, props) => { 62 | const { x, y } = props; 63 | 64 | Menu.buildFromTemplate([{ 65 | label: 'Inspect element', 66 | click() { 67 | mainWindow.inspectElement(x, y); 68 | } 69 | }]).popup(mainWindow); 70 | }); 71 | } 72 | 73 | if (process.platform === 'darwin') { 74 | template = [{ 75 | label: 'Electron', 76 | submenu: [{ 77 | label: 'About ElectronReact', 78 | selector: 'orderFrontStandardAboutPanel:' 79 | }, { 80 | type: 'separator' 81 | }, { 82 | label: 'Services', 83 | submenu: [] 84 | }, { 85 | type: 'separator' 86 | }, { 87 | label: 'Hide ElectronReact', 88 | accelerator: 'Command+H', 89 | selector: 'hide:' 90 | }, { 91 | label: 'Hide Others', 92 | accelerator: 'Command+Shift+H', 93 | selector: 'hideOtherApplications:' 94 | }, { 95 | label: 'Show All', 96 | selector: 'unhideAllApplications:' 97 | }, { 98 | type: 'separator' 99 | }, { 100 | label: 'Quit', 101 | accelerator: 'Command+Q', 102 | click() { 103 | app.quit(); 104 | } 105 | }] 106 | }, { 107 | label: 'Edit', 108 | submenu: [{ 109 | label: 'Undo', 110 | accelerator: 'Command+Z', 111 | selector: 'undo:' 112 | }, { 113 | label: 'Redo', 114 | accelerator: 'Shift+Command+Z', 115 | selector: 'redo:' 116 | }, { 117 | type: 'separator' 118 | }, { 119 | label: 'Cut', 120 | accelerator: 'Command+X', 121 | selector: 'cut:' 122 | }, { 123 | label: 'Copy', 124 | accelerator: 'Command+C', 125 | selector: 'copy:' 126 | }, { 127 | label: 'Paste', 128 | accelerator: 'Command+V', 129 | selector: 'paste:' 130 | }, { 131 | label: 'Select All', 132 | accelerator: 'Command+A', 133 | selector: 'selectAll:' 134 | }] 135 | }, { 136 | label: 'View', 137 | submenu: (process.env.NODE_ENV === 'development') ? [{ 138 | label: 'Reload', 139 | accelerator: 'Command+R', 140 | click() { 141 | mainWindow.webContents.reload(); 142 | } 143 | }, { 144 | label: 'Toggle Full Screen', 145 | accelerator: 'Ctrl+Command+F', 146 | click() { 147 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 148 | } 149 | }, { 150 | label: 'Toggle Developer Tools', 151 | accelerator: 'Alt+Command+I', 152 | click() { 153 | mainWindow.toggleDevTools(); 154 | } 155 | }] : [{ 156 | label: 'Toggle Full Screen', 157 | accelerator: 'Ctrl+Command+F', 158 | click() { 159 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 160 | } 161 | }] 162 | }, { 163 | label: 'Window', 164 | submenu: [{ 165 | label: 'Minimize', 166 | accelerator: 'Command+M', 167 | selector: 'performMiniaturize:' 168 | }, { 169 | label: 'Close', 170 | accelerator: 'Command+W', 171 | selector: 'performClose:' 172 | }, { 173 | type: 'separator' 174 | }, { 175 | label: 'Bring All to Front', 176 | selector: 'arrangeInFront:' 177 | }] 178 | }, { 179 | label: 'Help', 180 | submenu: [{ 181 | label: 'Learn More', 182 | click() { 183 | shell.openExternal('http://electron.atom.io'); 184 | } 185 | }, { 186 | label: 'Documentation', 187 | click() { 188 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 189 | } 190 | }, { 191 | label: 'Community Discussions', 192 | click() { 193 | shell.openExternal('https://discuss.atom.io/c/electron'); 194 | } 195 | }, { 196 | label: 'Search Issues', 197 | click() { 198 | shell.openExternal('https://github.com/atom/electron/issues'); 199 | } 200 | }] 201 | }]; 202 | 203 | menu = Menu.buildFromTemplate(template); 204 | Menu.setApplicationMenu(menu); 205 | } else { 206 | template = [{ 207 | label: '&File', 208 | submenu: [{ 209 | label: '&Open', 210 | accelerator: 'Ctrl+O' 211 | }, { 212 | label: '&Close', 213 | accelerator: 'Ctrl+W', 214 | click() { 215 | mainWindow.close(); 216 | } 217 | }] 218 | }, { 219 | label: '&View', 220 | submenu: (process.env.NODE_ENV === 'development') ? [{ 221 | label: '&Reload', 222 | accelerator: 'Ctrl+R', 223 | click() { 224 | mainWindow.webContents.reload(); 225 | } 226 | }, { 227 | label: 'Toggle &Full Screen', 228 | accelerator: 'F11', 229 | click() { 230 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 231 | } 232 | }, { 233 | label: 'Toggle &Developer Tools', 234 | accelerator: 'Alt+Ctrl+I', 235 | click() { 236 | mainWindow.toggleDevTools(); 237 | } 238 | }] : [{ 239 | label: 'Toggle &Full Screen', 240 | accelerator: 'F11', 241 | click() { 242 | mainWindow.setFullScreen(!mainWindow.isFullScreen()); 243 | } 244 | }] 245 | }, { 246 | label: 'Help', 247 | submenu: [{ 248 | label: 'Learn More', 249 | click() { 250 | shell.openExternal('http://electron.atom.io'); 251 | } 252 | }, { 253 | label: 'Documentation', 254 | click() { 255 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); 256 | } 257 | }, { 258 | label: 'Community Discussions', 259 | click() { 260 | shell.openExternal('https://discuss.atom.io/c/electron'); 261 | } 262 | }, { 263 | label: 'Search Issues', 264 | click() { 265 | shell.openExternal('https://github.com/atom/electron/issues'); 266 | } 267 | }] 268 | }]; 269 | menu = Menu.buildFromTemplate(template); 270 | mainWindow.setMenu(menu); 271 | } 272 | })); 273 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-typescript-boilerplate", 3 | "productName": "electron-typescript-react-boilerplate", 4 | "version": "1.0.0", 5 | "description": "Electron application boilerplate based on TypeScript, React, React Router, Webpack, React Hot Loader for rapid application development", 6 | "main": "./main.js", 7 | "author": { 8 | "name": "Alexander Rath", 9 | "email": "irath96@gmail.com", 10 | "url": "https://github.com/irath96" 11 | }, 12 | "scripts": { 13 | "postinstall": "npm rebuild --runtime=electron --target=1.6.6 --disturl=https://atom.io/download/atom-shell --build-from-source" 14 | }, 15 | "license": "MIT", 16 | "dependencies": { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/reducers/counter.ts: -------------------------------------------------------------------------------- 1 | import { IAction } from '../actions/helpers'; 2 | import { increment, decrement } from '../actions/counter'; 3 | 4 | export type TState = number; 5 | 6 | export default function counter(state: number = 0, action: IAction) { 7 | if (increment.test(action)) { 8 | return state + 1; 9 | } else if (decrement.test(action)) { 10 | return state - 1; 11 | } 12 | 13 | return state; 14 | } 15 | -------------------------------------------------------------------------------- /app/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers, Reducer } from 'redux'; 2 | import { routerReducer as routing } from 'react-router-redux'; 3 | import counter, { TState as TCounterState } from './counter'; 4 | 5 | const rootReducer = combineReducers({ 6 | counter, 7 | routing: routing as Reducer 8 | }); 9 | 10 | export interface IState { 11 | counter: TCounterState; 12 | } 13 | 14 | export default rootReducer; 15 | -------------------------------------------------------------------------------- /app/routes.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Switch, Route } from 'react-router'; 3 | import App from './containers/App'; 4 | import HomePage from './containers/HomePage'; 5 | import CounterPage from './containers/CounterPage'; 6 | 7 | export default () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /app/store/configureStore.development.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { createHashHistory } from 'history'; 4 | import { routerMiddleware, push } from 'react-router-redux'; 5 | import { createLogger } from 'redux-logger'; 6 | import rootReducer from '../reducers'; 7 | 8 | import * as counterActions from '../actions/counter'; 9 | 10 | declare const window: Window & { 11 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?(a: any): void; 12 | }; 13 | 14 | declare const module: NodeModule & { 15 | hot?: { 16 | accept(...args: any[]): any; 17 | } 18 | }; 19 | 20 | const actionCreators = Object.assign({}, 21 | counterActions, 22 | {push} 23 | ); 24 | 25 | const logger = (createLogger)({ 26 | level: 'info', 27 | collapsed: true 28 | }); 29 | 30 | const history = createHashHistory(); 31 | const router = routerMiddleware(history); 32 | 33 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose 34 | /* eslint-disable no-underscore-dangle */ 35 | const composeEnhancers: typeof compose = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? 36 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 37 | // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html 38 | actionCreators 39 | }) as any : 40 | compose; 41 | /* eslint-enable no-underscore-dangle */ 42 | const enhancer = composeEnhancers( 43 | applyMiddleware(thunk, router, logger) 44 | ); 45 | 46 | export = { 47 | history, 48 | configureStore(initialState: Object | void) { 49 | const store = createStore(rootReducer, initialState, enhancer); 50 | 51 | if (module.hot) { 52 | module.hot.accept('../reducers', () => 53 | store.replaceReducer(require('../reducers')) // eslint-disable-line global-require 54 | ); 55 | } 56 | 57 | return store; 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /app/store/configureStore.production.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { createBrowserHistory } from 'history'; 4 | import { routerMiddleware } from 'react-router-redux'; 5 | import rootReducer from '../reducers'; 6 | 7 | const history = createBrowserHistory(); 8 | const router = routerMiddleware(history); 9 | const enhancer = applyMiddleware(thunk, router); 10 | 11 | export = { 12 | history, 13 | configureStore(initialState: Object | void) { 14 | return createStore(rootReducer, initialState, enhancer); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /app/store/configureStore.ts: -------------------------------------------------------------------------------- 1 | let configureStore: any; 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | configureStore = require('./configureStore.production'); 5 | } else { 6 | configureStore = require('./configureStore.development'); 7 | } 8 | 9 | export = configureStore; 10 | -------------------------------------------------------------------------------- /app/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/app/utils/.gitkeep -------------------------------------------------------------------------------- /erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/erb-logo.png -------------------------------------------------------------------------------- /internals/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react-typescript-boilerplate", 3 | "productName": "ElectronReactTypescript", 4 | "version": "0.10.0", 5 | "description": "Electron application boilerplate based on TypeScript, React, React Router, Webpack, React Hot Loader for rapid application development", 6 | "main": "main.js", 7 | "scripts": { 8 | "test": "cross-env NODE_ENV=test node --trace-warnings ./test/runTests.js", 9 | "test-all": "npm run test && npm run build && npm run test-e2e", 10 | "test-watch": "npm test -- --watch", 11 | "test-e2e": "cross-env NODE_ENV=test node --trace-warnings ./test/runTests.js e2e", 12 | "hot-server": "cross-env NODE_ENV=development node --max_old_space_size=2096 server.js", 13 | "build-main": "cross-env NODE_ENV=production node ./node_modules/webpack/bin/webpack --config webpack.config.electron.js --progress --profile --colors", 14 | "build-renderer": "cross-env NODE_ENV=production node ./node_modules/webpack/bin/webpack --config webpack.config.production.js --progress --profile --colors", 15 | "build": "npm run build-main && npm run build-renderer", 16 | "start": "cross-env NODE_ENV=production electron ./app/", 17 | "start-hot": "cross-env HOT=1 NODE_ENV=development electron ./app/main.development", 18 | "postinstall": "npm run build", 19 | "dev": "npm run hot-server -- --start-hot", 20 | "package": "npm run build && build --publish never", 21 | "package-win": "npm run build && build --win --x64", 22 | "package-linux": "npm run build && build --linux", 23 | "package-all": "npm run build && build -mwl", 24 | "cleanup": "mop -v" 25 | }, 26 | "jest": { 27 | "moduleNameMapper": { 28 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/internals/mocks/fileMock.js", 29 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 30 | }, 31 | "moduleFileExtensions": [ 32 | "ts", 33 | "tsx", 34 | "js" 35 | ], 36 | "moduleDirectories": [ 37 | "node_modules", 38 | "app/node_modules" 39 | ], 40 | "transform": { 41 | "^.+\\.(ts|tsx)$": "/test/preprocessor.js" 42 | }, 43 | "testMatch": [ 44 | "**/?(*.)(spec|test).ts?(x)" 45 | ] 46 | }, 47 | "build": { 48 | "productName": "ElectronReactTypescript", 49 | "appId": "io.github.irath96.ElectronReactTypescript", 50 | "dmg": { 51 | "contents": [ 52 | { 53 | "x": 410, 54 | "y": 150, 55 | "type": "link", 56 | "path": "/Applications" 57 | }, 58 | { 59 | "x": 130, 60 | "y": 150, 61 | "type": "file" 62 | } 63 | ] 64 | }, 65 | "files": [ 66 | "dist/", 67 | "node_modules/", 68 | "app.html", 69 | "main.js", 70 | "main.js.map", 71 | "package.json" 72 | ], 73 | "directories": { 74 | "buildResources": "resources", 75 | "output": "release" 76 | }, 77 | "win": { 78 | "target": "nsis" 79 | }, 80 | "linux": { 81 | "target": [ 82 | "deb", 83 | "AppImage" 84 | ] 85 | } 86 | }, 87 | "bin": { 88 | "electron": "./node_modules/.bin/electron" 89 | }, 90 | "repository": { 91 | "type": "git", 92 | "url": "git+https://github.com/irath96/electron-react-typescript-boilerplate.git" 93 | }, 94 | "author": { 95 | "name": "Alexander Rath", 96 | "email": "irath96@gmail.com", 97 | "url": "https://github.com/irath96" 98 | }, 99 | "license": "MIT", 100 | "bugs": { 101 | "url": "https://github.com/irath96/electron-react-typescript-boilerplate/issues" 102 | }, 103 | "keywords": [ 104 | "electron", 105 | "boilerplate", 106 | "react", 107 | "react-router", 108 | "flux", 109 | "webpack", 110 | "react-hot" 111 | ], 112 | "homepage": "https://github.com/irath96/electron-react-typescript-boilerplate#readme", 113 | "devDependencies": { 114 | "@types/enzyme": "^3.1.1", 115 | "@types/history": "^4.5.2", 116 | "@types/jest": "^22.0.0", 117 | "@types/node": "^8.0.2", 118 | "@types/react": "^16.0.5", 119 | "@types/react-dom": "16.0.3", 120 | "@types/react-hot-loader": "^3.0.4", 121 | "@types/react-redux": "^5.0.4", 122 | "@types/react-router": "^4.0.11", 123 | "@types/react-router-dom": "^4.0.7", 124 | "@types/react-router-redux": "^5.0.2", 125 | "@types/redux-logger": "^3.0.0", 126 | "@types/sinon": "^4.0.0", 127 | "asar": "^0.14.0", 128 | "boiler-room-custodian": "^0.6.2", 129 | "concurrently": "^3.1.0", 130 | "cross-env": "^5.0.1", 131 | "css-loader": "^0.28.4", 132 | "css-modules-require-hook": "^4.0.6", 133 | "devtron": "^1.4.0", 134 | "electron": "1.8.4", 135 | "electron-builder": "^19.8.0", 136 | "electron-builder-http": "^19.15.0", 137 | "electron-devtools-installer": "^2.0.1", 138 | "enzyme": "^3.0.0", 139 | "express": "^4.14.0", 140 | "extract-text-webpack-plugin": "^3.0.0", 141 | "file-loader": "^1.1.5", 142 | "html-webpack-plugin": "^2.24.1", 143 | "identity-obj-proxy": "^3.0.0", 144 | "jest": "^22.0.4", 145 | "json-loader": "^0.5.4", 146 | "node-sass": "^4.1.1", 147 | "react-hot-loader": "^3.0.0-beta.6", 148 | "react-test-renderer": "^16.0.0", 149 | "enzyme-adapter-react-16": "^1.0.0", 150 | "redux-logger": "^3.0.6", 151 | "sass-loader": "^6.0.6", 152 | "sinon": "^4.0.0", 153 | "spectron": "^3.4.1", 154 | "style-loader": "^0.19.0", 155 | "ts-loader": "^3.1.0", 156 | "ts-node": "^4.1.0", 157 | "tslint": "^5.4.3", 158 | "typescript": "^2.4.1", 159 | "url-loader": "^0.6.1", 160 | "webpack": "^3.3.0", 161 | "webpack-dev-middleware": "^2.0.3", 162 | "webpack-hot-middleware": "^2.13.2", 163 | "webpack-merge": "^4.1.1" 164 | }, 165 | "dependencies": { 166 | "electron-debug": "^1.1.0", 167 | "font-awesome": "^4.7.0", 168 | "history": "^4.6.1", 169 | "react": "^16.0.0", 170 | "react-dom": "^16.0.0", 171 | "react-redux": "^5.0.1", 172 | "react-router": "^4.1.1", 173 | "react-router-dom": "^4.1.1", 174 | "react-router-redux": "^5.0.0-alpha.6", 175 | "redux": "^3.6.0", 176 | "redux-thunk": "^2.1.0", 177 | "source-map-support": "^0.5.0" 178 | }, 179 | "devEngines": { 180 | "node": ">=6.x", 181 | "npm": ">=3.x" 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icon.icns -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icon.png -------------------------------------------------------------------------------- /resources/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/1024x1024.png -------------------------------------------------------------------------------- /resources/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/128x128.png -------------------------------------------------------------------------------- /resources/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/16x16.png -------------------------------------------------------------------------------- /resources/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/24x24.png -------------------------------------------------------------------------------- /resources/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/256x256.png -------------------------------------------------------------------------------- /resources/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/32x32.png -------------------------------------------------------------------------------- /resources/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/48x48.png -------------------------------------------------------------------------------- /resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/512x512.png -------------------------------------------------------------------------------- /resources/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/64x64.png -------------------------------------------------------------------------------- /resources/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iRath96/electron-react-typescript-boilerplate/7b0911efa9a6e36fb129d1656ad6d8c866b4a17d/resources/icons/96x96.png -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Setup and run the development server for Hot-Module-Replacement 3 | * https://webpack.github.io/docs/hot-module-replacement-with-webpack.html 4 | */ 5 | 6 | const express = require('express'); 7 | const webpack = require('webpack'); 8 | const webpackDevMiddleware = require('webpack-dev-middleware'); 9 | const webpackHotMiddleware = require('webpack-hot-middleware'); 10 | const { spawn } = require('child_process'); 11 | 12 | const config = require('./webpack.config.development'); 13 | 14 | const argv = require('minimist')(process.argv.slice(2)); 15 | 16 | const app = express(); 17 | const compiler = webpack(config); 18 | const PORT = process.env.PORT || 3000; 19 | 20 | const wdm = webpackDevMiddleware(compiler, { 21 | publicPath: config.output.publicPath, 22 | stats: { 23 | colors: true 24 | } 25 | }); 26 | 27 | app.use(wdm); 28 | 29 | app.use(webpackHotMiddleware(compiler)); 30 | 31 | const server = app.listen(PORT, 'localhost', serverError => { 32 | if (serverError) { 33 | return console.error(serverError); 34 | } 35 | 36 | if (argv['start-hot']) { 37 | spawn('npm', ['run', 'start-hot'], { shell: true, env: process.env, stdio: 'inherit' }) 38 | .on('close', code => process.exit(code)) 39 | .on('error', spawnError => console.error(spawnError)); 40 | } 41 | 42 | console.log(`Listening at http://localhost:${PORT}`); 43 | }); 44 | 45 | process.on('SIGTERM', () => { 46 | console.log('Stopping dev server'); 47 | wdm.close(); 48 | server.close(() => { 49 | process.exit(0); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /setup.js: -------------------------------------------------------------------------------- 1 | // Note: this file is the configuration file *only* for 2 | // use by the boiler-room-custodian utility. the point 3 | // of the boiler-room-custodian is to clean sample code 4 | // from the boilerplate from initial state so that you 5 | // can start custom development on a "blank project" 6 | // 7 | // For more information or to report issues please go 8 | // to https://github.com/tstringer/boiler-room-custodian 9 | // 10 | // This file should remain unmodified by end users and 11 | // should only be invoked by running `npm run cleanup` 12 | module.exports = { 13 | // remove the following files as they are mostly 14 | // related to the sample counter page and functionality 15 | remove: [ 16 | { file: 'app/actions/counter.ts' }, 17 | { file: 'app/components/Counter.scss' }, 18 | { file: 'app/components/Counter.tsx' }, 19 | { file: 'app/containers/CounterPage.tsx' }, 20 | { file: 'app/reducers/counter.ts' }, 21 | { file: 'test/actions/counter.spec.ts' }, 22 | { file: 'test/components/Counter.spec.tsx' }, 23 | { file: 'test/containers/CounterPage.spec.tsx' }, 24 | { file: 'test/reducers/counter.spec.ts' }, 25 | { file: 'erb-logo.png' } 26 | ], 27 | // clean the following files by either clearing them 28 | // (by specifying {clear: true}) or by removing lines 29 | // that match a regex pattern 30 | clean: [ 31 | { 32 | file: 'app/reducers/index.ts', 33 | pattern: /counter/ 34 | }, 35 | { 36 | file: 'app/store/configureStore.development.ts', 37 | pattern: /counterActions/ 38 | }, 39 | { 40 | file: 'app/app.global.scss', 41 | clear: true 42 | }, 43 | { 44 | file: 'app/routes.tsx', 45 | pattern: /CounterPage/ 46 | }, 47 | { 48 | file: 'test/e2e.ts', 49 | clear: true 50 | }, 51 | { 52 | file: 'README.md', 53 | clear: true 54 | }, 55 | { 56 | file: 'app/components/Home.tsx', 57 | pattern: /(h2|Link)/ 58 | } 59 | ], 60 | // add the following files to the project, mostly 61 | // related to .gitkeep for version control 62 | add: [ 63 | { file: 'app/actions/.gitkeep' }, 64 | { file: 'test/actions/.gitkeep' }, 65 | { file: 'test/components/.gitkeep' }, 66 | { file: 'test/containers/.gitkeep' }, 67 | { file: 'test/reducers/.gitkeep' } 68 | ] 69 | }; 70 | -------------------------------------------------------------------------------- /test/actions/__snapshots__/counter.spec.ts.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/actions/counter.spec.ts: -------------------------------------------------------------------------------- 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.type })).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.type })).toBe(true); 38 | done(); 39 | }, 5); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/components/Counter.spec.tsx: -------------------------------------------------------------------------------- 1 | import '../utils/enzymeConfig'; 2 | 3 | import { spy } from 'sinon'; 4 | import * as React from 'react'; 5 | import { shallow } from 'enzyme'; 6 | import Counter from '../../app/components/Counter'; 7 | 8 | const CounterAny = Counter as any; 9 | 10 | function setup() { 11 | const actions = { 12 | increment: spy(), 13 | incrementIfOdd: spy(), 14 | incrementAsync: spy(), 15 | decrement: spy() 16 | }; 17 | const component = shallow(); 20 | return { 21 | component, 22 | actions, 23 | buttons: component.find('button'), 24 | p: component.find('.counter') 25 | }; 26 | } 27 | 28 | describe('Counter component', () => { 29 | it('should should display count', () => { 30 | const { p } = setup(); 31 | expect(p.text()).toMatch(/^1$/); 32 | }); 33 | 34 | it('should first button should call increment', () => { 35 | const { buttons, actions } = setup(); 36 | buttons.at(0).simulate('click'); 37 | expect(actions.increment.called).toBe(true); 38 | }); 39 | 40 | it('should second button should call decrement', () => { 41 | const { buttons, actions } = setup(); 42 | buttons.at(1).simulate('click'); 43 | expect(actions.decrement.called).toBe(true); 44 | }); 45 | 46 | it('should third button should call incrementIfOdd', () => { 47 | const { buttons, actions } = setup(); 48 | buttons.at(2).simulate('click'); 49 | expect(actions.incrementIfOdd.called).toBe(true); 50 | }); 51 | 52 | it('should fourth button should call incrementAsync', () => { 53 | const { buttons, actions } = setup(); 54 | buttons.at(3).simulate('click'); 55 | expect(actions.incrementAsync.called).toBe(true); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/containers/CounterPage.spec.tsx: -------------------------------------------------------------------------------- 1 | import '../utils/enzymeConfig'; 2 | 3 | import * as React from 'react'; 4 | import { mount } from 'enzyme'; 5 | import { Provider } from 'react-redux'; 6 | import { ConnectedRouter } from 'react-router-redux'; 7 | import CounterPage from '../../app/containers/CounterPage'; 8 | import { IState } from '../../app/reducers'; 9 | 10 | const CounterPageAny = CounterPage as any; 11 | let { configureStore, history } = require('../../app/store/configureStore'); 12 | 13 | function setup(initialState?: IState) { 14 | const store = configureStore(initialState); 15 | const app = mount( 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | return { 23 | app, 24 | buttons: app.find('button'), 25 | p: app.find('.counter') 26 | }; 27 | } 28 | 29 | describe('containers', () => { 30 | describe('App', () => { 31 | it('should display initial count', () => { 32 | const { p } = setup(); 33 | expect(p.text()).toMatch(/^0$/); 34 | }); 35 | 36 | it('should display updated count after increment button click', () => { 37 | const { buttons, p } = setup(); 38 | buttons.at(0).simulate('click'); 39 | expect(p.text()).toMatch(/^1$/); 40 | }); 41 | 42 | it('should display updated count after descrement button click', () => { 43 | const { buttons, p } = setup(); 44 | buttons.at(1).simulate('click'); 45 | expect(p.text()).toMatch(/^-1$/); 46 | }); 47 | 48 | it('shouldnt change if even and if odd button clicked', () => { 49 | const { buttons, p } = setup(); 50 | buttons.at(2).simulate('click'); 51 | expect(p.text()).toMatch(/^0$/); 52 | }); 53 | 54 | it('should change if odd and if odd button clicked', () => { 55 | const { buttons, p } = setup({ counter: 1 }); 56 | buttons.at(2).simulate('click'); 57 | expect(p.text()).toMatch(/^2$/); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/e2e/e2e.spec.ts: -------------------------------------------------------------------------------- 1 | import * as electronPath from 'electron'; 2 | import * as path from 'path'; 3 | 4 | const { Application } = require('spectron'); 5 | 6 | const delay = (time: number) => new Promise(resolve => setTimeout(resolve, time)); 7 | 8 | describe('main window', function spec() { 9 | let app: any; 10 | beforeAll(async () => { 11 | app = new Application({ 12 | path: electronPath, 13 | args: [path.join(__dirname, '..', '..', 'app')], 14 | }); 15 | return app.start(); 16 | }); 17 | 18 | afterAll(() => { 19 | if (app && app.isRunning()) { 20 | return app.stop(); 21 | } 22 | }); 23 | 24 | const findCounter = () => app.client.element('[data-tid="counter"]'); 25 | 26 | const findButtons = async () => { 27 | const { value } = await app.client.elements('[data-tclass="btn"]'); 28 | return value.map((btn: any) => btn.ELEMENT); 29 | }; 30 | 31 | it('should open window', async () => { 32 | const { client, browserWindow } = app; 33 | 34 | await client.waitUntilWindowLoaded(); 35 | await delay(500); 36 | const title = await browserWindow.getTitle(); 37 | expect(title).toBe('Hello Electron React!'); 38 | }); 39 | 40 | it('should not have any logs in console of main window', async () => { 41 | const { client } = app; 42 | const logs = await client.getRenderProcessLogs(); 43 | // Print renderer process logs 44 | logs.forEach((log: any) => { 45 | console.log(log.message); 46 | console.log(log.source); 47 | console.log(log.level); 48 | }); 49 | expect(logs).toHaveLength(0); 50 | }); 51 | 52 | it('should navigate to Counter by "to Counter" link', async () => { 53 | const { client } = app; 54 | 55 | await client.click('[data-tid="container"] > a'); 56 | await delay(100); 57 | expect(await findCounter().getText()).toBe('0'); 58 | }); 59 | 60 | it('should display updated count after increment button click', async () => { 61 | const { client } = 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 } = app; 70 | 71 | const buttons = await findButtons(); 72 | await client.elementIdClick(buttons[1]); // - 73 | expect(await findCounter().getText()).toBe('0'); 74 | }); 75 | 76 | it('shouldn\'t change if even and if odd button clicked', async () => { 77 | const { client } = 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 } = 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 } = app; 95 | 96 | const buttons = await findButtons(); 97 | await client.elementIdClick(buttons[3]); // async 98 | expect(await findCounter().getText()).toBe('2'); 99 | await delay(1000); 100 | expect(await findCounter().getText()).toBe('3'); 101 | }); 102 | 103 | it('should navigate to home using back button', async () => { 104 | const { client } = app; 105 | await client.element( 106 | '[data-tid="backButton"] > a' 107 | ).click(); 108 | await delay(100); 109 | 110 | expect( 111 | await client.isExisting('[data-tid="container"]') 112 | ).toBe(true); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/example.ts: -------------------------------------------------------------------------------- 1 | describe('description', () => { 2 | it('should have description', () => { 3 | expect(1 + 2).toBe(3); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /test/preprocessor.js: -------------------------------------------------------------------------------- 1 | const tsc = require('typescript'); 2 | const tsConfig = require('../tsconfig.json'); 3 | 4 | module.exports = { 5 | process(src, path) { 6 | if (path.endsWith('.ts') || path.endsWith('.tsx')) { 7 | return tsc.transpile(src, tsConfig.compilerOptions, path, []); 8 | } 9 | return src; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/reducers/counter.spec.ts: -------------------------------------------------------------------------------- 1 | import counter from '../../app/reducers/counter'; 2 | import { increment, decrement } from '../../app/actions/counter'; 3 | 4 | describe('reducers', () => { 5 | describe('counter', () => { 6 | it('should handle initial state', () => { 7 | expect(counter(undefined, { type: 'unknown' })).toBe(0); 8 | }); 9 | 10 | it('should handle INCREMENT_COUNTER', () => { 11 | expect(counter(1, increment())).toBe(2); 12 | }); 13 | 14 | it('should handle DECREMENT_COUNTER', () => { 15 | expect(counter(1, decrement())).toBe(0); 16 | }); 17 | 18 | it('should handle unknown action type', () => { 19 | expect(counter(1, { type: 'unknown' })).toBe(1); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/runTests.js: -------------------------------------------------------------------------------- 1 | const spawn = require('cross-spawn'); 2 | const path = require('path'); 3 | 4 | const s = `\\${path.sep}`; 5 | const pattern = process.argv[2] === 'e2e' 6 | ? `test${s}e2e${s}.+\\.spec\\.tsx?` 7 | : `test${s}(?!e2e${s})[^${s}]+${s}.+\\.spec\\.tsx?$`; 8 | 9 | const result = spawn.sync(path.normalize('./node_modules/.bin/jest'), [pattern], { stdio: 'inherit' }); 10 | 11 | process.exit(result.status); 12 | -------------------------------------------------------------------------------- /test/utils/enzymeConfig.ts: -------------------------------------------------------------------------------- 1 | const { configure } = require('enzyme'); 2 | const Adapter = require('enzyme-adapter-react-16'); 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "moduleResolution": "node", 5 | "module": "commonjs", 6 | "jsx": "react", 7 | "lib": [ 8 | "dom", 9 | "es6", 10 | "dom.iterable", 11 | "scripthost" 12 | ], 13 | "types": [], 14 | 15 | "alwaysStrict": true, 16 | "strictNullChecks": true, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noUnusedLocals": true, 21 | 22 | "experimentalDecorators": true, 23 | "emitDecoratorMetadata": true, 24 | 25 | "sourceMap": true, 26 | 27 | "outDir": "dist" 28 | }, 29 | "files": [ 30 | "app/index.tsx" 31 | ], 32 | "include": [ 33 | "app/**/*.ts", 34 | "app/**/*.tsx", 35 | "test/**/*.ts", 36 | "test/**/*.tsx", 37 | "node_modules/@types/**/*.d.ts" 38 | ], 39 | "exclude": [ 40 | "dist" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | const path = require('path'); 6 | const { 7 | dependencies: externals 8 | } = require('./app/package.json'); 9 | 10 | module.exports = { 11 | module: { 12 | loaders: [{ 13 | test: /\.tsx?$/, 14 | loaders: ['react-hot-loader/webpack', 'ts-loader'], 15 | exclude: /node_modules/ 16 | }, { 17 | test: /\.json$/, 18 | loader: 'json-loader' 19 | }] 20 | }, 21 | 22 | output: { 23 | path: path.join(__dirname, 'app'), 24 | filename: 'bundle.js', 25 | 26 | // https://github.com/webpack/webpack/issues/1114 27 | libraryTarget: 'commonjs2' 28 | }, 29 | 30 | // https://webpack.github.io/docs/configuration.html#resolve 31 | resolve: { 32 | extensions: ['.js', '.ts', '.tsx', '.json'], 33 | modules: [ 34 | path.join(__dirname, 'app'), 35 | 'node_modules', 36 | ] 37 | }, 38 | 39 | plugins: [], 40 | 41 | externals: Object.keys(externals || {}) 42 | }; 43 | -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | /** 3 | * Build config for development process that uses Hot-Module-Replacement 4 | * https://webpack.github.io/docs/hot-module-replacement-with-webpack.html 5 | */ 6 | 7 | const webpack = require('webpack'); 8 | const merge = require('webpack-merge'); 9 | const baseConfig = require('./webpack.config.base'); 10 | 11 | const port = process.env.PORT || 3000; 12 | 13 | module.exports = merge(baseConfig, { 14 | devtool: 'inline-source-map', 15 | 16 | entry: [ 17 | 'react-hot-loader/patch', 18 | `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr&reload=true`, 19 | './app/index' 20 | ], 21 | 22 | output: { 23 | publicPath: `http://localhost:${port}/dist/` 24 | }, 25 | 26 | module: { 27 | // preLoaders: [ 28 | // { 29 | // test: /\.js$/, 30 | // loader: 'eslint-loader', 31 | // exclude: /node_modules/ 32 | // } 33 | // ], 34 | loaders: [ 35 | { 36 | test: /\.global\.css$/, 37 | loaders: [ 38 | 'style-loader', 39 | 'css-loader?sourceMap' 40 | ] 41 | }, 42 | 43 | { 44 | test: /^((?!\.global).)*\.css$/, 45 | loaders: [ 46 | 'style-loader', 47 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 48 | ] 49 | }, 50 | 51 | // Add SASS support - compile all .global.scss files and pipe it to style.css 52 | { 53 | test: /\.global\.scss$/, 54 | use: [ 55 | { 56 | loader: 'style-loader' 57 | }, 58 | { 59 | loader: 'css-loader', 60 | options: { 61 | sourceMap: true, 62 | }, 63 | }, 64 | { 65 | loader: 'sass-loader' 66 | } 67 | ] 68 | }, 69 | // Add SASS support - compile all other .scss files and pipe it to style.css 70 | { 71 | test: /^((?!\.global).)*\.scss$/, 72 | use: [ 73 | { 74 | loader: 'style-loader' 75 | }, 76 | { 77 | loader: 'css-loader', 78 | options: { 79 | modules: true, 80 | sourceMap: true, 81 | importLoaders: 1, 82 | localIdentName: '[name]__[local]__[hash:base64:5]', 83 | } 84 | }, 85 | { 86 | loader: 'sass-loader' 87 | } 88 | ] 89 | }, 90 | 91 | // WOFF Font 92 | { 93 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 94 | use: { 95 | loader: 'url-loader', 96 | options: { 97 | limit: 10000, 98 | mimetype: 'application/font-woff', 99 | } 100 | }, 101 | }, 102 | // WOFF2 Font 103 | { 104 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 105 | use: { 106 | loader: 'url-loader', 107 | options: { 108 | limit: 10000, 109 | mimetype: 'application/font-woff', 110 | } 111 | } 112 | }, 113 | // TTF Font 114 | { 115 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 116 | use: { 117 | loader: 'url-loader', 118 | options: { 119 | limit: 10000, 120 | mimetype: 'application/octet-stream' 121 | } 122 | } 123 | }, 124 | // EOT Font 125 | { 126 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 127 | use: 'file-loader', 128 | }, 129 | // SVG Font 130 | { 131 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 132 | use: { 133 | loader: 'url-loader', 134 | options: { 135 | limit: 10000, 136 | mimetype: 'image/svg+xml', 137 | } 138 | } 139 | }, 140 | // Common Image Formats 141 | { 142 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 143 | use: 'url-loader', 144 | } 145 | ] 146 | }, 147 | 148 | plugins: [ 149 | // https://webpack.github.io/docs/hot-module-replacement-with-webpack.html 150 | new webpack.HotModuleReplacementPlugin(), 151 | 152 | new webpack.NoEmitOnErrorsPlugin(), 153 | 154 | // NODE_ENV should be production so that modules do not perform certain development checks 155 | new webpack.DefinePlugin({ 156 | 'process.env.NODE_ENV': JSON.stringify('development') 157 | }), 158 | 159 | new webpack.LoaderOptionsPlugin({ 160 | debug: true 161 | }), 162 | ], 163 | 164 | // https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works 165 | target: 'electron-renderer' 166 | }); 167 | -------------------------------------------------------------------------------- /webpack.config.electron.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron 'Main Process' file 3 | */ 4 | 5 | const webpack = require('webpack'); 6 | const merge = require('webpack-merge'); 7 | const baseConfig = require('./webpack.config.base'); 8 | 9 | module.exports = merge(baseConfig, { 10 | devtool: 'source-map', 11 | 12 | entry: ['./app/main.development'], 13 | 14 | // 'main.js' in root 15 | output: { 16 | path: __dirname, 17 | filename: './app/main.js' 18 | }, 19 | 20 | plugins: [ 21 | // Add source map support for stack traces in node 22 | // https://github.com/evanw/node-source-map-support 23 | // new webpack.BannerPlugin( 24 | // 'require("source-map-support").install();', 25 | // { raw: true, entryOnly: false } 26 | // ), 27 | new webpack.DefinePlugin({ 28 | 'process.env': { 29 | NODE_ENV: JSON.stringify('production') 30 | } 31 | }) 32 | ], 33 | 34 | /** 35 | * Set target to Electron specific node.js env. 36 | * https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works 37 | */ 38 | target: 'electron-main', 39 | 40 | /** 41 | * Disables webpack processing of __dirname and __filename. 42 | * If you run the bundle in node.js it falls back to these values of node.js. 43 | * https://github.com/webpack/webpack/issues/2010 44 | */ 45 | node: { 46 | __dirname: false, 47 | __filename: false 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./webpack.config.development'); 2 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron 'Renderer Process' file 3 | */ 4 | 5 | const path = require('path'); 6 | const webpack = require('webpack'); 7 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | const merge = require('webpack-merge'); 9 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 10 | const baseConfig = require('./webpack.config.base'); 11 | 12 | module.exports = merge(baseConfig, { 13 | devtool: 'cheap-module-source-map', 14 | 15 | entry: [ 16 | './app/index' 17 | ], 18 | 19 | output: { 20 | path: path.join(__dirname, 'app/dist'), 21 | publicPath: '../dist/' 22 | }, 23 | 24 | module: { 25 | loaders: [ 26 | // Extract all .global.css to style.css as is 27 | { 28 | test: /\.(scss|sass)$/, 29 | use: ExtractTextPlugin.extract({ 30 | use: [{ 31 | loader: 'css-loader', 32 | options: { 33 | //modules: true, 34 | importLoaders: 1, 35 | localIdentName: '[name]__[local]__[hash:base64:5]', 36 | } 37 | }, 38 | { 39 | loader: 'sass-loader' 40 | }] 41 | }) 42 | }, 43 | 44 | // WOFF Font 45 | { 46 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 47 | use: { 48 | loader: 'url-loader', 49 | options: { 50 | limit: 10000, 51 | mimetype: 'application/font-woff', 52 | } 53 | }, 54 | }, 55 | // WOFF2 Font 56 | { 57 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 58 | use: { 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | mimetype: 'application/font-woff', 63 | } 64 | } 65 | }, 66 | // TTF Font 67 | { 68 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 69 | use: { 70 | loader: 'url-loader', 71 | options: { 72 | limit: 10000, 73 | mimetype: 'application/octet-stream' 74 | } 75 | } 76 | }, 77 | // EOT Font 78 | { 79 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 80 | use: 'file-loader', 81 | }, 82 | // SVG Font 83 | { 84 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 85 | use: { 86 | loader: 'url-loader', 87 | options: { 88 | limit: 10000, 89 | mimetype: 'image/svg+xml', 90 | } 91 | } 92 | }, 93 | // Common Image Formats 94 | { 95 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 96 | use: 'url-loader', 97 | } 98 | ] 99 | }, 100 | 101 | plugins: [ 102 | // https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin 103 | // https://github.com/webpack/webpack/issues/864 104 | new webpack.optimize.OccurrenceOrderPlugin(), 105 | 106 | // NODE_ENV should be production so that modules do not perform certain development checks 107 | new webpack.DefinePlugin({ 108 | 'process.env.NODE_ENV': JSON.stringify('production') 109 | }), 110 | 111 | new ExtractTextPlugin('style.css'), 112 | 113 | new HtmlWebpackPlugin({ 114 | filename: '../app.html', 115 | template: 'app/app.html', 116 | inject: false 117 | }) 118 | ], 119 | 120 | // https://github.com/chentsulin/webpack-target-electron-renderer#how-this-module-works 121 | target: 'electron-renderer' 122 | }); 123 | -------------------------------------------------------------------------------- /webpack.config.test.js: -------------------------------------------------------------------------------- 1 | /** Used in .babelrc for 'test' environment */ 2 | 3 | const devConfig = require('./webpack.config.development'); 4 | 5 | module.exports = { 6 | output: { 7 | libraryTarget: 'commonjs2' 8 | }, 9 | module: { 10 | // Use base + development loaders, but exclude 'babel-loader' 11 | loaders: devConfig.module.loaders.slice(1) 12 | } 13 | }; 14 | --------------------------------------------------------------------------------