Aller au contenu principal

6 articles tagués avec "Electron Internals"

'Technical deep dives through Electron's source code'

Voir tous les tags

WebView2 et Electron

· 7 mins de lecture

Au cours des dernières semaines, nous avons reçu plusieurs questions sur les différences entre la nouvelle WebView2 et Electron.

Les deux équipes ont pour but de transposer la technologie du web pour les application de Bureau de la meilleure façon possible et une comparaison faite en commun est en cours de discussion.

Electron et WebView2 sont des projets en évolution rapide et constante. Nous avons rassemblé un bref aperçu des similitudes et des différences entre Electron et WebView2 telles qu’elles existent aujourd’hui.


Vue d’ensemble de l’architecture

Electron et WebView2 sont tous deux issus des source de Chromium pour le rendu du contenu Web. À proprement parler, WebView2 est généré à partir des sources de Edge, mais Edge est construit à l’aide d’un fork de Chromium. Electron ne partage aucune DLL avec Chrome. Les fichiers binaires WebView2 sont liés fortement à Edge (canal stable à compter d’Edge 90), et partagent certains éléments. Voir le mode de distribution Evergreen pour plus d'informations.

Les applications Electron regroupent et distribuent toujours la version exacte d’Electron avec laquelle elles ont été développées. WebView2 a deux options pour la distribution. Vous pouvez soit empaqueter la même bibliothèque WebView2 utilisée pour le développement de votre application, soit utiliser une version partagée du runtime pouvant être déjà présente sur le système. WebView2 fournit des outils pour chaque approche, y compris un programme d’installation d’amorçage au cas où le runtime partagé serait manquant. WebView2 est inclus dans Windows 11.

Les applications qui incluent des frameworks sont responsables de leur mise à jour et ce même pour les versions mineures de sécurité. Pour les applications utilisant le runtime partagé WebView2, WebView2 possède son propre programme de mise à jour, similaire à Chrome ou Edge, qui s’exécute indépendamment de votre application. La mise à jour du code de l’application ou de l’une de ses dépendances est toujours une responsabilité du développeur, comme avec Electron. Electron et WebView2 ne sont pas gérés par Windows Update.

Electron et WebView2 héritent tous deux de l’architecture multi-processus de Chromium - à savoir, un processus principal unique qui communique avec un ou plusieurs processus de rendu. Ces processus sont entièrement séparés des autres applications exécutées sur le système. Chaque application Electron est une arborescence de processus séparée qui contient un processus de navigation racine, des processus utilitaires et éventuellement d'autres processus de rendu. Les applications WebView2 qui utilisent le même dossier de données utilisateur (comme le ferait une suite d’applications), partagent les processus qui ne concernent pas le rendu. Les applications WebView2 utilisant différents dossiers de données ne partagent pas de processus.

  • Modèle des processus d'Electron:

    Modèle des processus d'Electron

  • Modèle de processus des applications basées sur WebView2 :

    Schéma du modèle du processus WebView2

Pour en savoir plus sur le modèle de processus de WebView2, et sur le modèle de processus d' Electron, cliquez ici.

Electron fournit des API pour les besoins courants des applications de bureau telles que les menus, l'accès au système de fichiers, les notifications et plus encore. WebView2 est un composant destiné à être intégré dans un framework d'application tel que WinForms, WPF, WinUI ou Win32. WebView2 ne fournit pas d'API vers le système d'exploitation en dehors de la norme web via JavaScript.

Node.js est intégré dans Electron. Les applications Electron peuvent utiliser n'importe quelle API Node.js, module ou node-native-addon depuis le moteur de rendu et les processus principaux. Une application WebView2 ne suppose pas dans quel langage ou infrastructure le reste de votre application est écrit. Votre code JavaScript doit transmettre par proxy tout accès au système d’exploitation via le processus application-hôte.

Electron s'efforce de maintenir la compatibilité avec l'API web, y compris les API développées à partir du Projet Fugu. Nous avons un aperçu de la compatibilité de l’API Fugu d’Electron. WebView2 maintient une liste similaire de différences d'API par rapport à Edge.

