Skip to content

unpack native modules 7.11.1 #4142

Open
Open

Description

@xunrua

Pre-flight checklist

  • I have read the contribution documentation for this project.
  • I agree to follow the code of conduct that this project uses.
  • I have searched the issue tracker for a bug that matches the one I want to file, without success.

Forge version

7.11.1

Electron version

36.5.0

Operating system

window 11

Last known working Forge version

7.11.1

Expected behavior

The @electron-forge/plugin-auto-unpack-natives plugin should automatically unpack native modules like serialport and ensure they work properly within the Electron app.

Actual behavior

Despite configuring the @electron-forge/plugin-auto-unpack-natives plugin, the serialport native module is not being unpacked, causing it to fail to work correctly.

Steps to reproduce

Create a new Electron app using the command npx create-electron-app@latest my-app --template=vite-typescript.

Install and configure the @electron-forge/plugin-auto-unpack-natives plugin.

Install the serialport module and reference it in the project.

Run the app and check whether the serialport module is properly unpacked and working.

Additional information

No response

Activity

  1. added theissue type on Feb 9, 2026
  2. SebastienBrun commented on Mar 17, 2026

    @SebastienBrun

    Try running the make command with DEBUG enabled, as there is likely a hidden issue.

    PowerShell:

    $env:DEBUG="electron-forge:*"; npm run make

    CMD:

    set DEBUG=electron-forge:* && npm run make

    When building your Electron app on Windows, native modules require additional dependencies such as Visual Studio and Python in order to compile (in my case, the native module is better-sqlite3).

    If Electron Forge is able to build the project successfully, I won’t need to manually mark the module as unpacked.

    I also tested on Linux (where the required build dependencies are usually already installed), and it worked correctly.

    Ideally, the best approach would be to avoid native modules altogether and use WASM-based solutions instead.

    Please see my final configuration below.

    import { FuseV1Options, FuseVersion } from "@electron/fuses";
    import { MakerZIP } from "@electron-forge/maker-zip";
    import AutoUnpackNativesPlugin from "@electron-forge/plugin-auto-unpack-natives";
    import { FusesPlugin } from "@electron-forge/plugin-fuses";
    import { VitePlugin } from "@electron-forge/plugin-vite";
    import type { ForgeConfig } from "@electron-forge/shared-types";
    import fs from "node:fs";
    import path from "node:path";
    
    /**
     * Native modules to unpack
     */
    const nativeModules = ["xmllint-wasm"];
    
    function copyDir(src: string, dest: string): void {
      try {
        fs.mkdirSync(dest, { recursive: true });
        for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
          const s = path.join(src, entry.name);
          const d = path.join(dest, entry.name);
          if (entry.isDirectory()) {
            copyDir(s, d);
          } else {
            fs.copyFileSync(s, d);
          }
        }
      } catch (error) {
        console.error("Error copying directory:", src, dest, error);
        throw error;
      }
    }
    
    const config: ForgeConfig = {
      packagerConfig: {
        asar: true,
      },
      rebuildConfig: {
        force: true,
      },
      hooks: {
        //Mettre les assets dans le build asar file
        packageAfterCopy: async (_config, buildPath) => {
          // buildPath = dossier “app” qui sera ensuite asarisé
          const src = path.resolve("src/main/assets");
          const dest = path.join(buildPath, ".vite", "build", "assets");
          copyDir(src, dest);
    
          //Copy manually the native modules to the build directory
          const sourceNodeModulesPath = path.resolve(__dirname, "node_modules");
          const destNodeModulesPath = path.resolve(buildPath, "node_modules");
          for (const module of nativeModules) {
            const sourceModulePath = path.resolve(sourceNodeModulesPath, module);
            const destModulePath = path.resolve(destNodeModulesPath, module);
            console.log(
              "Copying native module",
              module,
              "from",
              sourceModulePath,
              "to",
              destModulePath
            );
            copyDir(sourceModulePath, destModulePath);
          }
        },
      },
      makers: [new MakerZIP({}, ["win32", "linux", "darwin"])],
      plugins: [
        //Unpack the natives to the build directory
        new AutoUnpackNativesPlugin({}),
        //Vite plugin to build the application
        new VitePlugin({
          // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
          // If you are familiar with Vite configuration, it will look really familiar.
          build: [
            {
              // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
              entry: "src/main/main.ts",
              config: "vite.main.config.ts",
              target: "main",
            },
            {
              entry: "src/preload/preload.ts",
              config: "vite.preload.config.ts",
              target: "preload",
            },
          ],
          renderer: [
            {
              name: "main_window",
              config: "vite.renderer.config.ts",
            },
          ],
        }),
        // Fuses are used to enable/disable various Electron functionality
        // at package time, before code signing the application
        new FusesPlugin({
          version: FuseVersion.V1,
          //Run the application as a node process
          [FuseV1Options.RunAsNode]: false,
          //Enable cookie encryption
          [FuseV1Options.EnableCookieEncryption]: true,
          //Enable node options environment variable
          [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
          //Enable node cli inspect arguments
          [FuseV1Options.EnableNodeCliInspectArguments]: false,
          //Enable embedded asar integrity validation
          [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
          //Only load the app from the asar file
          [FuseV1Options.OnlyLoadAppFromAsar]: true,
        }),
      ],
    };
    
    export default config;
    
    
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions