What is Electron?
Electron is a framework for creating native applications. It's open-source and cross-platform. If you already know Javascript, HTML, and CSS you can build an application with electron.
In this tutorial, I'll show you how to start an electron project from scratch using webpack, react, and Typescript.
Start with Electron.
Let's start by creating a new folder and a new npm
project.
mkdir electron-react-ts
cd electron-react-ts
npm init -y
Now install these dependencies.
npm install --save-dev electron \
webpack webpack-cli webpack-dev-server \
babel-loader @babel/core @babel/preset-env \
@babel/preset-react @babel/preset-typescript
Create a tsconfig.json
file. This allows you to specify the configuration for the typescript compiler.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": [
"dom",
"es2015",
"es2016",
"es2017"
],
"allowJs": true,
"jsx": "react",
"sourceMap": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
}
}
Create a babel.config.js
and an index.html
file at the root of our app.
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
]
}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Electron App</title>
</head>
<body>
</body>
</html>
Let's create a new file called webpack.electron.config.js
on the root of our app. This webpack file will compile our electron app into a dist
folder.
const path = require('path');
module.exports = {
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
devtool: 'source-map',
entry: './electron/main.ts',
target: 'electron-main',
module: {
rules: [
{
test: /\.(js|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js',
},
};
This looks like a normal webpack configuration for typescript, except for the target. The target is the specific environment that webpack will compile for. In this case it's electron-main
.
Create an electron
folder, then inside a main.ts
file with the following code.
This file should create windows and handle the systems events for your app.
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import * as url from 'url';
let mainWindow: Electron.BrowserWindow | null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
});
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL(`http://localhost:4000`);
} else {
mainWindow.loadURL(
url.format({
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true
})
);
}
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.allowRendererProcessReuse = true;
The BrowserWindow
module will create a new window and render our react app.
Now let's add a script in the package.json
file in order to run electron. Also, we have to change the main
field for the path that has our electron app compiled.
{
"main": "./dist/main.js",
"scripts": {
"dev:electron": "NODE_ENV=development webpack --config webpack.electron.config.js --mode development && electron ."
},
}
Now run npm run dev:electron
in the console.
Note: If you are using Windows, chances are you'll face an error, this is because NODE_ENV
is not recognized as a command. You have to install crossenv and place the command before NODE_ENV
.
Add a React app.
Now that we have our electron app running, let's set up a react app to run within this electron context.
We need to install a few dependencies.
npm install react react-dom @types/react @types/react-dom
npm install --save-dev html-webpack-plugin
Create a new webpack.react.config.js
file.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
resolve: {
extensions: ['.tsx', '.ts', '.js'],
mainFields: ['main', 'module', 'browser'],
},
entry: './src/app.tsx',
target: 'electron-renderer',
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
devServer: {
contentBase: path.join(__dirname, '../dist/renderer'),
historyApiFallback: true,
compress: true,
hot: true,
port: 4000,
publicPath: '/',
},
output: {
path: path.resolve(__dirname, '../dist/renderer'),
filename: 'js/[name].js',
publicPath: './',
},
plugins: [
new HtmlWebpackPlugin(),
],
};
Our package.json
file now should look like this.
{
"main": "./dist/main.js",
"scripts": {
"dev:electron": "NODE_ENV=development webpack --config webpack.electron.config.js --mode development && electron .",
"dev:react": "NODE_ENV=development webpack-dev-server --config webpack.react.config.js --mode development"
},
}
In order to try this app, let's create a new folder src
with anapp.tsx
file inside.
import React from 'react';
import ReactDom from 'react-dom';
const mainElement = document.createElement('div');
document.body.appendChild(mainElement);
const App = () => {
return (
<h1>
Hi from a react app
</h1>
)
}
ReactDom.render(<App />, mainElement);
Now we are ready.
Run npm run dev:react
in one console, and npm run dev: electron
on other one.
Check this repo for the code.
Top comments (23)
Once I set nodeIntegration: false, I get this error:
That's weird, what if you don't set nodeIntegration? does the error still there?
I "solved" by changing in webpack.react.config.js :
target:
//"electron-renderer",
"web",
Does this change causes some side-effects?
Hi, maybe you will have problems when you build your app since that is what
electron-renderer
is used for. Do you have a repo to see your code? do you mind sharing it on the issue you opened? thanks.I loaded everything here: github.com/raphael10-collab/electr...
As you can see, I didn't anything special: it's your app, where the only differences are these ones:
electron/main.ts :
webPreferences: {
nodeIntegration: false
webpack.react.config.js :
target:
//"electron-renderer",
"web",
If you revert back webpack.react.config.js from "web" to "electron-renderer", you should get the error:
"Uncaught ReferenceError: require is not defined at Object.url", when nodeIntegration is kept to false. If nodeIntegration is set to true, the problem disappears. But this is not what we are looking for, because we would like to have a more secure electron-react-typescript app with nodeIntegration:false
I loaded everything on here: github.com/raphael10-collab/electr...
In your app I just modified these two things:
webPreferences:
nodeIntegration: true
webpack.react.config.js :
If you revert back target: "web" to "electron-renderer" (the correct setting), you should get the error: "Uncaught ReferenceError: require is not defined at Object.url"
And if you set nodeIntegration:true the problem disappears. But if we want to keep our app more secure, we ought to keep nodeIntegration:false
Removing the devServer part of webpack.react.config.js :
while keeping target: "electron-renderer" make it work also with nodeIntegration: false
webpack.react.config.js :
This is the new repo: github.com/raphael10-collab/Electr...
git cloning it, and then executing yarn electron .
I get the app without any errors
I wrote this github's issue about this problem: github.com/elisealcala/electron-re...
I think it's an important issue, because if we want, for strict security reasons, keep nodeIntegration:false + contextIsolation:true, we must solve it
Once i set nodeIntegration: true again, the error disappears. So It Is clearly related to nodeIntegration: false
So... my question is: how to solve the problem, while keeping nodeIntegration:false + contextIsolation:true for strict security reasons?
Nice and easy tutorial.
Do you plan to continue it with adding more electron based functionality (to tease some capability of the electron itself) or discuss the webpack configuration to let even the beginners understand why-what-where to do in it?
Hey, I could write about the webpack configuration, maybe a detailed and general view to understand how it works.
I thought about making more electron posts, maybe a serie about creating a complete app.
Hey, really greate post. Exactly what I need.
Can you also describe how to pack it with electron-builder or electron-packager?
Thx in advice.
Hi, sorry for the late reply, if you check the repo the configuration to pack it with
electron-builder
is already there.Ok now this work, but after i run
dev:electron
I got an empty planck white page ?! imageHi, great article, thanks a lot !
I just have a question, I'm struggled with a simple import of .png after the complete installation. Everything works great, including adding styled components and other packages, but I simply can't import an image file, I have an error : "no loaders configured for this file". I guess I have to edit the Webpack conf, but don't know how :(
Can you help me ?
It can do wonders, VS Code as an example :)
Hi, great article, thank you!
I was wondering if babel is really necessary. Couldn't we use the the normal typescript transpiler for that? After all, we don't need to be compatible to different browsers if we ship our own browser.
Hi, I haven't tried it but I think it could work.
Just what I need. You are my hero ❤️
Is it possible to start both react and electron with just one command?
hi!
just add the concurrently npm package to the dev dependencies and your scripts should be like this:
"scripts": {
"dev": "concurrently --success first \"npm run dev:electron\" \"npm run dev:react\" -k",
"dev:electron": "cross-env NODE_ENV=development webpack --config webpack.electron.config.js --mode development && electron .",
"dev:react": "cross-env NODE_ENV=development webpack serve --config webpack.react.config.js --mode development && electron ."
},
As you can see, the dev script calls the concurrently command and the commands that you need to execute concurrently have to be put one after the other among quotation marks ("), escaping them, obviously.
contentBase
andpublicPath
are invalid options in devServer.