node.jselectronelectron-buildernode-gypasar

electron-builder application cannot find node-gyp compiled *.node module


I want to run an express server backend from the electron main thread, which uses a C++ module compiled with node-gyp (called BackendCppNode.node).

The backend script includes the module using const backendCpp = require('./BackendCppNode.node'). When running in electron, it works fine, but when packaged with electron-builder it does not work. When running the packaged application, the .node file is not found though it exists in the temporary folder created to run the application.

The exact error message is

[2023-06-23T13:40:59.531Z] [31merror[39m: uncaughtException: The specified module could not be found.
\\?\C:\Users\XXX\AppData\Local\Temp\6c7c5c03-6990-4ca0-917b-4f8cd9ab0cf0.tmp.node
Error: The specified module could not be found.
\\?\C:\Users\XXX\AppData\Local\Temp\6c7c5c03-6990-4ca0-917b-4f8cd9ab0cf0.tmp.node
    at process.func [as dlopen] (node:electron/js2c/asar_bundle:2:1822)
    at Module._extensions..node (node:internal/modules/cjs/loader:1259:18)
    at Object.func [as .node] (node:electron/js2c/asar_bundle:2:2049)
    at Module.load (node:internal/modules/cjs/loader:1044:32)
    at Module._load (node:internal/modules/cjs/loader:885:12)
    at f._load (node:electron/js2c/asar_bundle:2:13330)
    at Module.require (node:internal/modules/cjs/loader:1068:19)
    at require (node:internal/modules/cjs/helpers:103:18)
    at Object.<anonymous> (C:\Users\XXX\AppData\Local\Temp\2RbnOiiTeglX50ZVX8JnWd0mNKr\resources\app.asar\dist\backend\backend.js:9:20)
    at Module._compile (node:internal/modules/cjs/loader:1174:14)

The Temp folder "2RbnOii...." and the file "6c7c5c....tmp.node" exist and the .node file is the required file. A .js file, which is also included in the same way as the .node file, is unpacked to the temp folder and included correctly. Only the .node file is copied to a different location and not found.

My electron-builder configuration (in package.json) is

  "build": {
    "appId": "XXX",
    "productName": "XXX",
    "files": [
      "app.js",
      "preload.js",
      "dist/**/*"
    ],
    "directories": {
      "output": "release"
    },
    "win": {
      "icon": "/src/assets/icons/XXX.png",
      "target": "portable"
    },
    "nodeGypRebuild": false
  },

The backend.js script and the BackendCppNode.node file are in dist/backend/. The backend.js script is started in the electron main thread with

  const resourcePath = path.join(__dirname, 'dist');
  const env = { ...process.env };
  backend = fork(path.join(resourcePath, 'backend', 'backend.js'), [], { cwd: path.join(__dirname, '..'), env, silent: true });

How can I use C++ modules with packaged electron applications?


Solution

  • I found the solution: The problem was not that the .node file was not found, as the error message states, but the .node file required a dll which was not found (zlibwapi.dll), so the .node file was found, but could not be loaded.

    I had to do two things to get it work:

    1. I had to add the .dll file as extra file in the package.json
    "win": {
      "icon": "/src/assets/icons/XXX.png",
      "target": "portable",
      "extraFiles": [
        {
          "from": "dist/backend/zlibwapi.dll",
          "to": "."
        }
      ]
    }
    

    This causes the dll to be in the same location as the unpackaged .exe file (C:\Users\XXX\AppData\Local\Temp\2RbnOiiTeglX50ZVX8JnWd0mNKr in the example posted).

    1. I had to change the working directory of the fork command to the same path:
    var basepath = path.resolve(app.getPath('exe'), '..');
    const env = { ...process.env };
    backend = fork(path.join(resourcePath, 'backend', 'backend.js'), [], { cwd: basepath, env, silent: true });
    

    In the process, I also dropped the nodeGypRebuild option, I do not know if this also has an influence.