Categories

Setting up a TypeScript Electron app in WebPack

Having spent a few days experimenting with setting up a build process for creating an Electron-based application thought I would put together a post describing the setup. First, a disclaimer that this is still a work in progress and as with everything within the JavaScript world it seems like there are a thousand ways to do it and this is just one way. Secondly, I am by no means an expert and this is just the process I wanted to create - I am sure there are improvements that can be made and would value suggestions!

So my requirements are:

Want to use Electron for creating a simple (or not…) desktop application

Setting Up The Build Process

In order to set this up, we need to set up a fair few pieces. Let’s start by getting the TypeScript process set up to build a file from the src directory to the dist folder. To configure the TypeScript compiler, add a new file called tsconfig.json to the root folder of the project with the following content:

This makes tslint follow the same configuration as StandardJS. I found the white-space settings were causing me some errors hence needing to add the additional configuration over tslint-config-standard.

Next, configure WebPack to compile TypeScript files (ts or tsx extensions) found in the src folder and output to the dist folder. The structure I use here is a little different from the standard as we will need two parallel configurations when we come to the Electron set up. Create a file called webpack.config.js and add the following:

The first rule tells WebPack to run tslint at the prebuild step, before then moving on run the TypeScript compiler. The resolve option adds the TypeScript extensions into WebPack so it will look for both JavaScript and TypeScript files (including JSX or TSX files).

To add the build command to yarn or npm, add the following code to the packages.json. This is assuming you don’t have scripts section already if you do merge it in.

"scripts":{"build":"webpack --config webpack.config.js"},

Visual Studio Code Set Up

In order to run this build from within Visual Studio Code, the next step is to configure the task and also set up the workspace environment appropriately.

Press Ctrl-Shift-B and then click Configure Build Task. Choose npm as a starting point, and then replace the default tasks array with:

The last part of setting up the editor is to add a settings.json within the .vscode folder (which should have been created for the tasks.json file) specifying the number of spaces and line endings to match the linting settings:

{"editor.tabSize":2,"files.eol":"\n"}

A restart of Visual Studio Code might be required in order for it to pick up these changes.

Testing the build

There are three ways to run the build (and all will do the same):

From within the root directory of the project, run yarn run build (or npm run build)

From within the root directory of the project, run .\node_modules\.bin\webpack

Press Ctrl-Alt-B within Visual Studio Code

As there is no code yet, running the build will just result in an error:

To test the build set up, create a src directory and add a main.ts file, with the following content (note the empty line at the end):

The next goal is to use babel-js to convert from this to fully compatible JavaScript for Node. As of Babel 6, a .babelrc file is used to tell it what ‘presets’ to load. The following will tell it to understand both ES2015 and React, and to transpile down as needed for Node:

{"presets":["es2015-node","react"]}

WebPack also needs to be told to call Babel. The loader setting in each rule can take an array of loaders which are loaded in reverse order. Replacing loader: 'ts-loader' with loader: ['babel-loader', 'ts-loader'] makes WebPack run the TypeScript code through the TypeScript compiler and then the Babel compiler.

After re-running the build the new main.js will be very similar to the last version, but should allow for all ES2015 features:

Having set up Babel for the second step in TypeScript build, need to also configure it for compiling JavaScript files. Additionally, StandardJS should be used as a linter for JavaScript files. To do this add the following 2 rules section of the webpack.config.js:

If you also want to target browsers you could switch from es2015-node preset to the es2015 preset.

Electron

So far the process above doesn’t have any settings to deal with Electron. Electron adds a few additional complications we need to deal with. The following command will add the core Electron package and the type definitions for Electron and Node. It also adds the HTML WebPack plugin which I will use to generate a placeholder index.html for the UI side of Electron.

yarn add electron html-webpack-plugin @types/electron @types/node -D

An Electron application consists of two processes:

Main: This is a NodeJS based script which serves as the entry point into the application. It is responsible for instantiating the BrowserWindows instances and also manages various application lifecycle events

Renderer: This is a Chromium based browser. It is the User Interface part of the application. It has the same kind of structure you would expect if you use Chrome. One master process and each WebView is its own process.

The two process share some APIs and communicate between each other over using interprocess communication. There is a great amount of detail on this Electron’s process post.

As we have two processes we are wanting to build output for we need to adjust the webpack.config.js file to handle this. For the two processes, there are two different target settings needed - electron-main and electron-renderer. If the configuration file exports an array then WebPack will interrupt each of the objects as parallel build processes. Replace the module.exports section of the configuration with:

This will pick up the main.ts file and compile this as the Electron main process. It also will compile a gui.ts to be the Electron renderer process. The HtmlWebpackPlugin will create an index.html automatically that includes a reference to the compiled gui.js.

WebPack also defaults to substituting the __dirname which is useful within Electron. This can be stopped by adding a setting to the commonConfig object:

node:{__dirname:false},

Now to put together the main.ts script to create the Electron app and BrowserWindow:

