For my first electron app with react i’ve used CRA.
What is kinda comfy to start, but pretty soon I’ve faced a problem that react scripts cannot properly pack
I’ve blamed CRA at the beginning, however some research I’ve found that neither neither parcel, neither webpack won’t solve the problem in a simple and transparent way.
The problem is — if you try to import
ipcRender in web application, it crashes with
TypeError: fs.existsSync is not a function.
To be more precise this ↓
will end-up like this ↓
Without deep sh*t — it cannot be packed, it should be requested by old good
There there are several solutions that, described below, mind the one that suits you more and all of them are pretty simple.
In ./public/electron.js enable
nodeIntegration which will allow use of node packages in renderer process.
Mind this is very VULNERABLE approach, and described just for educational purposes. To be clear, with
nodeIntegrationenabled, the successful XSS attack will provide the malefactor with all the power of NodeJS under privileges of user running the app.
CRA uses webpack,
and webpack has
__non_webpack_require__ function that behaves like classic
require instead of prepacking the specified module.
Fo reuse across react app let’s put it in a separate module say ./src/appRuntime.ts.
Further in react app, just import it from
./src/appRuntime.ts not from
For example in ./src/App.tsx
First thing is to disable
and specify path to preload script, in ./public/electron.js
nodeIntegration is disabled, the classic
require function won’t work within rederer context anymore,
however it is still available in preload script context.
Therefore we can resolve
electron using classic
require and store
ipcRenderer in a global variable in ./public/preload.js.
In react app we can expect
ipcRender to be available in a global context,
for more convenient use across the app, can use separate module like ./src/appRuntime.ts
Finally, same as in previous approach, further in react app,
just import it from
./src/appRuntime.ts not from
For example in ./src/App.tsx.
As renderer process does not have access to
require node modules,
this is less vulnerable as previous approach and quite a popular approach.
However even tho it might seem harder to exploit,
the hybrid context is still vulnerable to prototype pollution attach
that once again can give the malefactor full control of a NodeJS process.
As hybrid context is a vulnerability, it can be switched off by enabling
nodeIntegration should be disabled as well, and we’ll still gonna need the
preload script to expose some APIs to the renderer, in ./public/electron.js.
contextIsolation being enabled,
the preload script will still have access to node modules,
it will isolated from main renderer process so won’t share global variables as well.
Therefore there is a
contextBridge to expose APIs we need to renderer in ./public/preload.js
exposeInMainWorld function takes two arguments.
apiKey will be the name global variable, under which, the exposed api will be available in renderer.
api the object to be exposed.
Also notice that in subscribe, the listener is not being passed directly to ipcRenderer, it’s wrapped in function that prevents leak of ipcRednerer’s event object to a renderer process.
In react app we can expect that exposed object to be available under global variable
appRuntime, as we named it in
and what’s left is just to add some typings let’s say in ./src/appRuntime.ts
Mind that everything you are exposing to renderer process is vulnerable to XSS attacks. So keep it simple — primitives in, primitives out.
Further in react app, just import and use the exported api, for example in ./src/App.tsx
Good luck, with your great app :)