Node Native Modules
Note: The following information is currently only relevant to the Electron builds of RStudio, which is currently a work in progress.
The desktop edition of RStudio uses Electron, which allows for native code extensions via native modules. This document briefly outlines the approach used to implement new native module methods.
These are (roughly speaking) the following steps to take when adding or extending a native node module to be used with RStudio:
- Add your methods in
src/native/desktop.cc
or another similar appropriate file, - If you added more C++ sources, make sure to update the "sources" field in
binding.gyp
, - Update the appropriate
.d.ts
files insrc/native
, - Run
npm install
to rebuild the module, - Use it in Typescript like any other module.
The rest of the document describes these steps in more detail.
Our native modules are implemented using C++. The sources for our native module(s) live within the src/native
folder. The general structure is something like the following:
// actual method implementation
bool methodImpl(int a) { ... }
// a wrapper method for node-addon-api, which unpacks arguments
// and calls the underlying impl method
Napi::Value method(const Napi::CallbackInfo& info) {
// retrieve evaluation environment
auto env = info.Env();
// unpack arguments
auto a = env[0].As<Napi::Integer>().Value();
// call implementation method
auto result = methodImpl(a);
// return wrapped value
return Napi::Value::From(env, result);
}
// the entrypoint for the node module
// its primary responsibility is exporting the relevant methods,
// so they're visible from the imported node module
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.set(
Napi::String::New(env, "method"),
Napi::Function::New(env, method)
);
return exports;
}
If multiple C++ source files are used for compilation of a node module, then only one source file should define the Init()
method.
Node modules are automatically compiled when npm install
is invoked. Compilation is performed by node-gyp, and the targets for their compilation is defined in binding.gyp. Think of this file as a Makefile for node add-ons -- it can be used to define targets, which is used to control how node native modules are built and bundled with the application.
The document's contents (at the time this article was written) are:
{
"targets": [
{
"target_name": "desktop",
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"sources": [ "src/native/desktop.cc" ],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
},
{
"target_name": "copy",
"type": "none",
"dependencies": [ "desktop" ],
"copies": [
{
"files": ["<(module_root_dir)/build/Release/desktop.node"],
"destination": "<(module_root_dir)/src/native"
}
]
}
]
}
There are two targets defined:
-
The first is used to actually build the node module. In particular, note that it builds from the source file "src/native/desktop.cc".
-
The second is used to copy the generated node module from the build tree back into the source tree, mainly to make it easier to import and use in our
.ts
source scripts.
When npm install
is invoked, node-gyp
is also executed and used to compile the above targets.
See the official documentation at https://gyp.gsrc.io/docs/UserDocumentation.md for more information.
From the Typescript side, modules can be imported like "regular" Typescript modules. For example:
import desktop from '../native/desktop.node'
An accompanying desktop.node.d.ts
file is used to define the interface actually available for interacting with the module. This file needs to be manually updated and kept in sync with the underlying native module as it changes. For example, the above method would be "documented" as:
export declare function method(a: int): bool;
Finally, see https://github.com/nodejs/node-addon-examples if you need inspiration or more examples on how node-addon-api
can be used. Note that each folder provides examples for multiple different node interfaces; we're mainly interested in the node-addon-api
versions.
Developing
- Beginners guide
- RStudio Development
- Git conventions
- Accessibility
- Development with Vagrant
- Electron desktop
- GWT
- Internationalization (i18n)
- Node Native Modules
Issues
Personal development environment
- Installing RStudio Dependencies
- M1 Mac Dev Machine Setup
- Visual Markdown Editing
- IDE Development Using Visual Studio Code
Building
Coding standards
Tests
Other topics