Electron a un modèle de sécurité configurable pour le contenu web, qui va de l'accès complet au bac à sable. Le contenu WebView2 est toujours en bac à sable. Electron a une documentation de sécurité complète sur le choix de votre modèle de sécurité. WebView2 dispose également des : bonnes pratiques de sécurité.

Les sources d'Electron sont maintenues et disponibles sur GitHub. Les applications peuvent modifier et leurs propres version d'Electron. Les sources de WebView2 ne sont pas disponibles sur GitHub.

Résumé rapide:

ElectronWebView2
Installation des dépendancesChromiumEdge
Source disponible sur GitHubOuiNon
Partage des DLL Edge/ChromeNonOui (à compter de Edge 90)
Runtime partagé entre applicationsNonFacultatif
API d’applicationOuiNon
Node.jsOuiNon
Mode bac à sableFacultatifToujours
Nécessite un Framework d'applicationNonOui
Plateformes supportéesMac, Win, LinuxWin (Mac/Linux prévu)
Partage de processus entre applicationsJamaisFacultatif
Mises à jour du framework gérées parApplicationWebView2

Discussion sur les performances

En ce qui concerne le rendu de votre contenu web, nous nous attendons à peu de différence de performance entre Electron, WebView2 et tout autre moteur de rendu basé sur Chromium. Nous avons créé ossatures d'applications construites à l'aide d'Electron, C++ + WebView2, et C# + WebView2 pour les personnes intéressées à étudier les différences potentielles de performance.

Il y a quelques différences qui entrent en jeu en dehors du rendu de contenu web, et les gens d'Electron, WebView2, Edge, et d'autres personnes ont exprimé leur intérêt à travailler sur une comparaison détaillée, y compris les PWAs.

La communication inter-processus(IPC)

Nous voulons mettre en évidence immédiatement une différence , car nous pensons qu'il s'agit souvent d'une considération de performance dans les applications Electron.

Dans Chromium, le processus du navigateur agit comme un courtier d'IPC entre les moteurs de rendu en bac à sable et le reste du système. Tandis qu'Electron autorise les processus de rendu sans bac à sable (sandbox), de nombreuses applications choisissent d'activer le sandbox pour plus de sécurité. WebView2 a toujours le sandbox activé, donc pour la plupart des applications Electron et WebView2 les IPC peuvent affecter les performances globales.

Même si Electron et WebView2 ont des modèles de processus similaires, les IPC sous-jacents diffèrent. La communication entre JavaScript et C++ ou C# nécessite du marshalling, le plus souvent vers une chaîne JSON. La sérialisation/parsing JSON est une opération coûteuse, et les goulots d’étranglement IPC peuvent avoir un impact négatif sur les performances. À partir de Edge 93, WV2 utilisera CBOR pour les événements du réseau.

Electron prend en charge les IPC directement entre deux processus via l'API MessagePorts , qui utilise l'algorithme de clonage structuré. Les applications qui tirent parti de cela peuvent éviter de payer le tribut de la sérialisation JSON lors de l'envoi d'objets entre processus.

Récapitulatif

Electron et WebView2 ont un certain nombre de différences, mais ne vous attendez pas à une grande différence par rapport à la façon dont ils effectuent le rendu du contenu web. En définitive, l'architecture d'une application et ses bibliothèques/ frameworks JavaScript ont un impact plus important sur la mémoire et les performances que tout autre chose car de toute façon Chromium est Chromium peu importe où il fonctionne.

Remerciements spéciaux à l'équipe WebView2 pour avoir examiné ce post, et s'assurer que nous avons une vue actualisée de l'architecture WebView2. Ils seront heureux de recevoir tout commentaire sur le projet.

Du natif au JavaScript dans Electron

· 4 mins de lecture

Comment les fonctionnalités d'Electron écrites en C++ ou Objective-C peuvent-elles être accessibles à un utilisateur final?


Information de base

