DEV Community

loading...
Cover image for Setting Electron + React with Typescript

Setting Electron + React with Typescript

franamorim profile image Francisco Amorim Updated on ・5 min read

Heya!

In this tutorial series we will build an Desktop Alarm Widget with Electron and React written in Typescript.

What will we tackle in this series:

  • Typescript
  • Electron
  • React
  • Webpack

Feature:

  • Clock
  • Alarm w/ notification

Part 1: Setting up the project

Start the project

Let's start! First open your terminal in the desired root folder and run the command:

npm init -y
Enter fullscreen mode Exit fullscreen mode

This command will generate the package.json file.

{
  "name": "tokei",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
}
Enter fullscreen mode Exit fullscreen mode

Typescript Setup

Since we want to write our application with Typescript we need to install it:

npm install typescript --save-dev 
Enter fullscreen mode Exit fullscreen mode

After the installation, generate the tsconfig.json, for that run:

tsc --init
Enter fullscreen mode Exit fullscreen mode

We have our project ready to write Typescript 🥳

Electron Setup

Now we need to install Electron and setup everything related to it.

npm install electron --save-dev
Enter fullscreen mode Exit fullscreen mode

Let's create an html file and the entrypoint for electron under src folder. the project structure should look like this:

root
- src/
-------- index.html
-------- main.ts
- package.json
Enter fullscreen mode Exit fullscreen mode

Add the following content to the html file:

./src/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <div id="root"></div>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Now the content of the main file of our application.

./src/main.ts

import { app, BrowserWindow } from 'electron';

const createWindow = (): void => {
  let win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadFile('index.html');
}

app.on('ready', createWindow);
Enter fullscreen mode Exit fullscreen mode

Let's add our first script command in the package.json to run the electron app:

"scripts": {
  "build": "tsc src/main.ts",
  "start": "electron dist/main.js"
}
Enter fullscreen mode Exit fullscreen mode

Webpack Setup

We will use Webpack to optimize and build our application.
Start by installing it.

npm install webpack webpack-cli html-webpack-plugin ts-loader --save-dev
Enter fullscreen mode Exit fullscreen mode

We will create a file to setup the webpack configs for electron, for that create webpack.electron.jsin the root folder:

./webpack.electron.js

const path = require('path');

module.exports = {
  // Build Mode
  mode: 'development',
  // Electron Entrypoint
  entry: './src/main.ts',
  target: 'electron-main',
  resolve: {
    alias: {
      ['@']: path.resolve(__dirname, 'src')
    },
    extensions: ['.tsx', '.ts', '.js'],
  },
  module: {
    rules: [{
      test: /\.ts$/,
      include: /src/,
      use: [{ loader: 'ts-loader' }]
    }]
  },
  output: {
    path: __dirname + '/dist',
    filename: 'main.js'
  }
}
Enter fullscreen mode Exit fullscreen mode

Let me explain what this code does.

resolve: {
  alias: {
    ['@']: path.resolve(__dirname, 'src')   
  },
  extensions: ['.tsx', '.ts', '.js'],
},
Enter fullscreen mode Exit fullscreen mode

Now create the webpack.config.js in the root folder, this will consume all necessary configs for our webpack:

./webpack.config.js

const electronConfigs = require('./webpack.electron.js');

module.exports = [
  electronConfigs
];
Enter fullscreen mode Exit fullscreen mode

From now on don't need to navigate folder with '../../', we can use '@' as a starting point in the src folder.

Before:

./src/components/button/button.ts

// Lets import from ./src/services/service1.ts
import Service1 from '../../services/service1';
Enter fullscreen mode Exit fullscreen mode

After:

./src/component1/component1.ts

// Lets import from ./src/services/service1.ts
import stuff from '@/services/service1';
Enter fullscreen mode Exit fullscreen mode

Now update npm script in package.json:

"scripts": {
  "build": "webpack",
  "start": "npm run build && electron dist/main.js"
}
Enter fullscreen mode Exit fullscreen mode

React Setup

For our renderer we will install React and all dependencies necessary for typescript.

npm install --save-dev react react-dom @types/react @types/react-dom
Enter fullscreen mode Exit fullscreen mode

Create the react entrypoint as renderer.ts:

./renderer.ts

import React from 'react';
import ReactDOM from 'react-dom';
import App from '@/app/app';

