typescriptwebpackelectronelectron-forgenative-module

ElectronForge native npm module


I've got a project that is using ElectronForge v6.0.3 with TypeScript and WebPack, I'm loading a native npm, module leveldb-zlib I've got it to work when running electron-forge start but when I run electron-forge make it throws the following error

enter image description here

I'm importing leveldb-zlib like so import { LevelDB } from "leveldb-zlib";

I'm not importing the module from the renderer.

Here are the webpack configs

webpack.rules.ts

import type { ModuleOptions } from 'webpack';

export const rules: Required<ModuleOptions>['rules'] = [
  // Add support for native node modules
  {
    // We're specifying native_modules in the test because the asset relocator loader generates a
    // "fake" .node file which is really a cjs file.
    test: /native_modules[/\\].+\.node$/,
    use: 'node-loader',
  },
  {
    test: /\.(m?js|node)$/,
    parser: { amd: false },
    use: {
      loader: '@vercel/webpack-asset-relocator-loader',
      options: {
        outputAssetBase: 'native_modules',
      },
    },
  },
  {
    test: /\.tsx?$/,
    exclude: /(node_modules|\.webpack)/,
    use: {
      loader: 'ts-loader',
      options: {
        transpileOnly: true,
      },
    },
  },
  {
    test: /\.(woff|woff2|ttf|eot|png|jpg|svg|gif)$/i,
    use: ['file-loader']
  }
];

webpack.main.config.ts

import type { Configuration } from 'webpack';

import { rules } from './webpack.rules';

export const mainConfig: Configuration = {
  /**
   * This is the main entry point for your application, it's the first file
   * that runs in the main process.
   */
  entry: './src/index.ts',
  // Put your normal webpack config below here
  module: {
    rules,
  },
  resolve: {
    extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'],
  },
  externals: {
    "leveldb-zlib": "leveldb-zlib"
  }
};

!!! EDIT

I managed to get it to compile by adding the following hooks to the forge.config.ts, HOWEVER this makes it take forever to compile..

(I did not come up with this solution, I just modified it for typescript)

import type { ForgeConfig } from '@electron-forge/shared-types';
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { WebpackPlugin } from '@electron-forge/plugin-webpack';

import { mainConfig } from './webpack.main.config';
import { rendererConfig } from './webpack.renderer.config';

import * as path from "path";
import * as fs from "fs";
import { spawn } from "child_process";

const textDecoder = new TextDecoder();

const config: ForgeConfig = {
  packagerConfig: {},
  rebuildConfig: {},
  makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
  plugins: [
    new WebpackPlugin({
      mainConfig,
      renderer: {
        config: rendererConfig,
        entryPoints: [
          {
            html: './src/index.html',
            js: './src/renderer.ts',
            name: 'main_window',
            preload: {
              js: './src/preload.ts',
            },
          },
        ],
      },
    }),
  ],
  hooks: {
    readPackageJson: async (_forgeConfig, packageJson) => {
      // only copy deps if there isn't any
      if (Object.keys(packageJson.dependencies).length === 0) {
        const buffer = fs.readFileSync(path.resolve(__dirname, 'package.json'));
        const originalPackageJson = JSON.parse(textDecoder.decode(buffer));

        Object.keys(mainConfig.externals as Record<string, unknown>).forEach(key => {
          packageJson.dependencies[key] = originalPackageJson.dependencies[key];
        });
      }
      return packageJson;
    },
    packageAfterPrune: async (_forgeConfig, buildPath) => {
      return new Promise((resolve, reject) => {
        const npmInstall = spawn('npm', ['install'], {
          cwd: buildPath,
          stdio: 'inherit',
          shell: true
        });

        npmInstall.on('close', (code) => {
          if (code === 0) {
            resolve();
          } else {
            reject(new Error('process finished with error code ' + code));
          }
        });

        npmInstall.on('error', (error) => {
          reject(error);
        });
      });
    }
  }
};

export default config;

Solution

  • the edit fixed it, also added --production to the npm install command.