owned this note
owned this note
Published
Linked with GitHub
# [Discussion] A new future for Native Modules in Electron
### A History
A bit of history before we get started. Loading native modules in Electron has always been a bit of a tricky area. Between rebuilding, linking to the right `node.lib/dll` and ensuring your `NODE_MODULE_VERSION` was correct it was always a bit of a pain. Luckily one of the biggest pain points Electron experienced most users have never had to deal with. Until recently, it was impossible to load multiple instances of the same native module, in the same process, but in different v8 contexts.
This means if you imagine this scenario:
* Load https://google.com in BrowserWindow
* Process is launched
* Preload script runs and loads native module X
* Load https://google.com/foo in BrowserWindow
* Chromium decides to reuse the process
* Preload script runs and loads native module X
* ERROR _ BAD _ OH NO
Node simply wouldn't let you do it, in order to mitigate this issue for users without them ever having to worry about it, we patched Chromium to ensure that **every** navigation resulted in a new process being created. This worked 99.9% of the time, it still had a few edge cases (E.g. #4025, #12045, #17576) and a few side effects.
* We lost all the performance gains from the Chromium plz-navigate changes :cry:
* We have strange behavior with `window.open` and `window.opener` as we force child windows into separate processes sometimes. :cry:
* And we have other strange behavior / regressions in this patch every now and again because it isn't a process model that Chromium accepts / tests. :cry:
We've always though "wouldn't it be cool if we could change this" and now with the introduction of Node 12 and most importantly [Worker Threads](https://nodejs.org/api/worker_threads.html) we can.
### Worker Threads
When worker threads were implemented in NodeJS they instantly ran into the same issue, native modules loaded in one worker thread, could not be loaded in another. In order to solve this Node implemented the concept of ["Context Aware"](https://nodejs.org/api/addons.html#addons_worker_support) native modules. This was a way that native modules could declare themselves compatible with multiple `v8::Context`s (JS environments) and then node would load them multiple times correctly.
[Nan](https://github.com/nodejs/nan) even has a handy helper for you to do this `NAN_MODULE_WORKER_ENABLED` instead of `NODE_MODULE`. With these "Context Aware" native modules and NAPI modules becoming more used and implemented we can finally start looking into how we can use this work in Electron
### What does this mean for Electron?
Right now nothing, however, at some point in the future (think several major versions away) we intend to remove our patches that we apply against Chromium to change the process model and enforce that if you want to use native node modules in the renderer process they **have** to be either Context Aware or built using the NAPI.
Just to re-iterate, this change is not going to happen tomorrow, or next week or probably not this year, but it will happen and anyone who uses or maintains a native module should start looking at the work to mark them as Context Aware. For lots of modules it's as easy as replacing `NODE_MODULE` with `NAN_MODULE_WORKER_ENABLED`, for modules that require some clean-up it can be a bit more involved.
You may be asking, why? Why would you change this thing that is working right now? So I'll summarize it below, making this change:
* **Performance**, if we don't restart the renderer for every navigation certain same-site navigations will be WAYYY faster than before :smile:
* **Security**, if we follow Chromiums process model exactly we will benefit from all the the optimizations and decisions they have made around Site Isolation and other such initiatives. :smile:
* **Maintenance**, this reduces a hefty maintenance burden on us, the maintainers, these patches are not easy to keep working especially as Chromium makes more and more performance improvements and optimizations around process use. :smile:
### What does this look like for Native Module X?
It entirely depends on the native module as to how much work is required to make it Context Aware. For native modules that act as proxies for native getters and setters (i.e. they just expose JS apis to acccess native apis) it's normally as easy as adding a `NAN_MODULE_WORKER_ENABLED` declaration. You can find an example of that in [this pull request](https://github.com/atom/node-keytar/pull/182/files).
For some modules you need to do some cleanup when the context is destroyed to ensure your module isn't leaking memory or going to cause crashes. You can add hooks to clean up after yourself with `node::AddEnvironmentCleanupHook` A minimal example of this can be found in [this pull request](https://github.com/CharlieHess/node-mac-notifier/pull/32/files).