Creating a New Electron Browser Module

Welcome to the Electron API guide! If you are unfamiliar with creating a new Electron API module within the browser directory, this guide serves as a checklist for some of the necessary steps that you will need to implement.

This is not a comprehensive end-all guide to creating an Electron Browser API, rather an outline documenting some of the more unintuitive steps.

Add your files to Electron’s project configuration

Electron uses GN as a meta build system to generate files for its compiler, Ninja. This means that in order to tell Electron to compile your code, we have to add your API’s code and header file names into filenames.gni.

You will need to append your API file names alphabetically into the appropriate files like so:

  1. lib_sources = [
  2. "shell/browser/api/electron_api_demo.cc",
  3. "shell/browser/api/electron_api_demo.h",
  4. ]
  5. lib_sources_mac = [
  6. "shell/browser/api/electron_api_demo_mac.h",
  7. "shell/browser/api/electron_api_demo_mac.mm",
  8. ]
  9. lib_sources_win = [
  10. "shell/browser/api/electron_api_demo_win.cc",
  11. "shell/browser/api/electron_api_demo_win.h",
  12. ]
  13. lib_sources_linux = [
  14. "shell/browser/api/electron_api_demo_linux.cc",
  15. "shell/browser/api/electron_api_demo_linux.h",
  16. ]

Note that the Windows, macOS and Linux array additions are optional and should only be added if your API has specific platform implementations.

Create API documentation

Type definitions are generated by Electron using @electron/docs-parser and @electron/typescript-definitions. This step is necessary to ensure consistency across Electron’s API documentation. This means that for your API type definition to appear in the electron.d.ts file, we must create a .md file. Examples can be found in this folder.

Set up ObjectTemplateBuilder and Wrappable

Electron constructs its modules using object_template_builder.

wrappable is a base class for C++ objects that have corresponding v8 wrapper objects.

Here is a basic example of code that you may need to add, in order to incorporate object_template_builder and wrappable into your API. For further reference, you can find more implementations here.

In your shell/browser/api/electron_api_demo.h file:

  1. //
  2. // electron_api_demo.h
  3. //
  4. // Created by Danny Zhu on 10/7/22.
  5. //
  6. #ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_DEMO_H_
  7. #define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_DEMO_H_
  8. #include <string>
  9. #include "gin/handle.h"
  10. #include "gin/wrappable.h"
  11. namespace electron {
  12. namespace api {
  13. class Demo : public gin::Wrappable<Demo> {
  14. public:
  15. static gin::Handle<Demo> Create(v8::Isolate* isolate);
  16. // gin::Wrappable
  17. static gin::WrapperInfo kWrapperInfo;
  18. gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
  19. v8::Isolate* isolate) override;
  20. const char* GetTypeName() override;
  21. //
  22. void PrintMessage(const std::string &message);
  23. };
  24. } // namespace api
  25. } // namespace electron
  26. #endif

In your shell/browser/api/electron_api_demo.cc file:

  1. //
  2. // electron_api_demo.cpp
  3. // sources
  4. //
  5. // Created by Danny Zhu on 10/7/22.
  6. //
  7. #include "shell/browser/api/electron_api_demo.h"
  8. #include <iostream>
  9. #include "gin/dictionary.h"
  10. #include "gin/object_template_builder.h"
  11. #include "shell/common/node_bindings.h"
  12. #include "shell/common/node_includes.h"
  13. namespace electron {
  14. namespace api {
  15. gin::WrapperInfo Demo::kWrapperInfo = {gin::kEmbedderNativeGin};
  16. gin::Handle<Demo> Demo::Create(v8::Isolate* isolate) {
  17. return gin::CreateHandle(isolate, new Demo());
  18. }
  19. void Demo::PrintMessage(const std::string& message) {
  20. std::cout << "PrintMessage " << message << std::endl;
  21. }
  22. // gin::Wrappable:
  23. gin::ObjectTemplateBuilder Demo::GetObjectTemplateBuilder(
  24. v8::Isolate* isolate) {
  25. return gin::Wrappable<Demo>::GetObjectTemplateBuilder(isolate).SetMethod(
  26. "printMessage", &Demo::PrintMessage);
  27. }
  28. const char* Demo::GetTypeName() {
  29. return "Demo";
  30. }
  31. } // namespace api
  32. } // namespace electron
  33. namespace {
  34. void Initialize(v8::Local<v8::Object> exports,
  35. v8::Local<v8::Value> unused,
  36. v8::Local<v8::Context> context,
  37. void* priv) {
  38. gin::Dictionary dict(context->GetIsolate(), exports);
  39. dict.Set("demo", electron::api::Demo::Create(context->GetIsolate()));
  40. }
  41. } // namespace
  42. NODE_LINKED_MODULE_CONTEXT_AWARE(electron_api_demo, Initialize)

In your shell/common/node_bindings.cc file, add your node binding name to Electron’s built-in modules.

  1. #define ELECTRON_BUILTIN_MODULES(V) \
  2. V(electron_api_demo)

Note: More technical details on how Node links with Electron can be found on our blog.

Expose your API to TypeScript

Export your API as a module

We will need to create a new TypeScript file in the path that follows:

"lib/browser/api/demo.ts"

  1. const { demo } = process._linkedBinding('electron_api_demo');
  2. export default demo;

Expose your module to TypeScript

Add your module to the module list found at "lib/browser/api/module-list.ts" like so:

  1. export const browserModuleList: ElectronInternal.ModuleEntry[] = [
  2. { name: 'demo', loader: () => require('./demo') },
  3. ];