Aller au contenu principal

Du natif au JavaScript dans Electron

· 4 mins à lire

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 le marshaling des 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.