Henry Lucco | Posts

Electron, TypeScript, and Webpack

When learning how to create an Electron app, I had a hard time finding example code that explained how to build an application using TypeScript and Electron. The goal of this guide is to make the learning process easier for those who want to build their own Electron app using Typescript.

To begin, create a new directory and initialize it as a node module:

npm init -y

Then, add the required dependencies to the package.json

"devDependencies": {
    "clean-webpack-plugin": "^4.0.0",
    "electron": "^16.0.5",
    "html-webpack-plugin": "^5.5.0",
    "ts-loader": "^9.2.6",
    "typescript": "^4.5.4",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
}       

Once the dependencies are set, install with

npm i

Next up we will create a tsconfig.json file which will tell the TypeScript compiler to put our compiled code in the ./dist directory

{
    "compilerOptions": {
        "outDir": "./dist/",
        "noImplicitAny": true,
        "module": "es6",
        "target": "es5",
        "allowJs": true,
        "sourceMap": true,
        "moduleResolution": "node"
    }
}       

Having created a tsconfig.json, we can now create a ./src directory for three files: an index.html, electron.ts, and client.ts. First index.html is the root html page that the electron app will serve.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" 
        content="width=device-width,
        user-scalable=no,initial-scale=1,
        maximum-scale=1,minimum-scale=1">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>TS & Webpack</title></head>
<body>
</body>
</html>

The second file electron.ts is the electron process. This file is repsonsible for creating the browser instance, serving it in a window, and for loading the root html document ./index.html.

import { app, BrowserWindow } from "electron";

function createWindow() {
    // Create the browser window.
    let win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: true,
        },
    }); 
    // and load the index.html of the app.
    win.loadFile("../dist/index.html");
}

app.on("ready", createWindow);

It is important to note that the path for index.html is ../dist/index.html and not ./index.html. Finally, client.ts is the entry point for clientside functionality inside the chromium instance being served by electron.ts.

window.addEventListener("load", () => {
    const header = document.createElement("h1");
    header.innerText = "Webpack & TS";

    const body = document.querySelector("body");
    body.appendChild(header);

    let div = document.createElement("div");
    div.innerText = "Hello World!";

    body.appendChild(div);
});

With our ./src directory implemented, the final step is to use Webpack to package our code so that it can run in electron's browser instance. To acomplish this we will create two Webpack config files in the root directory: webpack.config.js and webpack-cli.config.js. The first config, webpack.config.js, is for the server side code in electron.ts.

module.exports = [
    {
        mode: 'development',
        entry: './src/electron.ts',
        target: 'electron-main',
        module: {
        rules: [{
            test: /\.ts$/,
            include: /src/,
            use: [{ loader: 'ts-loader' }]
        }]
        },
        output: {
        path: __dirname + '/dist',
        filename: 'electron.js'
        }
    }
];

This module finds our electron.ts entry point for our electron process and builds it using our tsconfig.json. The second Webpack config file webpack-cli-config.js is responsible for building client.ts using tsconfig.json, and packaging it into browser code so that it runs inside the electron browser instance.

const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const path = require('path');

module.exports = {
    mode: "production",
    entry: path.resolve(__dirname, './src/clientmain.ts'),
    module: {
        rules: [
            {
            test: /\.tsx?$/,
            use: 'ts-loader',
            exclude: /node_modules/,
            },
        ],
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    // <- ensure unique bundle name
    output: {
        filename: 'bundle.[hash].js', 
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 
            "./src/index.html")
        })
    ]
};

Now we update our package.json to run and build properly:

"scripts": {
"start": "npm run build && electron dist/electron.js",
"build": "webpack --config ./webpack-cli.config.js && 
          webpack --config ./webpack.config.js"
},

and run our application using

npm start

An example repo containing this code can be found here.