Electron est une plate-forme JavaScript dont le but principal est de réduire les obstacles pour les développeurs afin qu'ils construisent des applications de bureau robustes sans se soucier des implémentations spécifiques à la plate-forme. Cependant, à la base, Electron lui-même a toujours besoin de fonctionnalités spécifiques à la plate-forme pour être écrit dans un langage système donné.

En réalité, Electron gère le code natif pour vous afin que vous puissiez vous concentrer sur une seule API JavaScript.

Mais comment cela fonctionne-t-il? Comment les fonctionnalités d'Electron écrites en C++ ou Objective-C peuvent-elles être accessibles à un utilisateur final?

Pour tracer les grandes lignes, commençons par le module app.

En ouvrant le fichier app.ts dans notre répertoire lib/ , vous trouverez vers le haut du fichier la ligne de code suivante :

const binding = process.electronBinding('app');

Cette ligne pointe directement vers le mécanisme d'Electron pour relier ses modules C++/Objective-C à JavaScript pour une utilisation par les développeurs. Cette fonction est créée par l'en-tête et le fichier d'implémentation pour la classe ElectronBindings.

process.electronBinding

Ces fichiers ajoutent la fonction process.electronBinding , qui se comporte comme le process.binding de Node.js. process.binding est une implémentation de niveau inférieur de la méthode require() de Node, mais elle autorise les utilisateurs à effectuer unrequire de code natif au lieu d'un code écrit en JS. Cette fonction personnalisée process.electronBinding permet de charger du code natif depuis Electron.

Comment l'état de ce code natif est-il déterminé et défini quand un module JavaScript de haut niveau (comme app) requiert ce code natif, ? Où sont exposées les méthodes à JavaScript ? Qu'en est-il des propriétés ?

native_mate

À l'heure actuelle les réponses à cette question peuvent être trouvées dans native_mate : un fork de la bibliothèque gin de Chromium qui facilite l'échange de types entre C++ et JavaScript.

On trouve dans native_mate/native_mate un en-tête et un fichier d'implémentation pour object_template_builder. C'est ce qui nous permet de former des modules en code natif dont la forme est conforme à ce que les développeurs JavaScript attendent.

mate::ObjectTemplateBuilder

Si nous considérons chaque module Electron comme un object, il devient plus facile de voir pourquoi nous voudrions utiliser object_template_builder pour les construire. Cette classe est construite sur une classe exposée par V8, qui est le moteur JavaScript open source haute performance de Google et et WebAssembly écrits en C ++. V8 implémente la spécification JavaScript (ECMAScript) donc les implémentations de ses fonctionnalités natives peuvent être directement corrélées aux implémentations en JavaScript. Par exemple, v8::ObjectTemplate nous donne des objets JavaScript sans constructeur ni prototype dédiés. Il utilise Object[.prototype] avec l'équivalent en JavaScript étant Object.create().

Pour voir cela en action, consultez le fichier d’implémentation du module app, atom_api_app.cc. En bas de ce fichier vous trouverez:

mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("getGPUInfo", &App::GetGPUInfo)

Dans la ligne ci-dessus, .SetMethod est invoquée sur mate::ObjectTemplateBuilder. .SetMethod peut être invoquée sur toute instance de la classe ObjectTemplateBuilder pour définir des méthodes sur l' Object prototype en JavaScript, avec la syntaxe suivante :

.SetMethod("method_name", &function_to_bind)

Il s’agit de l’équivalent JavaScript de :

function App{}
App.prototype.getGPUInfo = function () {
// rajouter l'implementation ici
}

Cette classe contient également des fonctions pour définir des propriétés d'un module :

.SetProperty("property_name", &getter_function_to_bind)

ou

.SetProperty("property_name", &getter_function_to_bind, &setter_function_to_bind)

Et donc les implémentations JavaScript de Object.defineProperty seraient elles les suivantes:

function App {}
Object.defineProperty(App.prototype, 'myProperty', {
get() {
return _myProperty
}
})

et

