Abstractions, Abstractions every where...

I decided to create a small application with two components: the Chrome extension to trap all the xhr calls and the desktop that opens a web socket to which the Chrome extension sends all the details.

I wrote the desktop app using Electronjs. For reasons unknown, I decided to use Typescript with Electronjs. Well, that’s what pushed me down the rabbit hole of transpiling with Typescript, to using bundlers such as Webpack, and finally electron-forge using template webpack-typescript.

Let’s deal with each issue at a time.

Plain Typescript, nothing fancy!!

When Electronjs loads the renderer process, it throws an error -

Uncaught ReferenceError: exports is not defined at renderer.js:5:23 (anonymous) @ renderer.js:5

On inspection of renderer.js, the culprit is the first line, which tries to define a property on object exports.

Object.defineProperty(exports, "__esModule", { value: true });

Check more about Object.defineProperty

Why is this line generated?, well ChatGpt says to provide ‘interoperability’. This line is trying to set the renderer process as esm, but there is no object defined at the top called exports

Adding a line var exports = {} at the top in index.js, resolves the issue.

The discussion around this issue on Typescript & Electronjs forum mentioned two solution, one mentioned above and second to comment the offending line

What happens if it’s just plain old typescript?, In that case, the line Object.defineProperty is also present, but then the keyword exports is defined and is an empty object. What’s the issue that Electronjs has? Electronjs is a web application encapsulated and presented as a desktop app. It loads the renderer process with a script tag. When a node process executes in a debug mode, vs code will show under local variables exports

Local Variable Exports

The global Object on a browser is called window. Object.keys will not list anything that is exports.

The github discussion (click here) was very helpful.

Good’ol Webpack

Well, here’s the twist, when I started writing the article, I didn’t face any issue. In fact, I feel webpack could be the best option for making an electronjs app. As long as webpack.config.ts is correctly written, the app works like a charm.

webpack has a config key called electron-main and electron-renderer. The entry points and target should be correct, and then electron works like a charm.

Electron Forge, the swiss knife

webpack has toooo… many features, such as webpack dev or hot reloading, configuring it for the first time is no joke, but electron forge makes it easy. What’s difficult is, to understand how electron-forge works. The template that is generated for index.ts has a comment which says -

// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production).

Initially, I felt it was entirely magic, untill I started peeling onion.

Few things that I learnt during the whole process -

  1. To enable debugging, execute the command electron-forge start -l
  2. Electron-forge uses [debug](https://www.npmjs.com/package/debug) module to list the messages while building. The messages can be read at http://localhost:9000
  3. The renderer process is served using webserver. So, the electronjs’s renderer html can be viewed at http://localhost:3000. Electron-Forge is using expressjs to serve the artifacts related to rederer process.
  4. The standard index.html doesn’t have the renderer file path mentioned. Similarly, the main process javascipt doesn’t have html file path hard coded. These are generated using webpack. Check out @electron-forge\webpack-plugin
  5. The index.js of main process is everything but english. To understand the code, change devtool option to false. It can be done by navigating to \node_modules\@electron-forge\plugin-webpack\src\WebpackConfig.ts
  6. Debug can be enabled by setting environment variable DEBUG=*, (source from readme of debug package)
  7. In forge.config.ts, renderer option has entryPoints. This is an array. So, one can include multiple renderer process by defining multiple items in the array.
entryPoints: [
          {
            html: './src/index.html',
            js: './src/renderer.ts',
            name: 'some_window',
            preload: {
              js: './src/preload.ts',
            },
          },
          {
            html: './src/another.html',
            js: './src/another.ts',
            name: 'another_window',
            preload: {
              js: './src/preload.ts',
            },
          },
        ],

The magic constants, would be -

SOME_WINDOW_WEBPACK_ENTRY
ANOTHER_WINDOWS_WEBPACK_ENTRY