44

I originally thought that the renderer process in Electron was sandboxed in a chrome-like environment, meaning all you can do is mess with the DOM. However, I recently learned that you can access the filesystem, run child processes and get their output, and import any other node modules you want.

If this is the case, what exactly is the distinction between the main process and the renderer process? Is it not a hard separation? What kind of code goes in the main process and what kind of code goes in the renderer process?

If anyone has a good in-depth reading/presentation on Electron app architecture I would love to look at that too; might help clear up some of the confusion

rcplusplus
  • 2,767
  • 5
  • 29
  • 43

1 Answers1

60

The main/renderer process distinction isn't actually an Electron concept per se -- it's inherited from Chromium (here's an article on Chromium's architecture and the reasoning behind it). This is the architecture that Chrome uses for performance and stability reasons. Each webContents instance runs in it's own process (a "renderer" process). The main process (there can only be one of these) manages webContents instances, among other things.

There's a good discussion here on the differences between the two.

Some APIs are only available in one process or the other, which can help you get a sense of what logic goes where. For example, Notifications (uses the HTML5 interface but are implemented as native notifications) can only be created from a renderer process. The Menu class can only be called from within the main process. Read through the Electron modules' API docs and see what goes where. You can use IPC, remote module, or electron-remote to coordinate between the two processes (which one you use depends on your use case).

I would say it is a "hard" separation. They're all separate processes and thus don't share any resources or state. This is a paradigm shift for most JS devs I think (at least it was for me). For example if I had a stateful module that I set some state in in the main process, and then I required that module in the renderer, that state won't be there. They're two entirely different instances of that module. Sharing state like that would probably be best in the main process and then either use one of the methods described above to share that state between renderer processes.

Here is a list of real life apps and some sample apps.

Shawn Rakowski said it well (in comments below): "It may be a good rule to put code dealing with the platform infrastructure code (i.e. creating windows, registering global shortcuts, etc) in the Main process and application specific code (what your app is actually doing) in the Renderer processes."

[My app's functionality is it] parses some files and then renders the info to the screen

There's lots of approaches you can take to this in Electron because the fs module (and all node.js modules) are available to you in the renderer process.

If you're only dealing with one browser window instance and not doing CPU intensive parsing, I would say run all the fs related code in that renderer process instance. That's the simplest way.

If you're doing CPU intensive work on these files, you don't want to lock up the UI, which means you can't do your processing browser window renderer, and you can't do it in the main (this will lock up all your renderers!). So I would look into something like electron-remote or create an invisible browser window instance that runs the heavy lifting.

This article about the main and renderer processes talks about these topics more in-depth (disclosure: I wrote that).

ccnokes
  • 6,885
  • 5
  • 47
  • 54
  • 1
    Ok, so it seems I can 90% stick to one process. But the question is, should most of my code be in the main process or the renderer process? – rcplusplus Jun 07 '16 at 03:27
  • It might depend on your application but I would say in the apps that I've seen, most of the code is in a renderer process. Many people use frameworks like Angular2 or React for the renderer process. What kind of app are you working on? Here are some sample apps that might give you some ideas: https://github.com/hokein/electron-sample-apps. – ccnokes Jun 07 '16 at 03:51
  • For example, if your app is like Dropbox then I would think a lot of your code is the main (syncing and downloading content to the fs seems like a better fit for the main process to me). If your app is like Slack, I would think more of it is in the renderer (lots of UI involved for chat). Hope that makes sense. It all depends. – ccnokes Jun 07 '16 at 03:59
  • It just parses some files and then renders the info to the screen. My first thought was that it should all be in the main process because the main process kinda seemed like a back-end, but upon learning that the renderer process can access the file system with `fs`, I'm lost again. Then I thought ok if I want to spawn a worker I need to be in the main process, nope! `child_process` can be included in renderer processes! – rcplusplus Jun 07 '16 at 04:02
  • This framework just confuses me a bit because there's no compelling reason to put code in either place. The app would be pretty much identical either way. This is driving me insane however because there has to be some convention or some preferred method. – rcplusplus Jun 07 '16 at 04:04
  • 1
    It is worth noting that you can turn off Node integration in the Renderer process, if desired, which would give new meaning to the Main process :-). The documentation also notes that dealing with the creation and modification of the BrowserWindows themselves is not conducive to a Renderer process because of potential leaks. It may be a good rule to put code dealing with the platform infrastructure code (i.e. creating windows, registering global shortcuts, etc) in the Main process and application specific code (what your app is actually doing) in the Renderer processes. – Shawn Rakowski Jun 07 '16 at 04:21
  • @ShawnRakowski are there any advantages to disabling node integration? Also, that is a very good way of describing the split! Renderer = app logic and main = very platform specific stuff (not done often) – rcplusplus Jun 07 '16 at 04:46
  • 3
    Of course there are exceptions to that, but if you want a general rule it may be a good one to follow. You'll generally leave Node integration on unless you want a window that is more sandboxed... e.g. loading untrusted content from the web. – Shawn Rakowski Jun 07 '16 at 13:04
  • 2
    @ccnokes Regarding blocking the UI, I put an infinite while loop in the main and the renderer process and both times it blocked the UI. I thought the main process didn't handle rendering at all, just managed windows? – rcplusplus Jun 07 '16 at 14:39
  • @ccnokes I noticed that too, I have the similar problem - but wwith a real world code example: https://discuss.atom.io/t/electron-ui-browserwindow-freezes-my-main-ui-browserwindow/37197 It's suggested it runs different threads, but I see it still blocks, so I don't think it's totally seperated. – Yves Schelpe Dec 19 '16 at 14:56
  • 2
    @YvesSchelpe that's true I was wrong in my answer and I'll update it soon so it's correct. Blocking code in the main will block ALL your renderers unfortunately. However, for most use cases, JS runs fast enough it won't ever actually block (or it won't be noticeable to the user). See my updated answer for more details... – ccnokes Dec 20 '16 at 04:04
  • It's no longer true that `require()` is available in renderer processes even with `webPreferences.nodeIntegration=true`. You have to inject it with a preload script. – Marc Jan 17 '21 at 09:43