function App {}
Object.defineProperty(App.prototype, 'myProperty', {
get() {
return _myProperty
}
set(newPropertyValue) {
_myProperty = newPropertyValue
}
})

Il est possible de créer des objets JavaScript avec prototypes et propriétés comme attendus par les développeurs et plus clairement de raisonner sur les fonctions et les propriétés implémentées à ce niveau inférieur du system!

La décision quant à l'endroit où implémenter une méthode d'un module donné est elle-même complexe et souvent non déterministe, que nous aborderons dans un article futur.

Electron Internals: Building Chromium as a Library

· 7 mins de lecture

Electron is based on Google's open-source Chromium, a project that is not necessarily designed to be used by other projects. This post introduces how Chromium is built as a library for Electron's use, and how the build system has evolved over the years.


Using CEF

The Chromium Embedded Framework (CEF) is a project that turns Chromium into a library, and provides stable APIs based on Chromium's codebase. Very early versions of Atom editor and NW.js used CEF.

To maintain a stable API, CEF hides all the details of Chromium and wraps Chromium's APIs with its own interface. So when we needed to access underlying Chromium APIs, like integrating Node.js into web pages, the advantages of CEF became blockers.

So in the end both Electron and NW.js switched to using Chromium's APIs directly.

Building as part of Chromium

Even though Chromium does not officially support outside projects, the codebase is modular and it is easy to build a minimal browser based on Chromium. The core module providing the browser interface is called Content Module.

To develop a project with Content Module, the easiest way is to build the project as part of Chromium. This can be done by first checking out Chromium's source code, and then adding the project to Chromium's DEPS file.

NW.js et les toutes premières versions d'Electron utilisent cette méthode pour la construction.

The downside is, Chromium is a very large codebase and requires very powerful machines to build. For normal laptops, that can take more than 5 hours. So this greatly impacts the number of developers that can contribute to the project, and it also makes development slower.

Building Chromium as a single shared library

As a user of Content Module, Electron does not need to modify Chromium's code under most cases, so an obvious way to improve the building of Electron is to build Chromium as a shared library, and then link with it in Electron. In this way developers no longer need to build all off Chromium when contributing to Electron.

Le projet libchromiumcontent a été créé par @aroben à cet effet. It builds the Content Module of Chromium as a shared library, and then provides Chromium's headers and prebuilt binaries for download. Le code de la version initiale de libchromiumcontent peut être trouvé dans ce lien.

Le projet brillant est également né dans le cadre de libchromiumcontent, qui fournit une fine couche autour du module de contenu.

By using libchromiumcontent and brightray together, developers can quickly build a browser without getting into the details of building Chromium. And it removes the requirement of a fast network and powerful machine for building the project.

En dehors d'Electron, il y a eu aussi d'autres projets basés sur Chromium construits de cette manière , comme le navigateur Breach.

Filtering exported symbols

On Windows there is a limitation of how many symbols one shared library can export. As the codebase of Chromium grew, the number of symbols exported in libchromiumcontent soon exceeded the limitation.

La solution consistait à filtrer les symboles inutiles lors de la génération du fichier DLL. It worked by providing a .def file to the linker, and then using a script to judge whether symbols under a namespace should be exported.

By taking this approach, though Chromium kept adding new exported symbols, libchromiumcontent could still generate shared library files by stripping more symbols.

Component build

Before talking about the next steps taken in libchromiumcontent, it is important to introduce the concept of component build in Chromium first.

As a huge project, the linking step takes very long in Chromium when building. Normally when a developer makes a small change, it can take 10 minutes to see the final output. To solve this, Chromium introduced component build, which builds each module in Chromium as separated shared libraries, so the time spent in the final linking step becomes unnoticeable.

Shipping raw binaries

With Chromium continuing to grow, there were so many exported symbols in Chromium that even the symbols of Content Module and Webkit were more than the limitation. It was impossible to generate a usable shared library by simply stripping symbols.

Finalement, nous avons dû expédier les binaires bruts de Chromium au lieu de générer une seule bibliothèque partagée.

