Web, mobile, and desktop are the three most popular software application platforms. The emergence of Electron.js made it possible to develop cross platform apps for all three of these platforms. With HTML, CSS, and JavaScript being the default languages for the web, developers can now extend their range of application development by using the same technologies to build mobile applications (made possible by the Cordova project) and desktop apps. In this article, we will be configuring a continuous integration (CI) pipeline to build distribution copies of our desktop applications for different operating systems.

Prerequisites

To follow this tutorial, a few things are required:

  1. Basic knowledge of JavaScript
  2. Node.js installed on your system (version >= 11)
  3. A CircleCI account
  4. A GitHub account
  5. Familiarity with Electron.js (Not a deal breaker but would be nice).

With all these installed and set up, let’s begin.

Creating a simple Electron project

Let’s quickly scaffold a simple Electron.js application by running the following command:

npx create-electron-app my-electron-app

In the command above, we use npx to invoke the create-electron-app utility to scaffold a new Electron.js application in the folder my-electron-app. We are using create-electron-app because it helps scaffold an Electron.js application that comes prepackaged with electron-forge, a tool used for packaging and creating distribution builds of our Electron.js desktop application.

Note: You don’t need to have create-electron-app installed globally to use it. That’s why we are using npx.

Once the scaffolding process is done, go into the root of the project (cd my-electron-app) and run the following command to start up the desktop application:

npm start

This will run a local build for your current operating system and boot up the desktop application.

App Launch

The screen above shows the development tools opened. This is not something you will want in production. Comment out the line below in the file src/index.js to stop this from opening.

mainWindow.webContents.openDevTools();

Then terminate the app running by hitting Ctrl + C while you’re on the CLI (Command Line Interface) running the application. Then run npm start again to relaunch the application.

App Launch. No devtools

Now, as seen in the screen above, only our application screen is displayed in the app window.

Configuring the build platforms

As mentioned earlier, the scaffolded project already comes packaged with electron-forge, which is a package that can help us produce distribution builds from our app. In our case, we are going to be creating a distributable .zip build for Linux platforms and a .deb package for Debian-based Linux distributions such as Ubuntu.

To configure this, replace the config section in the package.json file with the following configuration:


"config": {
  "forge": {
    "packagerConfig": {},
    "makers": [
      {
        "name": "@electron-forge/maker-zip",
        "platforms": [
          "linux"
        ]
      },
      {
        "name": "@electron-forge/maker-deb",
        "config": {}
      }
    ]
  }
},

The electron-forge package uses a number of internal utilities known as makers to create distribution builds for different platforms. makers exist for creating distribution builds for MacOS, Windows, and Linux platforms. In the file above, we have configured two makers, maker-zip and maker-deb, to generate the .zip file for Linux and the .deb file for Debian-based platforms, respectively.

For more information about configuring makers for different platforms, visit the makers documentation page.

Now we have the complete electron-forge configuration to create our distribution builds.

Connecting the project to CircleCI

Our next task is to get our Electron.js desktop application project set up on CircleCI. Begin by pushing your project to GitHub.

Next, go to the Add Projects page on the CircleCI dashboard.

Add Project - CircleCI

Click Set Up Project to begin.

Add Config - CircleCI

On the setup page, click Add Manually to instruct CircleCI that we would be adding a configuration file manually and not using the sample displayed. Next, you get a prompt to either download a configuration file for the pipeline or start building.

Build Prompt - CircleCI

Click Start Building. This build will fail because we have not set up our configuration file yet. This we will do in the next section.

Automating distribution builds and storing the build outputs

Time to write our CI pipeline. The following operations need to be done by our pipeline:

  • Checkout the repository
  • Update npm
  • Install project dependencies
  • Install system dependencies for target builds (The .deb package requires two system packages; dpkg and fakeroot, to be installed for a .deb file to be generated)
  • Generate the builds
  • Store the distributable build files as artifacts that can be downloaded

Inside the root of the project, create a new folder named .circleci. Within this folder, create a file named config.yml and enter the following code into it:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:11
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm"
      - restore_cache:
          key: dependency-cache-{{ checksum "package.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - run:
          name: Install dpkg and fakeroot
          command: |
            sudo apt-get update -y
            sudo apt-get install -y dpkg fakeroot

      - save_cache:
          key: dependency-cache-{{ checksum "package.json" }}
          paths:
            - ./node_modules
      - run:
          name: Generate Builds
          command: npm run make

      - store_artifacts:
          path: ~/repo/out/make

In the above configuration file, we perform all of the tasks that we listed earlier. Let’s go through the important sections.

First, we specify the working directory for the operations we want to perform. We then specify the Docker image with the minimum required Node.js installation.

Next, we checkout the project from the remote repository and install dependencies. Then we install dpkg and fakeroot using admin privileges.

We finish off by generating our configured builds by running the electron-forge package’s make command.

Finally, we store the distributable files, which are located in a generated /out/make folder.

Commit and push these changes to your GitHub repository. This will trigger the pipeline to go into action and run. You can view it running on the Pipelines page of the CircleCI console.

Build Success - CircleCI

You can view the process details by clicking build.

Build Details - CircleCI

As seen in the above screen, the Upload artifacts section shows that we now have our two builds generated and stored.

Downloading stored build artifacts

Downloading our builds is pretty easy, click the Artifacts tab on the build screen and you will see links to download your distributables.

Download distributables - CircleCI

You can now download both the zipped package and the .deb installation file.

Conclusion

With Electron.js adding more superpowers to web developers by enabling the creation of desktop applications with HTML, CSS, and Javascript, it feels like web developers can now build almost anything. This is pretty exciting!

Distributing applications to end-users is also a very important part of that process and in this tutorial, we have seen how easy it is to create installable desktop applications and even automate the process of packaging distributable builds using CircleCI.

If you are not getting the desired results, please go through the article once again to see if there’s anything you may have missed.

Happy Coding!


Fikayo is a fullstack developer and author with over a decade of experience developing web and mobile solutions. He is currently the Software Lead at Tech Specialist Consulting and develops courses for Packt and Udemy. He has a strong passion for teaching and hopes to become a full-time author.