Use ES modules in Electron

build-electron

Use ES modules in Electron now!

build-electron is a simple build tool for main and preload code of your Electron app, so you don’t have to setup a webpack build system yourself. The aim is to make it easier to get started building Electron apps, like it used to be.

Background

Because Electron does not support ES Modules, and the Node ecosystem is shifting to ESM we are lacking an easy way to simply use ESM Node.js code and modules in our Electron projects. A boilerplate is possible, but it can get outdated quickly and hard to maintain with new updates. So I created this project in the spirit of Create React App. When Electron supports ESM in the future ?, build-electron can simply be unplugged from the project and it will hopefully “just work”.

Features

  • Simple – Doesn’t build frontend; less hairy logic
  • Supports Webpack 5
  • Customizable
  • Building for production or watching

How to use

Note that this project provides Node.js code building only (main, preload) – not renderer code due to there being so many different languages and frameworks for that, and there are already excellent tools for building those. For React based Electron apps, I recommend pairing with Create React App (CRA).

yarn add -D build-electron concurrently wait-on

Put your Electron main ESM source code in src/main/index.js and preload source in src/preload/index.js.

Now create a configuration file in your project root build-electron.config.js:

module.exports = {
  mainEntry: 'src/main/index.js',
  preloadEntry: 'src/preload/index.js',
  outDir: 'build',
  mainTarget: 'electron16.0-main',
  preloadTarget: 'electron16.0-preload',
}

Add to your package.json:

{
  "main": "build/main.js",
  "build": {
    "files": [
      "build/**/*"
    ]
  },
  "scripts": {
    "start": "concurrently -k \"build-electron -d\" \"wait-on build/.build-electron-done && electron .\"",
    "build": "build-electron"
  }

Optionally add your frontend builder like CRA (see below for example).

Now you can start developing:

npm run start

And to build your production app:

npm run build && npm exec electron-builder --mac

Using with Create React App

In this example we will use CRA and electron-builder, although it should be similar with any other framework too.

yarn create react-app my-awesome-app
cd my-awesome-app
yarn add -D build-electron electron electron-builder concurrently wait-on

Now let’s create the project structure. Because CRA uses a particular directory structure (and src is reserved for the frontend source), we have to adapt to that. The relevant structure will be like this:

? my-awesome-app
  ? src-main
    ? index.js
  ? src-preload
    ? index.js
  ? src
    [React source]

  ? public
    ? main.js [generated by build-electron]
    ? preload.js [generated by build-electron]

  ? build [generated by CRA]
  ? dist [generated by electron-builder]

  ? package.json
  ? .gitignore
  • ? src-main – Source for the electron main process (where you create your BrowserWindow). Entry point index.js bundles to public/main.js.
  • ? src-preload – Source for the electron preload script. Entry point index.js bundles to public/preload.js.
  • ? src – Static frontend React files processed by CRA and outputted into build, along with files from public.
  • ? public – Output of build-electron‘s entry points (src-main and src-preload), and is the input of the CRA build’s static files.
  • ? dist – Final production app output generated by electron-builder.

Now create a configuration file in your project root build-electron.config.js:

module.exports = {
  mainEntry: 'src-main/index.js',
  preloadEntry: 'src-preload/index.js',
  outDir: 'public',
  mainTarget: 'electron16.0-main',
  preloadTarget: 'electron16.0-preload',
}

Relevant bits to add to your package.json:

  "main": "public/main.js",
  "build": {
    "extraMetadata": {
      "main": "build/main.js"
    },
    "files": [
      "build/**/*"
    ]
  },
  "scripts": {
    "start": "concurrently -k \"BROWSER=none react-scripts start\" \"build-electron -d\" \"wait-on public/.build-electron-done http://localhost:3000 && electron .\"",
    "build": "build-electron && react-scripts build",
    "postinstall": "electron-builder install-app-deps"
  }

Add to .gitignore:

/build
/dist
/icon-build
/public/main.js
/public/preload.js

Now you can start developing:

npm run start

And to build your production app:

npm run build && npm exec electron-builder --mac

build-electron.config.js options

Note that paths can be relative to project root or absolute.

  • mainEntry – Electron’s main entrypoint.
  • preloadEntry – Electron’s preload entrypoint.
  • mainExtraEntries, preloadExtraEntries – Include additional main or preload entrypoints:
    • Key-value pairs, example: { 'name': 'path/to/source' }
  • outDir – Output files to this path.
  • externals – Use this to exclude certain modules from being bundled, like native dependencies.
  • customConfig – Customize Webpack config for both main.js and preload.js
  • customMainConfig – Customize Webpack config just for main.js
  • customPreloadConfig – Customize Webpack config just for preload.js
  • mainTarget – Should use electronX.Y-main. See Webpack target
  • preloadTarget – Should use electronX.Y-preload. See Webpack target

build-electron CLI

  • --config, -c – Override path to build-electron.config.js (default is in project root)
  • --dev, -d – Run in development mode with file watcher (default is production build + exit after finish)

Size optimizations

Make sure you follow these guidelines:

  • Any npm dependencies that have binaries (native Node.js modules) must be in dependencies and included in the externals options so they don’t get bundled in
  • All other dependencies should be devDependencies to prevent them from being included by electron-builder.

Source Maps

yarn add -D source-map-support

Add to the top of src-main/index.js:

import 'source-map-support/register';

TODO

Alternatives / inspiration

Development

To test in a consuming project (yarn v2+), add to its package.json:

"resolutions": {
  "build-electron": "portal:/path/to/build-electron"
}

then run yarn.

GitHub

View Github