import{app,BrowserWindow}from'electron'declarevar__dirname:stringletmainWindow:Electron.BrowserWindowfunctiononReady(){mainWindow=newBrowserWindow({width:800,height:600})constfileName=`file://${__dirname}/index.html`mainWindow.loadURL(fileName)mainWindow.on('close',()=&gt;app.quit())}app.on('ready',()=&gt;onReady())app.on('window-all-closed',()=&gt;app.quit())console.log(`Electron Version ${app.getVersion()}`)

For the UI side, a simple gui.ts writing the node version out to the document:

The last adjustment is to add a new task the packages.json file. The prestart entry makes it build it as well.

"prestart":"yarn run build","start":"electron ./dist/main.js",

If you run yarn run start hopefully an electron window will appear with the node version displayed in it:

Adding React

In order to move to using TSX (or JSX), need to add React packages to the project:

yarn add react react-dom
yarn add @types/react @types/react-dom -D

The entry point for the UI part of the application needs to be switched from gui.ts to gui.tsx. First, change the entry: { gui: './src/gui.ts' }, line in the webpack.config file to entry: { gui: './src/gui.tsx' }, and rename the file to gui.tsx. Replace the content in gui.tsx with:

Rerunning yarn run start will produce the same result as before but now we have an Electron-based application written in TypeScript using React and build with WebPack!

Unit Tests

The next piece to set up is a unit testing solution. Sticking with the rule that WebPack should build all the TypeScript, the idea is that the tests are written in TypeScript compiled from tests directory into another directory where Jest then runs the JavaScript output.

Again the first step is to add the additional packages:

yarnaddjestjest-junit@types/jest -D

WebPack needs another configuration file to run the tests. In general the settings should be the same between the two. By requiring the main webpack.config file, this test config file can use it as a starting point. Create a new config file called webpack.tests.config.js and add the following content:

The getEntries function is designed to search all folders within tests/host and tests/gui for TypeScript files with filenames ending either with tests or specs. This scan process limits the watch functionality of WebPack as it will only scan for files at start up. Files within tests/host will be built with the target setting of electron-main and tests/gui will be electron-renderer. The output will be built to a __tests__ folder and as before will pass through tslint, tsc and babel to produce JavaScript files.

To add the test command to yarn, add the following to packages.json. The pretest stage will build all the test files before running jest on the result.

"pretest":"webpack --config webpack.tests.config.js","test":"jest"

By default, Jest will search for the test files within the __tests__ folder or any JavaScript file with a filename ending either spec or test. Adding the configuration below (to package.json) limits Jests to just reading the __tests__ folder. The second part configures jest-junit to write out an XML file containing the test results - this is for Visual Studio Team Services to be able to read the results.

Finally, create the test directory structure and a couple of placeholder tests. Note the top line in the sample code below, this adds the global variables that Jest declares into TypeScript so the compiler will be happy!

Running yarn run test will build these two tests files and then run Jest on the resulting output:

It will also create a TEST-jest_junit.xml file. This is for reading with Visual Studio Team Services so we get nice test results.

Visual Studio Team Services

Create a new project in Visual Studio Team Services and select build code from an external repository.

Select New Definition and choose either of NodeJS scripts as a starting point. These are based on top of npm so it is easy to configure to build this project. I am sure you could make it use yarn but for simplicity have stuck with npm.

First, reconfigure the Get Sources step to get the code from GitHub (you may need to allow pop-ups for the authorization step). It’s great how easy it is to integrate with external repositories now within VSTS.

Next, remove the Run gulp task. Add a new npm task after the npm install, with npm command of run and argument of test. This will build and run the Jest tests. It needs to continue even if it the tests fail so choose Continue on error within the Control Options section.

In order to make VSTS report the test results, add a new task to Publish Test Results. The default configuration of this task will pick up the JUnit format XML we have configured to be published in the npm test step.

The last step is to run the actual WebPack build. So add another npm command. This time configured to run build.

Finally, switch on the triggers for Continuous Integration and Pull Requests. That is it - a CI process from GitHub into VSTS!

Future Improvements

Since I started writing this Electron has updated with support for TypeScript. This doesn’t change much but does mean you don’t need type definitions for electron and node. If you have followed these instructions all you need do is run:

yarn upgrade electron -D
yarn remove @types/electron @types/node -D

Currently, I don’t have a good solution for watching tests. While watch mode works fine for build (run yarn run build -- --watch) the test command set up doesn’t support watching yet. I’ll inevitably spend a fair chunk of time mucking around trying to get this bit set up as well but at present, I accept just having to run the command to run my tests.

Primarily to keep this post shorter (well a little shorter), I haven’t gone into much detail on writing the Electron or React side of an application, instead just looking at the build process.

I haven’t covered packaging or any of the other steps needed for Electron. Lots more I can add if people are interested.

Hopefully, as I experiment and learn more, I will write a few more posts on Electron as it is a platform I am growing to really like.

I am a Technical Architect at Scott Logic, based in London. I have many years of using C# within financial services. My current interests are in data analytics and visualisation, particularly using Tableau and Alteryx. I also enjoying learning about new big data technologies and machine learning.