electron / electron/forge

Add API to WebpackPlugin for compiling utilityProcess code

Open
#3,169 6 comments 29 reactions 0 assignees View on GitHub
plugin/webpack
Dominant language
TypeScript
Stars
7.1k
Forks
641
Avg merge
3d 1h
Merged PRs (30d)
30

Description

### Pre-flight checklist

- [X] I have read the [contribution documentation](https://github.com/electron/forge/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [code of conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project uses.
- [X] I have searched the issue tracker for a feature request that matches the one I want to file, without success.

### Problem description

Currently, the `WebpackPlugin` only takes Webpack configs for the main and renderer process. If I want to create a `utilityProcess` in Electron, there's no way to compile a separate bundle for that code.

### Proposed solution

I propose adding an option to the `WebpackPlugin` constructor to pass a Webpack config for `utilityProcess` code, so that it is compiled alongside the main and renderer process code. Similar to the renderer process, the `utilityProcess` bundle would also have a global variable exposed in the main process indicating from where to load the compiled module.

### Alternatives considered

For now, I have written a custom Forge plugin to mimic this behavior, but I do not think this is a stable solution. To do this, I have created a plugin called `UtilityProcessPlugin`:

```typescript
import { webpack, Configuration as RawWebpackConfiguration } from "webpack";
import { PluginBase } from "@electron-forge/plugin-base";
import { ForgeHookMap } from "@electron-forge/shared-types";

export default class UtilityProcessPlugin extends PluginBase {
name = "utility-process";

getHooks(): ForgeHookMap {
return {
generateAssets: this.generateAssets.bind(this),
};
}

private async generateAssets(): Promise {
return new Promise((resolve, reject) => {
webpack(this.config, (err, stats) => {
if (err) {
reject(err);
return;
}
if (stats?.hasErrors()) {
const json = stats.toJson();
for (const error of json.errors ?? []) {
reject(new Error(`${error.message}\n${error.stack}`));
return;
}
}
resolve();
});
});
}
}
```

Then, I have created a separate config for the `utilityProcess`, like so:

```typescript
import { Configuration } from "webpack";
import path from "path";

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

const modulePath = path.resolve(__dirname, "dist_utility/utility_process");

export const utilityConfig: Configuration = {
entry: "./src/utility.ts", // Change to your own entry point
target: "node",
module: {
rules,
},
output: {
path: modulePath,
filename: "index.js",
},
resolve: {
extensions: [".js", ".ts", ".jsx", ".tsx", ".css", ".json"],
},
// TODO: find a way to infer this based on whether we run electron-forge start
// or package.
mode: "development",
};
```

Finally, to expose the compiled location, I use the `DefinePlugin` from Webpack to manually set the global variable in the main process config:

```typescript
new DefinePlugin({
UTILITY_PROCESS_MODULE_PATH: JSON.stringify(modulePath),
}),
```

As you can see in my TODO for the `utilityProcess` config, I believe it is currently not possible to tell whether we are running `electron-forge start` or `electron-forge make`, which makes it hard to pass the correct `mode` value to Webpack. This also feels like a less stable approach than having something natively supported by the `WebpackPlugin`.

### Additional information

The main use case for this feature is that you want to share code across your main process, renderer process, and utility process. If you are using Typescript for the common code, for example, then sharing without using a Webpack build step is impossible.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.