Sharing code between main and renderer processes
See original GitHub issueIs your feature request related to a problem? Please describe. This repository is posited as an alternative to electron-webpack as the library is now in “maintenance mode” as stated in readme.
In electron-webpack, is it possible to share resources between main and renderer processes. This allows for best practices such as avoiding the hard-coding of event name strings for IPC communication via constants. In this case, common code resources are located in a folder which is a sibling to main and renderer folders.
Describe the solution you’d like This repo should include configuration that enables sharing of code between main and renderer builds.
Describe alternatives you’ve considered I’ve attempted to use the solution posited in this issue but this causes typescript errors. Aliases present DX issues because one can’t use regular import statements like in webpack and must resort to some kind of module resolver which is beyond the scope of someone who wants to get started on an electron project quickly and easily.
Another solution I’ve attempted: move index.html file directly inside packages folder, setting the PACKAGE_ROOT to be packages folder. For renderer, main, and preload folders, in vite config files, change build.lib.entry value to be “[folderName]/src/index.ts” and change index.html script src to be “./renderer/src/index.tsx”. This does not fix the issue and the watch tasks errors out:
Error: Cannot find module '../../common/constants'
Require stack:
- /home/user/code/polyhedral-net-factory/packages/main/dist/index.cjs
- /home/user/code/polyhedral-net-factory/node_modules/electron/dist/resources/default_app.asar/main.js
Issue Analytics
- State:
- Created 2 years ago
- Comments:8 (1 by maintainers)
Top GitHub Comments
I’d just like to explain how I share type-safe IPC between render and main process. Unfortunately, I don’t have a nice template repo like this to share, so I’ll just show code fragments.
First, I have a
packages/api
project which only contains type definitions, no code. In thepackages/api
project, I define interfaces for the various things that will be sent between renderer and main and then defineEach request or response defines both the request type and the response type (
TheFileData
is an interface for the contents of the file.)I then define
This automatically pulls out the message types, the request type for a specific message, and the response type for a specific message. You get compile errors if you try and get the request type of a message that does not exist.
In
packages/main
I define the handlers for each message as follows. First, defineWhat is nice is that you get a compile error if you forget one of the types (
MessageHandlers
specifies[key in ProjectNameIpcMessageTypes]
which requires a key for each message. Also, the type of thereq
parameter is automatically inferred and you get a compile error if you return the wrong response. For large handlers, I move the handler function out into its own file and import it when creating thehandlers
object.Since the handlers are in main instead of preload, I need to bridge main and preload which I do using
MessageChannelMain
.In
main
, I have code likeEach message is an object containing an
id
, thetype
, and therequest
. We then lookup the handler in thehandlers
object, make the call, and send a response with the sameid
.For
port2
, it is sent to the renderer process once thedid-finish-load
event occurs, which means the renderer has had time to attach an event handler for theprojectname-init
.The preload script is very short because all the work is done in main and the renderer. It just shuffles the projectnameInit message along to the renderer, passing along the port. This is the entierty of my
preload.js
which I don’t even bother to write in typescript or compile.Finally, the renderer contains code to send and receive messages over the port using the specific api types imported from
packages/api
.Each mesage has an incrementing id and the
inFlight
map stores message id to the response function. ThehandleProjectInit
function listens for the message from preload with the port for communication. It resolves the port and attaches a handler to messages coming over the port. When a message arrives on the port, it looks up the handler ininFlight
by message id.The
sendIpc
function, which is the main export, creates a promise which first waits for the port. (This allows code to callsendIpc
before the port from main arrives and correctly suspends them.)sendIpc
then sets up the response in theinFlight
map and then sends the request across the port.Finaly,
sendIpc
is typed so that you can only call it with a message type defined inprojects/api
. VSCode even pops up the suggestion box with a list of messages. The request and response are then inferred from the specified message type. SincesendIpc
returns a promise, it can be awaited everywhere throughout the renderer.For sharing actual code I just use a “local” dependency. It’s a little klunky because you need to do “yarn install” when you make changes to the common code, and it gets duplicated across the two parts of the code, but it’s nice for things like shared string ID’s. You can see it in action if you want right here https://github.com/kevinfrei/EMP/commit/0c1af4026a0c254157dcc0a4437e854873fa9626