 3d2a754531
			
		
	
	
	
	
	3d2a754531* chore: extend linting of code blocks in the docs * chore: combine lint:markdownlint and lint:markdown scripts
		
			
				
	
	
		
			171 lines
		
	
	
	
		
			6.2 KiB
			
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
	
		
			6.2 KiB
			
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 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`](https://github.com/electron/electron/tree/main/shell/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](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 title='filenames.gni'
 | |
| 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 [`@electron/docs-parser`](https://github.com/electron/docs-parser) and [`@electron/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).
 | |
| 
 | |
| ## Set 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 title='api_name.h'
 | |
| 
 | |
| #ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_{API_NAME}_H_
 | |
| #define ELECTRON_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<ApiName>  {
 | |
|  public:
 | |
|   static gin::Handle<ApiName> 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 title='api_name.cc'
 | |
| #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> ApiName::Create(v8::Isolate* isolate) {
 | |
|   return gin::CreateHandle(isolate, new ApiName());
 | |
| }
 | |
| 
 | |
| } // namespace api
 | |
| 
 | |
| } // namespace electron
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| void Initialize(v8::Local<v8::Object> exports,
 | |
|                 v8::Local<v8::Value> unused,
 | |
|                 v8::Local<v8::Context> 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
 | |
| 
 | |
| In the [`typings/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 like so:
 | |
| 
 | |
| ```ts title='typings/internal-ambient.d.ts' @ts-nocheck
 | |
| interface Process {
 | |
|     _linkedBinding(name: 'electron_browser_{api_name}'): Electron.ApiName;
 | |
| }
 | |
| ```
 | |
| 
 | |
| At the very bottom of your `api_name.cc` file:
 | |
| 
 | |
| ```cpp title='api_name.cc'
 | |
| NODE_LINKED_BINDING_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 title='shell/common/node_bindings.cc'
 | |
| #define ELECTRON_BROWSER_MODULES(V)      \
 | |
|   V(electron_browser_{api_name})
 | |
| ```
 | |
| 
 | |
| > Note: More technical details on how Node links with Electron can be found on [our blog](https://www.electronjs.org/blog/electron-internals-using-node-as-a-library#link-node-with-electron).
 | |
| 
 | |
| ## 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-theme.ts).
 | |
| 
 | |
| ### Expose your module to TypeScript
 | |
| 
 | |
| Add your module to the module list found at `"lib/browser/api/module-list.ts"` like so:
 | |
| 
 | |
| ```ts title='lib/browser/api/module-list.ts' @ts-nocheck
 | |
| export const browserModuleList: ElectronInternal.ModuleEntry[] = [
 | |
|   { name: 'apiName', loader: () => require('./api-name') },
 | |
| ];
 | |
| ```
 |