As introduced earlier there are two build modes in Chromium. As a result of shipping raw binaries, we have to ship two different distributions of binaries in libchromiumcontent. One is called static_library build, which includes all static libraries of each module generated by the normal build of Chromium. The other is shared_library, which includes all shared libraries of each module generated by the component build.

In Electron, the Debug version is linked with the shared_library version of libchromiumcontent, because it is small to download and takes little time when linking the final executable. And the Release version of Electron is linked with the static_library version of libchromiumcontent, so the compiler can generate full symbols which are important for debugging, and the linker can do much better optimization since it knows which object files are needed and which are not.

So for normal development, developers only need to build the Debug version, which does not require a good network or powerful machine. Though the Release version then requires much better hardware to build, it can generate better optimized binaries.

The gn update

Being one of the largest projects in the world, most normal systems are not suitable for building Chromium, and the Chromium team develops their own build tools.

Earlier versions of Chromium were using gyp as a build system, but it suffers from being slow, and its configuration file becomes hard to understand for complex projects. After years of development, Chromium switched to gn as a build system, which is much faster and has a clear architecture.

One of the improvements of gn is to introduce source_set, which represents a group of object files. In gyp, each module was represented by either static_library or shared_library, and for the normal build of Chromium, each module generated a static library and they were linked together in the final executable. By using gn, each module now only generates a bunch of object files, and the final executable just links all the object files together, so the intermediate static library files are no longer generated.

This improvement however made great trouble to libchromiumcontent, because the intermediate static library files were actually needed by libchromiumcontent.

The first try to solve this was to patch gn to generate static library files, which solved the problem, but was far from a decent solution.

La seconde tentative a été faite par @alespergl pour produire des bibliothèques statiques personnalisées à partir de la liste des fichiers d'objet. It used a trick to first run a dummy build to collect a list of generated object files, and then actually build the static libraries by feeding gn with the list. It only made minimal changes to Chromium's source code, and kept Electron's building architecture still.

Récapitulatif

As you can see, compared to building Electron as part of Chromium, building Chromium as a library takes greater efforts and requires continuous maintenance. However the latter removes the requirement of powerful hardware to build Electron, thus enabling a much larger range of developers to build and contribute to Electron. Les efforts en valent la peine.

Electron Internals: Weak References

· 6 mins de lecture

As a language with garbage collection, JavaScript frees users from managing resources manually. But because Electron hosts this environment, it has to be very careful avoiding both memory and resources leaks.

This post introduces the concept of weak references and how they are used to manage resources in Electron.


Weak references

In JavaScript, whenever you assign an object to a variable, you are adding a reference to the object. As long as there is a reference to the object, it will always be kept in memory. Once all references to the object are gone, i.e. there are no longer variables storing the object, the JavaScript engine will recoup the memory on next garbage collection.

A weak reference is a reference to an object that allows you to get the object without effecting whether it will be garbage collected or not. You will also get notified when the object is garbage collected. It then becomes possible to manage resources with JavaScript.

Using the NativeImage class in Electron as an example, every time you call the nativeImage.create() API, a NativeImage instance is returned and it is storing the image data in C++. Once you are done with the instance and the JavaScript engine (V8) has garbage collected the object, code in C++ will be called to free the image data in memory, so there is no need for users manage this manually.

Un autre exemple est le problème de disparition de fenêtre, qui montre visuellement comment la fenêtre est des ordures collectées quand toutes les références à lui sont terminées.

Testing weak references in Electron

There is no way to directly test weak references in raw JavaScript since the language doesn't have a way to assign weak references. The only API in JavaScript related to weak references is WeakMap, but since it only creates weak-reference keys, it is impossible to know when an object has been garbage collected.

In versions of Electron prior to v0.37.8, you can use the internal v8Util.setDestructor API to test weak references, which adds a weak reference to the passed object and calls the callback when the object is garbage collected:

// Le code ci-dessous ne peut s'exécuter que sur Electron < v0.37.8.
var v8Util = process.atomBinding('v8_util');

