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
Oldest 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 ?
Hello, I will add the Reload to this tutorial today!!
Sorry for the late replay.
Updated!
Webpack configs refactored, the hor reload will depende of it.
Any question just ask!
Have you tried to use TailwindCSS with this template?
No