Cinan's world

GNU/Linux & free software, howtos, web development, scripts and other geek stuff

Integrate Native Node.js Modules Into an Electron App (2/2)

tl;dr

  • package an Electron app into OS-specific bundle
  • save space by keeping only a few needed node_modules directories—tips & tricks

Current state

So we’ve got super simple app which uses node.js features thanks to Electron. Let’s say it evolves into 100k LOC large app with dozens of dependencies (both browser-friendly and native node.js). How to produce a space-efficient bundle (in the context of Electron)?

Overview and planning

We’ll be using electron-packager to create a OS-specific distributable bundle (Electron bundle). After we build javascript (javascript bundle) we keep an eye on native modules location and node_modules content inside Electron bundle.

Build production quality javascript bundle is webpack-specific (and probably also babel-specific). I won’t cover this part as it has nothing to do with to Electron. If you use newest Webpack 4.0 you can use nice new features related to development/production mode.

Organize package.json

Electron-packager copies node_modules into the final Electron bundle (which is slow and isn’t space-efficient at all). The good news is it ignores all packages in devDependencies group in package.json. We’ll use that.

We need bindings dependency to keep in Electron bundle node_modules. The dependency is responsible for lazy loading of native node.js modules and cannot be part of javascript bundle. As it is a dependency of your project dependencies, it is not listed in package.json. Simply do npm i --save bindings. This can be tricky and can break things but yolo.

Notice deps groups:

package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "name": "electron-tutorial",
  "main": "index.electron.js",
  "scripts": {
    "build": "webpack",
    "electron": "electron .",
    "test": "jest"
  },
  "dependencies": {
    "bindings": "^1.3.0"
  },
  "devDependencies": {
    "electron": "^1.8.2",
    "electron-packager": "^11.0.1",
    "electron-rebuild": "^1.7.3",
    "jest": "^22.4.0",
    "serialport": "^6.0.5",
    "webpack": "^3.11.0"
  }
}

In projects I develop there’s usually a few non-Electron dependencies in the main Electron file (as seen in example below). Keep all non-Electron dependencies inside dependencies group (unless you plan to bundle the main file with Webpack’s target: 'electron-main' option).

index.electron.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const { app, BrowserWindow } = require('electron');
const Raven = require('raven');
const os = require('os');
const isDev = require('electron-is-dev');

const isBundled = !isDev;

if (process.env.NODE_ENV === 'production') {
  Raven.config('XXX', {
    captureUnhandledRejections: true,
    tags: {
      process: process.type,
      electron: process.versions.electron,
      chrome: process.versions.chrome,
      platform: os.platform(),
      platform_release: os.release()
    }
  }).install();
}

// ...rest of electron main file

I would keep raven and electron-is-dev in dependencies group.

Make sure there are native modules

Simply copy all native modules (*.node) to build directory (they should be built in production quality by default). I wrote a few words about them in the previous article.

There’s a tiny change in relectron-rebuild command. By default it won’t rebuild modules in devDependencies group. Run the command with t option: ./node_modules/.bin/electron-rebuild -e node_modules/electron -t prod,dev.

Note 1: I’ve run into this error while running Electron app: Uncaught Error: Could not find module root given file: "file:///Users/cinan/Coding/js/electron-tutorial/electron-tutorial-darwin-x64/electron-tutorial.app/Contents/Resources/app/build/app.js". Do you have a package.json file? This is a known bug. There is a pull request (not yet merged), you can install fixed version with npm i --save "bindings@https://github.com/ArnsboMedia/node-bindings.git#fix-getFileName-method-for-electron-use"

Can I build the package finally?

First run PLATFORM=electron npm run build to create a javacript bundle. Build native modules with ./node_modules/.bin/electron-rebuild -e node_modules/electron -t prod,dev and copy them into build directory: cp node_modules/serialport/build/Release/serialport.node build.

Now run ./node_modules/.bin/electron-packager . --overwrite and wait a minute. New Electron bundle will be created inside directory electron-tutorial-darwin-x64 (differs on Linux and Windows).

Check out node_modules in Electron bundle (in macOS it is electron-tutorial-darwin-x64/electron-tutorial.app/Contents/Resources/app/node_modules). There should be a single bindings directory. On macOS you can run the product with open electron-tutorial-darwin-x64/electron-tutorial.app.

Note 2: if you find out your node_modules directory is empty (although there are dependencies defined in package.json) then upgrade to npm@next npm i -g npm@next (related bug).

Comments