var object = {};
v8Util.setDestructor(object, function () {
console.log('The object is garbage collected');
});

// Remove all references to the object.
object = undefined;
// Manually starts a GC.
gc();
// Console prints "The object is garbage collected".

Note that you have to start Electron with the --js-flags="--expose_gc" command switch to expose the internal gc function.

The API was removed in later versions because V8 actually does not allow running JavaScript code in the destructor and in later versions doing so would cause random crashes.

Weak references in the remote module

Apart from managing native resources with C++, Electron also needs weak references to manage JavaScript resources. Un exemple est le module distantd'Electron, , qui est un module d'appel à distance (RPC) qui permet d'utiliser des objets dans le processus principal à partir des processus de rendu.

One key challenge with the remote module is to avoid memory leaks. When users acquire a remote object in the renderer process, the remote module must guarantee the object continues to live in the main process until the references in the renderer process are gone. Additionally, it also has to make sure the object can be garbage collected when there are no longer any reference to it in renderer processes.

For example, without proper implementation, following code would cause memory leaks quickly:

const { remote } = require('electron');

for (let i = 0; i < 10000; ++i) {
remote.nativeImage.createEmpty();
}

The resource management in the remote module is simple. Whenever an object is requested, a message is sent to the main process and Electron will store the object in a map and assign an ID for it, then send the ID back to the renderer process. In the renderer process, the remote module will receive the ID and wrap it with a proxy object and when the proxy object is garbage collected, a message will be sent to the main process to free the object.

Using remote.require API as an example, a simplified implementation looks like this:

remote.require = function (name) {
// Tell the main process to return the metadata of the module.
const meta = ipcRenderer.sendSync('REQUIRE', name);
// Create a proxy object.
const object = metaToValue(meta);
// Tell the main process to free the object when the proxy object is garbage
// collected.
v8Util.setDestructor(object, function () {
ipcRenderer.send('FREE', meta.id);
});
return object;
};

Dans le processus principal :

const map = {};
const id = 0;

ipcMain.on('REQUIRE', function (event, name) {
const object = require(name);
// Add a reference to the object.
map[++id] = object;
// Convert the object to metadata.
event.returnValue = valueToMeta(id, object);
});

ipcMain.on('FREE', function (event, id) {
delete map[id];
});

Maps with weak values

With the previous simple implementation, every call in the remote module will return a new remote object from the main process, and each remote object represents a reference to the object in the main process.

The design itself is fine, but the problem is when there are multiple calls to receive the same object, multiple proxy objects will be created and for complicated objects this can add huge pressure on memory usage and garbage collection.

For example, the following code:

const { remote } = require('electron');

for (let i = 0; i < 10000; ++i) {
remote.getCurrentWindow();
}

It first uses a lot of memory creating proxy objects and then occupies the CPU (Central Processing Unit) for garbage collecting them and sending IPC messages.

An obvious optimization is to cache the remote objects: when there is already a remote object with the same ID, the previous remote object will be returned instead of creating a new one.

This is not possible with the API in JavaScript core. Using the normal map to cache objects will prevent V8 from garbage collecting the objects, while the WeakMap class can only use objects as weak keys.

To solve this, a map type with values as weak references is added, which is perfect for caching objects with IDs. Now the remote.require looks like this:

const remoteObjectCache = v8Util.createIDWeakMap()

remote.require = function (name) {
// Tell the main process to return the meta data of the module.
...
if (remoteObjectCache.has(meta.id))
return remoteObjectCache.get(meta.id)
// Create a proxy object.
...
remoteObjectCache.set(meta.id, object)
return object
}

Notez que le remoteObjectCache conserve des objets comme des références faibles, il n’est donc pas nécessaire de supprimer la clé lorsque l’objet est collecté par le garbage collector.

Native code

For people interested in the C++ code of weak references in Electron, it can be found in following files:

The setDestructor API:

The createIDWeakMap API:

Au coeur d'Electron : Utilisation de Node en tant que Bibliothèque

· 4 mins de lecture