ReactDOM.render(<App />, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

As you can see we are importing the App Component, but we don't have it yet, let code it!

./app/app.ts

import React from 'react';

const App = () => {
  return (
    <div className="app">
      <h1>I'm React running in Electron App!!</h1>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Do you remember the tsconfig.json file? 🤔 Let´s add two options to it:

{
  "compilerOptions": {
    ...
    "jsx": "react",
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Now setup the Webpack configuration for React, like we did for electron, we need to create a specific config file for react in the root folder:

webpack.react.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/renderer.tsx',
  target: 'electron-renderer',
  devtool: 'source-map',
  devServer: {
    contentBase: path.join(__dirname, 'dist/renderer.js'),
    compress: true,
    port: 9000
  },
  resolve: {
    alias: {
      ['@']: path.resolve(__dirname, 'src')
    },
    extensions: ['.tsx', '.ts', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.ts(x?)$/,
        include: /src/,
        use: [{ loader: 'ts-loader' }]
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      }
    ]
  },
  output: {
    path: __dirname + '/dist',
    filename: 'renderer.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};
Enter fullscreen mode Exit fullscreen mode

And update the webpack.config.js:

./webpack.config.js

const electronConfigs = require('./webpack.electron.js');
const reactConfigs = require('./webpack.react.js');

module.exports = [
  electronConfigs,
  reactConfigs
];
Enter fullscreen mode Exit fullscreen mode

Hot Reload Setup (Optional)

To avoid running the build whenever we make any changes we will add Hot Reload, for that we need to install the following packages:

npm install nodemon webpack-dev-server electron-is-dev concurrently --save-dev
Enter fullscreen mode Exit fullscreen mode

First we will setup the Electron Hot Reload, for that we need to create a nodemon.json file in the root, and add the following settings:

nodemon.json

{
  "watch": [ 
    "src/main.ts", 
    "src/electron/*"
  ],
  "exec": "webpack --config ./webpack.electron.js && electron ./dist/main.js",
  "ext": "ts"
}
Enter fullscreen mode Exit fullscreen mode

Now for React we need to update Webpack Configuration:

...
module.exports = {
  ...
  devServer: {
    contentBase: path.join(__dirname, 'dist/renderer.js'),
    compress: true,
    port: 9000
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

We should update our package.json:

  ...
  "scripts": {
    "build": "webpack",
    "react:dev": "webpack serve --mode=development",
    "electron:dev": "nodemon",
    "dev": "concurrently --kill-others \"npm run react:dev\" \"npm run electron:dev\"",
    "start": "npm run build && electron dist/main.js"
  },
  ...
Enter fullscreen mode Exit fullscreen mode

Last thing we should change our main.js:

import { app, BrowserWindow } from 'electron';
import isDev from 'electron-is-dev'; // New Import

const createWindow = (): void => {
  let win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });
  console.log(isDev);
  win.loadURL(
    isDev
      ? 'http://localhost:9000'
      : `file://${app.getAppPath()}/index.html`,
  );
}

app.on('ready', createWindow);
Enter fullscreen mode Exit fullscreen mode

SCSS Setup (Optional)

Install dependecies necessary for it:

npm install sass-loader sass style-loader css-loader --save-dev
Enter fullscreen mode Exit fullscreen mode

Update React Webpack Configuration:

...
rules: [
  ...
   {
      test: /\.s[ac]ss$/i,
      use: [
        'style-loader',
        'css-loader',
        'sass-loader',
      ],
    }
]
Enter fullscreen mode Exit fullscreen mode

Create you first SCSS file under src/app folder, and update you app.tsx importing it.

./src/app/app.tsx

import React from 'react';
import 'app.scss'; // New import!!

const App = () => {
  return (
    <div className="app">
      <h1>I'm React running in Electron App!!</h1>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Conclusion of Part 1

Finally, eveything is ready to start our application.
Let's run it!!

npm run start
Enter fullscreen mode Exit fullscreen mode

Alt Text

Repository: Tokei - Part 1 Branch

Part 2: The Tray Menu

Discussion (5)

pic
Editor guide
Collapse
ahmeteneskcc profile image
Ahmet Enes KCC

Hello. Thank you for that awesome tutorial.
Can i use hot reload future with this setup, if yes how ?

Collapse
franamorim profile image
Francisco Amorim Author

Updated!
Webpack configs refactored, the hor reload will depende of it.

Any question just ask!

Collapse
franamorim profile image
Francisco Amorim Author

Hello, I will add the Reload to this tutorial today!!
Sorry for the late replay.

Collapse
shadowtsw profile image
Timmy

Nice work, good job !!

I guess, there is a small typing mistake:
To render the content correctly, the passed element needs to be "root" instead of "app" ?

ReactDOM.render(, document.getElementById('app'));
-->
ReactDOM.render(, document.getElementById('root'));

And inside the webpack.config.js the module "path" is missing via require
-->
const path = require("path");

Or am I wrong ?

Collapse
franamorim profile image
Francisco Amorim Author

Yup, you are right! Will edit the post.

For any strange behaivor you can check the repo.

Thx!