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
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",
}
Typescript Setup
Since we want to write our application with Typescript
we need to install it:
npm install typescript --save-dev
After the installation, generate the tsconfig.json
, for that run:
tsc --init
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
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
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>
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);
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"
}
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
We will create a file to setup the webpack configs for electron, for that create webpack.electron.js
in 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'
}
}
Let me explain what this code does.
resolve: {
alias: {
['@']: path.resolve(__dirname, 'src')
},
extensions: ['.tsx', '.ts', '.js'],
},
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
];
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';
After:
./src/component1/component1.ts
// Lets import from ./src/services/service1.ts
import stuff from '@/services/service1';
Now update npm script
in package.json
:
"scripts": {
"build": "webpack",
"start": "npm run build && electron dist/main.js"
}
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
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'));
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;
Do you remember the tsconfig.json
file? 🤔 Let´s add two options to it:
{
"compilerOptions": {
...
"jsx": "react",
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
},
}
}
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'
})
]
};
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
];
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
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"
}
Now for React we need to update Webpack Configuration:
...
module.exports = {
...
devServer: {
contentBase: path.join(__dirname, 'dist/renderer.js'),
compress: true,
port: 9000
},
...
}
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"
},
...
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);
SCSS Setup (Optional)
Install dependecies necessary for it:
npm install sass-loader sass style-loader css-loader --save-dev
Update React Webpack Configuration:
...
rules: [
...
{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
}
]
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;
Conclusion of Part 1
Finally, eveything is ready to start our application.
Let's run it!!
npm run start
Repository: Tokei - Part 1 Branch
Top comments (8)
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 ?
Yup, you are right! Will edit the post.
For any strange behaivor you can check the repo.
Thx!
Great article that helped me set up my enviroment. But i see that you did not changed in article @shadowtsw 's notes.
I am not sure if it's by changes over time, but this part of code did not worked for me.
Instead of
win.loadURL('index.html');
i usedwin.loadFile('index.html');
and it started working showing content in windows.Thx.
Hello. Thank you for that awesome tutorial.
Can i use hot reload future with this setup, if yes how ?
Updated!
Webpack configs refactored, the hor reload will depende of it.
Any question just ask!
Hello, I will add the Reload to this tutorial today!!
Sorry for the late replay.
Have you tried to use TailwindCSS with this template?
No