Ceci est le deuxième article d’une série en cours expliquant le fonctionnement interne d'Electron. Consultez le premier post sur l'intégration de la boucle d'événement si vous ne l'avez pas déjà fait.

La plupart des gens utilisent Node pour les applications côté serveur, mais en raison de l’ensemble d’API riche de Node et de sa communauté florissante, il convient également parfaitement à une bibliothèque intégrée. Cet article explique comment Node est utilisé comme bibliothèque dans Electron.


Système de génération

Node et Electron utilisent GYP comme systèmes de génération. Si vous souhaitez intégrer Node dans votre application, vous devez également l’utiliser comme système de génération.

Vous ne connaissez pas GYP? Lisez ce guide avant de continuer dans cet article.

Node's flags

Le nœud yp dans le répertoire de code source de Node décrit comment Node est construit, avec beaucoup de GYP variables qui contrôlent quelles parties de Node sont activées et si pour ouvrir certaines configurations.

To change the build flags, you need to set the variables in the .gypi file of your project. The configure script in Node can generate some common configurations for you, for example running ./configure --shared will generate a config.gypi with variables instructing Node to be built as a shared library.

Electron does not use the configure script since it has its own build scripts. Les configurations pour Node sont définies dans le fichier common.gypi dans le répertoire du code source d'Electron.

In Electron, Node is being linked as a shared library by setting the GYP variable node_shared to true, so Node's build type will be changed from executable to shared_library, and the source code containing the Node's main entry point will not be compiled.

Since Electron uses the V8 library shipped with Chromium, the V8 library included in Node's source code is not used. This is done by setting both node_use_v8_platform and node_use_bundled_v8 to false.

Shared library or static library

When linking with Node, there are two options: you can either build Node as a static library and include it in the final executable, or you can build it as a shared library and ship it alongside the final executable.

In Electron, Node was built as a static library for a long time. This made the build simple, enabled the best compiler optimizations, and allowed Electron to be distributed without an extra node.dll file.

Cependant, cela a changé après que Chrome ait basculé pour utiliser BoringSSL. BoringSSL est un fork de OpenSSL qui supprime plusieurs API inutilisées et modifie de nombreuses interfaces existantes. Because Node still uses OpenSSL, the compiler would generate numerous linking errors due to conflicting symbols if they were linked together.

Electron n'a pas pu utiliser BoringSSL dans le nœud, ou utiliser OpenSSL dans Chromium, la seule option était donc de basculer vers la construction d'un noeud en tant que bibliothèque partagée, et cachent les symboles BoringSSL et OpenSSL dans les composantes de chacune.

This change brought Electron some positive side effects. Before this change, you could not rename the executable file of Electron on Windows if you used native modules because the name of the executable was hard coded in the import library. After Node was built as a shared library, this limitation was gone because all native modules were linked to node.dll, whose name didn't need to be changed.

Supporting native modules

Modules natifs dans le travail de Noeude en définissant une fonction d'entrée pour le chargement de Noeud, puis la recherche des symboles de V8 et libuv à partir de Node. This is a bit troublesome for embedders because by default the symbols of V8 and libuv are hidden when building Node as a library and native modules will fail to load because they cannot find the symbols.

So in order to make native modules work, the V8 and libuv symbols were exposed in Electron. For V8 this is done by forcing all symbols in Chromium's configuration file to be exposed. Pour libuv, il est réalisé en définissant la définition BUILDING_UV_SHARED=1 définition.

Starting Node in your app

After all the work of building and linking with Node, the final step is to run Node in your app.

Node doesn't provide many public APIs for embedding itself into other apps. Habituellement, vous pouvez juste appeler node::Start et node::Init pour démarrer une nouvelle instance de Node. However, if you are building a complex app based on Node, you have to use APIs like node::CreateEnvironment to precisely control every step.

In Electron, Node is started in two modes: the standalone mode that runs in the main process, which is similar to official Node binaries, and the embedded mode which inserts Node APIs into web pages. The details of this will be explained in a future post.

Au coeur d'Electron: Intégration de la boucle de Messages

