├── tsconfig.prod.json
├── .babelrc
├── images.d.ts
├── public
├── favicon.ico
├── manifest.json
└── index.html
├── app
├── renderer
│ ├── index.css
│ ├── reducers
│ │ ├── index.ts
│ │ └── counter.ts
│ ├── actions
│ │ └── counter
│ │ │ ├── action_type.ts
│ │ │ └── index.ts
│ ├── components
│ │ ├── NavBar
│ │ │ └── index.tsx
│ │ ├── Home
│ │ │ ├── home.css
│ │ │ ├── index.tsx
│ │ │ └── logo.svg
│ │ └── Counter
│ │ │ └── index.tsx
│ ├── types
│ │ └── index.ts
│ ├── containers
│ │ ├── App.tsx
│ │ ├── App.css
│ │ ├── App.test.tsx
│ │ └── counter.tsx
│ ├── routes
│ │ └── index.tsx
│ ├── index.tsx
│ ├── store
│ │ └── index.ts
│ └── registerServiceWorker.ts
└── main
│ ├── windows
│ └── mainWindow.ts
│ ├── tray.ts
│ ├── menu.ts
│ └── main.ts
├── assets
└── electron.png
├── tsconfig.test.json
├── config
├── jest
│ ├── typescriptTransform.js
│ ├── fileTransform.js
│ └── cssTransform.js
├── polyfills.js
├── webpack.config.electron.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── .gitignore
├── README.md
├── tsconfig.json
├── scripts
├── test.js
├── start.js
└── build.js
├── LICENSE
├── tslint.json
└── package.json
/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json"
3 | }
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env", "react-app"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/images.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg'
2 | declare module '*.png'
3 | declare module '*.jpg'
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leskd/electron-react-typescript/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/app/renderer/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/assets/electron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leskd/electron-react-typescript/HEAD/assets/electron.png
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------
/app/renderer/reducers/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import counter from './counter';
3 |
4 | const rootReducers = combineReducers({
5 | counter
6 | });
7 |
8 | export default rootReducers;
9 |
--------------------------------------------------------------------------------
/app/renderer/actions/counter/action_type.ts:
--------------------------------------------------------------------------------
1 | export const INCREMENT = 'increment';
2 | export type INCREMENT = typeof INCREMENT;
3 |
4 | export const DECREMENT = 'DECREMENT';
5 | export type DECREMENT = typeof DECREMENT;
6 |
--------------------------------------------------------------------------------
/config/jest/typescriptTransform.js:
--------------------------------------------------------------------------------
1 | // Copyright 2004-present Facebook. All Rights Reserved.
2 |
3 | 'use strict';
4 |
5 | const tsJestPreprocessor = require('ts-jest/preprocessor');
6 |
7 | module.exports = tsJestPreprocessor;
8 |
--------------------------------------------------------------------------------
/app/renderer/components/NavBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const NavBar = () => (
5 |
8 | );
9 |
10 | export default NavBar;
11 |
--------------------------------------------------------------------------------
/app/renderer/types/index.ts:
--------------------------------------------------------------------------------
1 | import { RouterState } from 'connected-react-router';
2 |
3 | export interface RouterState {
4 | router: RouterState;
5 | }
6 |
7 | export interface CounterState {
8 | count: number;
9 | }
10 |
11 | export interface StoreState {
12 | counter: CounterState;
13 | }
14 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/en/webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 | /package
12 | /dist
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | /main.js
25 | /package-lock.json
26 |
--------------------------------------------------------------------------------
/app/renderer/actions/counter/index.ts:
--------------------------------------------------------------------------------
1 | import { INCREMENT, DECREMENT } from './action_type';
2 |
3 | export type counterActions = Increment | Decrement;
4 |
5 | interface Increment {
6 | type: INCREMENT;
7 | }
8 |
9 | export function increment(): Increment {
10 | return {
11 | type: INCREMENT,
12 | };
13 | }
14 |
15 | interface Decrement {
16 | type: DECREMENT;
17 | }
18 |
19 | export function decrement(): Decrement {
20 | return {
21 | type: DECREMENT
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/app/renderer/containers/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { RouterState } from '../types';
4 | import Routes from '../routes';
5 |
6 | class App extends Component {
7 | render() {
8 | return (
9 |
10 | );
11 | }
12 | }
13 |
14 | function mapStateToProps(state: RouterState) {
15 | return {
16 | location: state.router.location.pathname
17 | };
18 | }
19 |
20 | export default connect(mapStateToProps)(App);
21 |
--------------------------------------------------------------------------------
/app/renderer/containers/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from { transform: rotate(0deg); }
27 | to { transform: rotate(360deg); }
28 | }
29 |
--------------------------------------------------------------------------------
/app/renderer/components/Home/home.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from { transform: rotate(0deg); }
27 | to { transform: rotate(360deg); }
28 | }
29 |
--------------------------------------------------------------------------------
/app/renderer/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Route, Switch } from 'react-router';
3 | import Home from '../components/Home';
4 | import Counter from '../containers/counter';
5 | import NavBar from '../components/NavBar';
6 |
7 | export default function Routes() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/app/renderer/components/Counter/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | interface CounterProps {
4 | count: number;
5 | increment: () => void;
6 | decrement: () => void;
7 | }
8 |
9 | export default class Counter extends Component {
10 | render() {
11 | const { count, increment, decrement } = this.props;
12 |
13 | return (
14 |
15 |
Counter: {count}
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/renderer/components/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 | import './home.css';
4 | export default class Home extends Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 | Welcome to React
11 |
12 |
13 | To get started, edit renderer/containers/App.tsx and save to reload.
14 |
15 |
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/renderer/containers/App.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import App from './App';
4 | import {configureStore, history} from '../store';
5 | import {ConnectedRouter} from 'connected-react-router';
6 | import {Provider} from 'react-redux';
7 |
8 | it('renders without crashing', () => {
9 | const div = document.createElement('div');
10 | const store = configureStore();
11 | ReactDOM.render(
12 |
13 |
14 |
15 | , div);
16 | ReactDOM.unmountComponentAtNode(div);
17 | });
18 |
--------------------------------------------------------------------------------
/app/renderer/containers/counter.tsx:
--------------------------------------------------------------------------------
1 | import { bindActionCreators, Dispatch } from 'redux';
2 | import { connect } from 'react-redux';
3 | import { StoreState } from '../types';
4 | import Counter from '../components/Counter';
5 | import { counterActions, increment, decrement } from '../actions/counter';
6 |
7 | function mapStateToProps(state: StoreState) {
8 | return {
9 | count: state.counter.count
10 | };
11 | }
12 |
13 | function mapDispatchToProps(dispatch: Dispatch) {
14 | return bindActionCreators({increment, decrement}, dispatch);
15 | }
16 |
17 | export default connect(mapStateToProps, mapDispatchToProps)(Counter);
18 |
--------------------------------------------------------------------------------
/app/renderer/reducers/counter.ts:
--------------------------------------------------------------------------------
1 | import { INCREMENT, DECREMENT } from '../actions/counter/action_type';
2 | import { counterActions } from '../actions/counter';
3 | import { CounterState } from '../types';
4 |
5 | const INITIAL_STATE = {
6 | count: 1
7 | };
8 |
9 | export default function counter(state = INITIAL_STATE, action: counterActions): CounterState {
10 | switch (action.type) {
11 | case INCREMENT:
12 | return {
13 | count: state.count + 1
14 | };
15 |
16 | case DECREMENT:
17 | return {
18 | count: state.count - 1
19 | };
20 |
21 | default:
22 | return state;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/main/windows/mainWindow.ts:
--------------------------------------------------------------------------------
1 | import { BrowserWindow } from 'electron';
2 |
3 | export default class MainWindow {
4 | mainWindow: BrowserWindow;
5 | constructor() {
6 | this.createMainWindow();
7 | }
8 |
9 | createMainWindow() {
10 | this.mainWindow = new BrowserWindow({
11 | height: 600,
12 | width: 800,
13 | show: false
14 | });
15 | }
16 |
17 | loadURL(url: string) {
18 | this.mainWindow.loadURL(url);
19 | // this.mainWindow.webContents.openDevTools();
20 | }
21 |
22 | openDevTools() {
23 | this.mainWindow.webContents.openDevTools();
24 | }
25 |
26 | show() {
27 | this.mainWindow.show();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/renderer/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { ConnectedRouter } from 'connected-react-router';
5 | import { configureStore, history } from './store';
6 | import App from './containers/App';
7 | import './index.css';
8 | import registerServiceWorker from './registerServiceWorker';
9 |
10 | const store = configureStore();
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 | ,
17 | document.getElementById('root') as HTMLElement
18 | );
19 | registerServiceWorker();
20 |
--------------------------------------------------------------------------------
/app/main/tray.ts:
--------------------------------------------------------------------------------
1 | import { Tray, Menu } from 'electron';
2 |
3 | const contextMenu = Menu.buildFromTemplate([
4 | { label: 'Item1', type: 'radio' },
5 | { label: 'Item2', type: 'radio' },
6 | { label: 'Item3', type: 'radio', checked: true },
7 | { label: 'Item4', type: 'radio' }
8 | ]);
9 |
10 | export default class TrayCreator {
11 | trayInstance: Tray;
12 | path: string;
13 | constructor(iconPath: string) {
14 | this.path = iconPath;
15 | }
16 |
17 | initTray() {
18 | this.trayInstance = new Tray(this.path);
19 | this.setContextMenu(contextMenu);
20 | }
21 |
22 | setContextMenu(menu: Menu) {
23 | this.trayInstance.setContextMenu(menu);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/main/menu.ts:
--------------------------------------------------------------------------------
1 | import { Menu, MenuItemConstructorOptions } from 'electron';
2 |
3 | const mainMenuTemplate: MenuItemConstructorOptions[] = [
4 | {
5 | label: 'Edit',
6 | submenu: [
7 | { label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' },
8 | { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' },
9 | { type: 'separator' },
10 | { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
11 | { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
12 | { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' },
13 | { label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectall' },
14 | ]}
15 | ];
16 |
17 | const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
18 |
19 | export { mainMenu };
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Electron-TypeScript-React
2 |
3 | ### Create An Electron App with TypeScript, React, Redux, React-Router
4 |
5 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
6 |
7 | ## To Use
8 |
9 | ```bash
10 | # Clone this repository
11 | git clone https://github.com/Leskd/electron-react-typescript.git
12 | # Go into the repository
13 | cd
14 | # Install dependencies
15 | npm install
16 | # Run the app
17 | npm start
18 | ```
19 |
20 | ## Resources for Learning
21 |
22 | - [electronjs.org/docs](https://electronjs.org/docs) - all of Electron's documentation
23 | - [TypeScript](https://www.typescriptlang.org/)
24 | - [React](https://reactjs.org/)
25 | - [Redux](https://redux.js.org/)
26 |
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "outDir": "build/dist",
5 | "module": "esnext",
6 | "target": "es5",
7 | "lib": ["es6", "dom"],
8 | "sourceMap": true,
9 | "allowJs": true,
10 | "jsx": "react",
11 | "moduleResolution": "node",
12 | "rootDir": "app",
13 | "forceConsistentCasingInFileNames": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noImplicitAny": true,
17 | "strictNullChecks": true,
18 | "suppressImplicitAnyIndexErrors": true,
19 | "noUnusedLocals": true,
20 | "allowSyntheticDefaultImports": true,
21 | "paths": {
22 |
23 | }
24 | },
25 | "exclude": [
26 | "node_modules",
27 | "build",
28 | "scripts",
29 | "acceptance-tests",
30 | "webpack",
31 | "jest",
32 | "src/setupTests.ts"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/app/renderer/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import logger from 'redux-logger';
4 | import * as History from 'history';
5 | import { connectRouter, routerMiddleware } from 'connected-react-router';
6 | import rootReducer from '../reducers';
7 |
8 | const isDev = process.env.NODE_ENV === 'development' ? true : false;
9 |
10 | export const history = History.createHashHistory();
11 |
12 | const router = routerMiddleware(history);
13 | let middleArgs = [];
14 |
15 | if (isDev) {
16 | middleArgs = [ thunk, router, logger ];
17 | } else {
18 | middleArgs = [ thunk, router ];
19 | }
20 |
21 | const enhancer = compose(applyMiddleware(...middleArgs));
22 |
23 | export function configureStore(initialState: {} = {}) {
24 | return createStore(connectRouter(history)(rootReducer), initialState, enhancer);
25 | }
26 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | let argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI, in coverage mode, or explicitly running all tests
22 | if (
23 | !process.env.CI &&
24 | argv.indexOf('--coverage') === -1 &&
25 | argv.indexOf('--watchAll') === -1
26 | ) {
27 | argv.push('--watch');
28 | }
29 |
30 |
31 | jest.run(argv);
32 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === 'test') {
21 | require('raf').polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
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 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/main/main.ts:
--------------------------------------------------------------------------------
1 | import { app, Menu } from 'electron';
2 | import path from 'path';
3 | import url from 'url';
4 | import MainWindow from './windows/mainWindow';
5 | import { mainMenu } from './menu';
6 | import TrayCreator from './tray';
7 |
8 | const isDev = process.env.NODE_ENV === 'development';
9 | const port = parseInt(process.env.PORT!, 10) || 3000;
10 | const devUrl = `http://localhost:${port}/`;
11 |
12 | // https://github.com/jarek-foksa/xel/issues/23
13 | const prodUrl = url.format({
14 | pathname: path.resolve(__dirname, 'build/index.html'),
15 | protocol: 'file:',
16 | slashes: true
17 | });
18 | const indexUrl = isDev ? devUrl : prodUrl;
19 |
20 | class Electron {
21 | mainWindowInstance: MainWindow;
22 |
23 | init() {
24 | this.initApp();
25 | }
26 |
27 | initApp() {
28 | app.on('ready', async () => {
29 | this.createMainWindow();
30 | this.mainWindowInstance.loadURL(indexUrl);
31 | Menu.setApplicationMenu(mainMenu);
32 | const appIconPath = path.join(__dirname, './assets/electron.png');
33 | const tray = new TrayCreator(appIconPath);
34 | tray.initTray();
35 | if (isDev) {
36 | this.mainWindowInstance.openDevTools();
37 | await this.installExtensions();
38 | }
39 | });
40 | app.on('window-all-closed', () => {
41 | app.quit();
42 | });
43 | }
44 |
45 | createMainWindow() {
46 | this.mainWindowInstance = new MainWindow();
47 | this.mainWindowInstance.show();
48 | }
49 |
50 | installExtensions = async () => {
51 | const { default: installer, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer');
52 |
53 | installer([REACT_DEVELOPER_TOOLS])
54 | .then((name: string) => console.log(`Added Extension: ${name}`))
55 | .catch((err: Error) => console.log('An error occurred: ', err));
56 | }
57 | }
58 |
59 | new Electron().init();
60 |
--------------------------------------------------------------------------------
/config/webpack.config.electron.js:
--------------------------------------------------------------------------------
1 |
2 | const webpack = require('webpack');
3 | const path = require('path');
4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
5 | const paths = require('./paths');
6 |
7 | module.exports = {
8 | entry: paths.appEntryJs,
9 | output: {
10 | path: path.resolve(__dirname, '../'),
11 | filename: 'main.js'
12 | },
13 | resolve: {
14 | extensions: ['.js', '.ts', '.json']
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.tsx?$/,
20 | include: paths.appMain,
21 | use: [
22 | {
23 | loader: require.resolve('ts-loader'),
24 | options: {
25 | // disable type checker - we will use it in fork plugin
26 | transpileOnly: true,
27 | },
28 | },
29 | ],
30 | }
31 | ]
32 | },
33 | plugins: [
34 | new webpack.DefinePlugin({
35 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) || 'production'
36 | }),
37 | new UglifyJsPlugin({
38 | uglifyOptions: {
39 | parse: {
40 | // we want uglify-js to parse ecma 8 code. However we want it to output
41 | // ecma 5 compliant code, to avoid issues with older browsers, this is
42 | // whey we put `ecma: 5` to the compress and output section
43 | // https://github.com/facebook/create-react-app/pull/4234
44 | ecma: 8,
45 | },
46 | compress: {
47 | ecma: 5,
48 | warnings: false,
49 | // Disabled because of an issue with Uglify breaking seemingly valid code:
50 | // https://github.com/facebook/create-react-app/issues/2376
51 | // Pending further investigation:
52 | // https://github.com/mishoo/UglifyJS2/issues/2011
53 | comparisons: false,
54 | },
55 | mangle: {
56 | safari10: true,
57 | },
58 | output: {
59 | ecma: 5,
60 | comments: false,
61 | // Turned on because emoji and regex is not minified properly using default
62 | // https://github.com/facebook/create-react-app/issues/2488
63 | ascii_only: true,
64 | },
65 | }
66 | })
67 | ],
68 | node: {
69 | __dirname: false,
70 | __filename: false
71 | },
72 | target: 'electron-main'
73 | }
74 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right