8/26/2024
Electron, TypeScript & Parcel
Documentation for Electron is entirely in JavaScript, but that doesn't stop you from using TypeScript to generate that JavaScript. A few simple rules must be followed, mainly in the file loading paths. I have also prepared a small addition for the front-end part. Instead of the standard Electron HTML page, I will make a small compilation with Parcel.
The Project
First, we will organize our project. It will have 2 subfolders - one for the Electron part, the other for the Browser part. We create a folder electron-typescript-parcel
and open it in VSCode - or whichever editor you use. Open the built-in terminal in VSCode (or another if you don't use VSCode) and execute:
npm init -y
This will create a package.json
file in the electron-typescript-parcel
folder. Open the file and edit the author
field. As a start, it is enough. Next, we need to add the Electron module to the project.
npm install --save-dev electron
If you are going to use GIT, now is a good time to execute:
git init
and add a .gitignore
file. Add node_modules
as a start.
Then add 2 folders - electron
and browser
in the project folder. As the names suggest - Electron will live in the first, and the front-end part for the browser will live in the second.
Electron
Through the terminal, enter the electron
folder and execute:
npm init -y
and immediately after that add the TypeScript module:
npm install --save-dev typescript
Load package.json
. In the script part, add "build": "tsc"
and remove the "main"
attribute.
// electron/package.json
{
"name": "electron",
"version": "1.0.0",
"scripts": {
"build": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"typescript": "^5.5.4"
}
}
In the console execute:
npx tsc --init
This will create a tsconfig.json
file. In it, you will need to find "outDir"
, uncomment the line, and set "outDir": "../dist"
.
From here, we follow the standard steps for creating a basic Electron application, skipping the part about creating the index.html
file and renderer.js
file, which we will add through Parcel.
Add a main.ts
file to the electron
folder and write the following code:
// electron/main.ts
import { app, BrowserWindow, ipcMain, nativeTheme } from "electron";
import path from "node:path";
/**
* Creates a new window and loads an HTML file.
*/
const createWindow = (): void => {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, "./preload.js"),
},
});
mainWindow.loadFile("./dist/index.html");
ipcMain.handle("dark-mode:toggle", () => {
if (nativeTheme.shouldUseDarkColors) {
nativeTheme.themeSource = "light";
} else {
nativeTheme.themeSource = "dark";
}
return nativeTheme.shouldUseDarkColors;
});
};
app.whenReady().then(() => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
This is an example from the Electron documentation that changes the dark or light theme of the application.
Next, we add a preload.ts
file to the electron
folder and write the following code:
// electron/preload.ts
import { contextBridge, ipcRenderer } from "electron/renderer";
contextBridge.exposeInMainWorld("electronAPI", {
toggle: () => ipcRenderer.invoke("dark-mode:toggle"),
});
Browser
Through the terminal, we navigate to the browser
folder and execute:
npm init -y
After that, we install Parcel:
npm install --save-dev parcel
Loading package.json
. In the script section, we add "build": "parcel build index.html --dist-dir ../dist --no-source-maps --public-url ./ --no-optimize"
and remove the "main"
attribute.
Create an index.html
file in the browser
folder and write the following code:
<!-- browser/index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World!</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<link rel="stylesheet" type="text/css" href="./styles.css">
</head>
<body>
<h1>Hello World!</h1>
<p>Current theme source: <strong id="theme-source">System</strong></p>
<p><button id="toggle-dark-mode">Toggle Theme Color</button></p>
<script src="./render.ts"></script>
</body>
</html>
Creating a styles.css
file in the browser
folder and writing the following code in it:
/* browser/styles.css */
@media (prefers-color-scheme: dark) {
body { background: #333; color: white; }
}
@media (prefers-color-scheme: light) {
body { background: #ddd; color: black; }
}
Adding a render.ts
file to the browser
folder and writing the following code in it:
const toggleDarkMode = document.getElementById("toggle-dark-mode");
const themeSource = document.getElementById("theme-source");
if (themeSource && toggleDarkMode) {
toggleDarkMode.addEventListener("click", async () => {
// @ts-expect-error
const isDarkMode = await window.electronAPI.toggle();
themeSource.innerHTML = isDarkMode ? "Dark" : "Light";
toggleDarkMode.innerHTML = `Toggle ${!isDarkMode ? "Dark" : "Light"} Mode`;
});
}
To 'compile' the TypeScript file with Parcel, we will add a .parcelrc
file in the folder and write the following:
// browser/.parcelrc
{
"extends": "@parcel/config-default",
"transformers": {
"*.ts": ["@parcel/transformer-typescript-tsc"]
}
}
Start
We go back to the project folder and edit the scripts and main fields in the package.json
file:
// package.json
{
"main": "./dist/main.js",
"scripts": {
"start": "npm run build --prefix ./electron && npm run build --prefix ./browser && electron ."
}
}
In the .gitignore
file, we add the dist
and .parcel-cache
folders, and in the command line, we execute:
npm start
After starting the application, a dist
folder will appear in the project folder, containing all the code of the Electron application.
I have created repositories for this project on GitHub.
If you have an opinion or questions about the article, don't hesitate to share them.