· 4 mins de lecture

C'est le premier article d'une série qui explique le fonctionnement interne d'Electron. Cet article présente comment la boucle d’événements de Node est intégrée dans Electron avec Chromium.


Il y a eu de nombreuses tentatives d'utiliser Node pour la programmation GUI, comme node-gui pour les liaisons avec GTK+ et node-qt pour les liaisons avec QT. Mais aucune d'entre elle ne fonctionne en production parce que les kits d'outils d'interface graphique ont leurs propres boucles de message tandis que Node utilise libuv pour sa propre boucle d'événement, et le thread principal ne peut exécuter qu'une seule boucle en même temps. Ainsi, l’astuce courante pour exécuter la boucle de message GUI dans Node consiste à activer la boucle de message par l'intermédiaire d'un timer avec un très petit intervalle, ce qui ralentit la réponse de l’interface graphique et occupe beaucoup de ressources CPU.

Lors du développement d'Electron, nous avons rencontré le même problème, bien que de manière inversée : nous devions intégrer la boucle d'événements de Node dans la boucle message de Chromium.

Le processus principal et le processus de rendu

Avant de plonger dans les détails de l'intégration de la boucle de messages, je vais d'abord expliquer l'architecture multi-processus de Chromium.

Dans Electron, il y a deux types de processus : le processus principal et le processus de rendu (ceci est en fait extrêmement simplifié, pour une vue complète, voir Architecture multi-processus). Le processus principal est responsable de l'interface utilisateur comme la création de fenêtres, tandis que le processus de rendu ne s'occupe que de l'exécution et du rendu des pages Web.

Electron permet d'utiliser JavaScript pour contrôler à la fois le processus principal et le processus de rendu, ce qui signifie que nous devons intégrer Node dans les deux processus.

Remplacement de la boucle de message de Chromium par libuv

Ma première tentative fut de ré implémenter la boucle de message de Chromium avec libuv.

C'était facile pour le processus de rendu car sa boucle de message n'écoutait que les descripteurs de fichiers et les timers et je n'avais besoin que d'implémenter l'interface avec libuv.

Cependant, c’était beaucoup plus difficile pour le processus principal. Chaque plate-forme a son propre type de boucles de messages GUI. macOS Chromium utilise NSRunLoop, alors que Linux utilise glib. J'ai essayé de nombreux hacks pour extraire les descripteurs de fichiers sous-jacents des boucles de messages de l'interface native, et puis les fournir à libuv pour itération, mais j'ai toujours rencontré des cas particuliers qui ne fonctionnaient pas.

J'ai donc finalement ajouté un timer pour interroger la boucle du message de l'interface dans un petit intervalle. Comme un résultat le processus a une utilisation constante du processeur, et certaines opérations ont de longs retards.

Scrutation de la boucle d’événements de Node dans un thread distinct

Comme libuv a mûri, il a été possible d’adopter une autre approche.

Le concept de fd backend a été introduit dans libuv, qui est un descripteur de fichier (ou un manipulateur) que libuv interroge pour sa boucle d'événements. Donc, en interrogeant le backend fd, il est possible d’être averti lorsqu’il y a un nouvel événement dans libuv.

Donc, dans Electron, j'ai créé un thread séparé pour interroger le backend fd, et puisque j'utilise les appels du système pour scruter au lieu des API libuv, c'est thread safe sûr. Et chaque fois qu'il y a un nouvel événement dans la boucle d'événement de libuv, un message est posté dans la boucle de message de Chromium, et les événements de libuv sont alors traités dans le thread principal.

De cette façon, j'ai évité de patcher Chromium et Node, et le même code a été utilisé dans à la fois les processus principal et de rendu.

Le code

Vous pouvez trouver l'implémentation de l'intégration de la boucle message dans les fichiers node_bindings sous electron/atom/common/. Il peut être facilement réutilisé pour les projets souhaitant intégrer Node.

Update: l'Implementation a été déplacée dans electron/shell/common/node_bindings.cc.