DEV Community


React, Typescript, Electron, all steps to start

garryxiao profile image Garry Xiao Updated on ・5 min read

The guides and issues are scattered, I summarized all together to start the journey.

Two computers for testing:

  1. Mac - MacBook Pro (Retina, 13-inch, Early 2015): macOS High Sierra (Version 10.13.6), English.
  2. Win - ThinkPad T430: Windows 10 professional, Chinese.

Setup steps:

  1. Install node.js from
  2. Mac: Terminal, Win: Node.js command prompt. input command 'node -v' or 'node --version' to show the version of node.js and make sure the installation is successful. Installations below are global(-g) visible. If you want to limit to a specific project, add '--save-dev' before the package.
  3. 'npm install -g create-react-app' to install React (
  4. 'npm install -g typescript' to install TypeScript ( Step 3 and 4 together command 'npx create-react-app my-app --template typescript'.
  5. 'npm install -g electron' to install Electron (
  6. 'npm install -g electron-builder' for packaging Electron application.
  7. 'npm install -g concurrently' for concurrent commands support.
  8. 'npm install -g wait-on' for step by step commands support. On Mac, you may be failed because of 'Missing write access permission'. Please access to to fix it.

Configuration steps:

  1. Mac starts from the user's home folder. Mac & Win both use 'cd' to change directory.
  2. 'npx create-react-app my-app --template typescript' create an application named 'my-app' (change it to your project name) with typescript support. Use 'cd my-app' to the project folder. Setting up ESLint on VS Code with Airbnb JavaScript Style Guide:
  3. 'npm start' will launch the project and open a browser window to load URL 'http://localhost:3000/'. It works then the React works.
  4. Create an 'electron.ts' under the 'public' folder. Please check the sample codes below.
  5. Create a 'preload.js' under the 'public' folder. Please check the sample codes below.
  6. Edit 'src/App.tsx', add codes to communicate with Electron main process. Please check the sample codes (App.tsx, IBridge.ts, IAppData.ts, ElectronBrideg.ts) below.
  7. Edit 'package.json', add lines:
"main": "public/electron.ts", -- Electron initialization script
"homepage": "./",  -- set it to fix possible ‘Failed to load resource’ errors
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "electron": "concurrently \"set BROWSER=none&&npm start\" \"wait-on http://localhost:3000&&set ELECTRON_ENV=development&&electron .\"", -- Win
    "electron": "concurrently \"export BROWSER=none&&npm start\" \"wait-on http://localhost:3000&&export ELECTRON_ENV=development&&electron .\"", -- Mac
    -- set or export ELECTRON_ENV to pass production environment variable
    "package": "npm run build&&electron-builder build"
  "build": {
    "appId": "***",
    "copyright": "***",
    "productName": "***",
    "files": [
    "mac": {
      "icon": "public/logo512.png",
      "category": ""
      -- Need to provide signing details
    "win": {
      "icon": "public/logo512.png",
      "target": [
Enter fullscreen mode Exit fullscreen mode

-- 'npm install' to fix possilbe dependencies or links issues.
-- Remove "react-scripts": "3.4.1" to avoid react-builder tricky error.
-- Add to improve security when load URLs (not file://).

  1. use 'npm run electron' in development mode. Please run 'npm run build' once before; use 'npm run package' to build the application. You can press 'Ctrl + C' to exit node.js process and run a new command.

Sample codes:

  1. electron.ts:
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const isDev = process.env.ELECTRON_ENV?.trim() == 'development'

function createWindow() {
    // Create the browser window.
    const mainWindow = new BrowserWindow({
        show: false,
        webPreferences: {
            contextIsolation: true,
            preload: path.join(app.getAppPath(), './build/preload.js')

    // Show the window after its ready
    mainWindow.once('ready-to-show', () => {
        // Show the window

        // Maximize the window

        // Not allowed to resize
        // mainWindow.resizable = false

    // Hide the application menu

    // and load the app.
    if (isDev)
        mainWindow.loadFile(`${path.join(app.getAppPath(), './build/index.html')}`)

    // Open the DevTools.
    // Use 'Ctrl + Shift + I' instead
    // mainWindow.webContents.openDevTools()
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', function () {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') app.quit()

app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
ipcMain.on('app', (event, arg) => {
    event.reply('app-reply', { name:, version: app.getVersion(), path: app.getAppPath() })
Enter fullscreen mode Exit fullscreen mode
  1. preload.js:
const {
} = require("electron")

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
    "appRuntime", {
        send: (channel, data = null) => {
            ipcRenderer.send(channel, data);
        subscribe: (channel, listener) => {
            const subscription = (event, ...args) => listener(...args);
            ipcRenderer.on(channel, subscription);

            return () => {
                ipcRenderer.removeListener(channel, subscription);
Enter fullscreen mode Exit fullscreen mode
  1. App.tsx:
import React, {useState} from 'react'
import logo from './logo.svg'
import 'antd/dist/antd.css'
import './App.css'

import appRuntime from "./etsoo/bridges/ElectronBridge"
import IAppData from "./etsoo/bridges/IAppData";

function App() {
  const [appData, setAppData] = useState('Loading...')

  if(appRuntime != null) {
    appRuntime.subscribe('app-reply', (data:IAppData) => {
      setAppData( + ': ' + data.version)

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
          Edit <code>src/App.tsx</code> and save to reload.
          App: {appData}
          rel="noopener noreferrer"
          Learn React

export default App
Enter fullscreen mode Exit fullscreen mode
  1. etsoo/bridges/IBridge.ts:
 * IBridge unsubscribe type
type IBridgeUnsubscribe = () => void

 * IBridge subscribe listener type
type IBridgeListener = (...args: any[]) => void

 * window.external calls bridge interface
export interface IBridge {
     * Send data to the host process with an unique channel
     * @param channel an unique channel
     * @param data Data to send
    send(channel: string, data?: any):void

     * Subscribe to the host process
     * @param channel an unique channel
     * @param listener callback listener
    subscribe(channel: string, listener: IBridgeListener):IBridgeUnsubscribe
Enter fullscreen mode Exit fullscreen mode
  1. etsoo/bridges/IAppData.ts:
 * Bridge App Data interface
export default interface IAppData {
     * Application name
    name: string,

     * Application version
    version: string,

     * Application path
    path: string
Enter fullscreen mode Exit fullscreen mode
  1. etsoo/bridges/ElectronBridge.ts:
import { IBridge } from './IBridge'

 * Electron bridge class
 * copes with preload.js, contextBridge.exposeInMainWorld
 * BrowserWindow.webPreferences, contextIsolation: true
const appRuntime = (window as any).appRuntime as IBridge
export default appRuntime
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

Editor guide