diff --git a/docs/tutorial/creating-api.md b/docs/tutorial/creating-api.md new file mode 100644 index 000000000000..4edc198f9c10 --- /dev/null +++ b/docs/tutorial/creating-api.md @@ -0,0 +1,175 @@ +# Creating an Electron Browser Module API + +Welcome to the Electron API guide! If you are unfamiliar with creating electron APIs within the `browser` module, 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. + +## Adding Your Files To Electron's Project Configuration + +Electron uses [GN](https://gn.googlesource.com/gn) as a meta build system to generate files for its compiler, [Ninja](https://ninja-build.org/). 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`](https://github.com/electron/electron/blob/main/filenames.gni). + +You will need to append your api file names alphabetically into the appropriate files like so: + +```cpp +lib_sources = [ + "path/to/api/api_name.cc", + "path/to/api/api_name.h", +] + +lib_sources_mac = [ + "path/to/api/api_name_mac.h", + "path/to/api/api_name_mac.mm", +] + +lib_sources_win = [ + "path/to/api/api_name_win.cc", + "path/to/api/api_name_win.h", +] + +lib_sources_linux = [ + "path/to/api/api_name_linux.cc", + "path/to/api/api_name_linux.h", +] +``` + +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 [`docs-parser`](https://github.com/electron/docs-parser) and [`typescript-definitions`](https://github.com/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](https://github.com/electron/electron/tree/main/docs/api). + +## Setting Up `ObjectTemplateBuilder` and `Wrappable` + +Electron constructs its modules using [`object_template_builder`](https://www.electronjs.org/blog/from-native-to-js#mateobjecttemplatebuilder). + +[`wrappable`](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/gin/wrappable.h) 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](https://github.com/electron/electron/tree/main/shell/browser/api). + +In your `api_name.h` file: + +```cpp + +#ifndef SHELL_BROWSER_API_ELECTRON_API_{API_NAME}_H_ +#define SHELL_BROWSER_API_ELECTRON_API_{API_NAME}_H_ + +#include "gin/handle.h" +#include "gin/wrappable.h" + +namespace electron { + +namespace api { + +class ApiName : public gin::Wrappable { + public: + static gin::Handle Create(v8::Isolate* isolate); + + // gin::Wrappable + static gin::WrapperInfo kWrapperInfo; + gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + const char* GetTypeName() override; +} // namespace api +} // namespace electron +``` + +In your `api_name.cc` file: + +```cpp +#include "shell/browser/api/electron_api_safe_storage.h" + +#include "shell/browser/browser.h" +#include "shell/common/gin_converters/base_converter.h" +#include "shell/common/gin_converters/callback_converter.h" +#include "shell/common/gin_helper/dictionary.h" +#include "shell/common/gin_helper/object_template_builder.h" +#include "shell/common/node_includes.h" +#include "shell/common/platform_util.h" + +namespace electron { + +namespace api { + +gin::WrapperInfo ApiName::kWrapperInfo = {gin::kEmbedderNativeGin}; + +gin::ObjectTemplateBuilder ApiName::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return gin::ObjectTemplateBuilder(isolate) + .SetMethod("methodName", &ApiName::methodName); +} + +const char* ApiName::GetTypeName() { + return "ApiName"; +} + +// static +gin::Handle ApiName::Create(v8::Isolate* isolate) { + return gin::CreateHandle(isolate, new ApiName()); +} + +} // namespace api + +} // namespace electron + +namespace { + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + gin_helper::Dictionary dict(isolate, exports); + dict.Set("apiName", electron::api::ApiName::Create(isolate)); +} + +} // namespace +``` + +## Link Your Electron API With Node + +To learn about how Electron links with Node, [click here.](https://www.electronjs.org/blog/electron-internals-using-node-as-a-library#link-node-with-electron) + +In the [`internal-ambient.d.ts`](https://github.com/electron/electron/blob/main/typings/internal-ambient.d.ts) file: + +We need to append a new property onto the `Process` interface found in this file like so: + +```ts +interface Process { + _linkedBinding(name: 'electron_browser_{api_name}', Electron.ApiName); +} +``` + +At the very bottom of your `api_name.cc` file: + +```cpp +NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_{api_name},Initialize) +``` + +In your [`shell/common/node_bindings.cc`](https://github.com/electron/electron/blob/main/shell/common/node_bindings.cc) file: + +Add your node binding name to Electron's built-in modules. + +```cpp +#define ELECTRON_BUILTIN_MODULES(V) \ + V(electron_browser_{api_name}) +``` + +## 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/{electron_browser_{api_name}}.ts"` + +An example of the contents of this file can be found [here](https://github.com/electron/electron/blob/main/lib/browser/api/native-image.ts). + +### Expose Your module to TypeScript + +Add your module to the module list found at `"lib/browser/api/module-list.ts"` like so: + +```typescript +export const browserModuleList: ElectronInternal.ModuleEntry[] = [ + { name: 'apiName', loader: () => require('./api-name') }, +]; +```