diff --git a/doc/api/addons.md b/doc/api/addons.md index 94a855a14f2e51..b4d25128235896 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -4,22 +4,17 @@ -_Addons_ are dynamically-linked shared objects written in C++. The -[`require()`][require] function can load addons as ordinary Node.js modules. -Addons provide an interface between JavaScript and C/C++ libraries. +_Addons_ are dynamically-linked shared objects that can be loaded via the +[`require()`][] function as ordinary Node.js modules. +Addons provide a foreign function interface between JavaScript and native code. There are three options for implementing addons: -* Node-API +* [Node-API][] (recommended) * `nan` ([Native Abstractions for Node.js][]) * direct use of internal V8, libuv, and Node.js libraries -Unless there is a need for direct access to functionality which is not\ -exposed by Node-API, use Node-API. -Refer to [C/C++ addons with Node-API](n-api.md) for more information on -Node-API. - -When not using Node-API, implementing addons becomes more complex, requiring\ +This rest of this document focuses on the latter, requiring knowledge of multiple components and APIs: * [V8][]: the C++ library Node.js uses to provide the @@ -28,7 +23,7 @@ knowledge of multiple components and APIs: `v8.h` header file (`deps/v8/include/v8.h` in the Node.js source tree), and is also available [online][v8-docs]. -* [libuv][]: The C library that implements the Node.js event loop, its worker +* [`libuv`][]: The C library that implements the Node.js event loop, its worker threads and all of the asynchronous behaviors of the platform. It also serves as a cross-platform abstraction library, giving easy, POSIX-like access across all major operating systems to many common system tasks, such @@ -87,37 +82,40 @@ void Initialize(Local exports) { NODE_SET_METHOD(exports, "hello", Method); } -NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) +NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) // N.B.: no semi-colon, this is not a function } // namespace demo ``` -All Node.js addons must export an initialization function following -the pattern: +On most platforms, the following `Makefile` can get us started: -```cpp -void Initialize(Local exports); -NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) + + +```bash +NODEJS_DEV_ROOT ?= $(shell dirname "$$(command -v node)")/.. +CXXFLAGS = -std=c++23 -I$(NODEJS_DEV_ROOT)/include/node -fPIC -shared -Wl,-undefined,dynamic_lookup + +hello.node: hello.cc + $(CXX) $(CXXFLAGS) -o $@ $< ``` -There is no semi-colon after `NODE_MODULE` as it's not a function (see -`node.h`). + -The `module_name` must match the filename of the final binary (excluding -the `.node` suffix). +Then running the following commands will compile and run the code: -In the `hello.cc` example, then, the initialization function is `Initialize` -and the addon module name is `addon`. +```console +$ make +$ node -p 'require("./hello.node").hello()' +world +``` + +To integrate with the npm ecosystem, see the [Building][] section. -When building addons with `node-gyp`, using the macro `NODE_GYP_MODULE_NAME` as -the first parameter of `NODE_MODULE()` will ensure that the name of the final -binary will be passed to `NODE_MODULE()`. +### Context-aware addons Addons defined with `NODE_MODULE()` can not be loaded in multiple contexts or multiple threads at the same time. -### Context-aware addons - There are environments in which Node.js addons may need to be loaded multiple times in multiple contexts. For example, the [Electron][] runtime runs multiple instances of Node.js in a single process. Each instance will have its own @@ -257,8 +255,8 @@ changes: In order to be loaded from multiple Node.js environments, such as a main thread and a Worker thread, an add-on needs to either: -* Be an Node-API addon, or -* Be declared as context-aware using `NODE_MODULE_INIT()` as described above +* Be an [Node-API][] addon. +* Be declared as context-aware using `NODE_MODULE_INIT()` as described above. In order to support [`Worker`][] threads, addons need to clean up any resources they may have allocated when such a thread exits. This can be achieved through @@ -340,7 +338,7 @@ require('./build/Release/addon'); Once the source code has been written, it must be compiled into the binary `addon.node` file. To do so, create a file called `binding.gyp` in the top-level of the project describing the build configuration of the module -using a JSON-like format. This file is used by [node-gyp][], a tool written +using a JSON-like format. This file is used by [`node-gyp`][], a tool written specifically to compile Node.js addons. ```json @@ -375,7 +373,7 @@ version of `node-gyp` to perform this same set of actions, generating a compiled version of the addon for the user's platform on demand. Once built, the binary addon can be used from within Node.js by pointing -[`require()`][require] to the built `addon.node` module: +[`require()`][] to the built `addon.node` module: ```js // hello.js @@ -422,18 +420,46 @@ aware of: ### Loading addons using `require()` The filename extension of the compiled addon binary is `.node` (as opposed -to `.dll` or `.so`). The [`require()`][require] function is written to look for +to `.dll` or `.so`). The [`require()`][] function is written to look for files with the `.node` file extension and initialize those as dynamically-linked libraries. -When calling [`require()`][require], the `.node` extension can usually be +When calling [`require()`][], the `.node` extension can usually be omitted and Node.js will still find and initialize the addon. One caveat, however, is that Node.js will first attempt to locate and load modules or JavaScript files that happen to share the same base name. For instance, if there is a file `addon.js` in the same directory as the binary `addon.node`, -then [`require('addon')`][require] will give precedence to the `addon.js` file +then [`require('addon')`][`require()`] will give precedence to the `addon.js` file and load it instead. +### Loading addons using `import` + + + +> Stability: 1.0 - Early development + +You can use the [`--experimental-addon-modules`][] flag to enable support for +both static `import` and dynamic `import()` to load binary addons. + +If we reuse the Hello World example from earlier, you could do: + +```mjs +// hello.mjs +import myAddon from './hello.node'; +// N.B.: import {hello} from './hello.node' would not work + +console.log(myAddon.hello()); +``` + +```console +$ node --experimental-addon-modules hello.mjs +world +``` + ## Native abstractions for Node.js Each of the examples illustrated in this document directly use the @@ -453,59 +479,7 @@ illustration of how it can be used. > Stability: 2 - Stable -Node-API is an API for building native addons. It is independent from -the underlying JavaScript runtime (e.g. V8) and is maintained as part of -Node.js itself. This API will be Application Binary Interface (ABI) stable -across versions of Node.js. It is intended to insulate addons from -changes in the underlying JavaScript engine and allow modules -compiled for one version to run on later versions of Node.js without -recompilation. Addons are built/packaged with the same approach/tools -outlined in this document (node-gyp, etc.). The only difference is the -set of APIs that are used by the native code. Instead of using the V8 -or [Native Abstractions for Node.js][] APIs, the functions available -in the Node-API are used. - -Creating and maintaining an addon that benefits from the ABI stability -provided by Node-API carries with it certain -[implementation considerations][]. - -To use Node-API in the above "Hello world" example, replace the content of -`hello.cc` with the following. All other instructions remain the same. - -```cpp -// hello.cc using Node-API -#include - -namespace demo { - -napi_value Method(napi_env env, napi_callback_info args) { - napi_value greeting; - napi_status status; - - status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting); - if (status != napi_ok) return nullptr; - return greeting; -} - -napi_value init(napi_env env, napi_value exports) { - napi_status status; - napi_value fn; - - status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn); - if (status != napi_ok) return nullptr; - - status = napi_set_named_property(env, exports, "hello", fn); - if (status != napi_ok) return nullptr; - return exports; -} - -NAPI_MODULE(NODE_GYP_MODULE_NAME, init) - -} // namespace demo -``` - -The functions available and how to use them are documented in -[C/C++ addons with Node-API](n-api.md). +See [C/C++ addons with Node-API][Node-API]. ## Addon examples @@ -1378,18 +1352,20 @@ console.log(result); // Prints: 30 ``` +[Building]: #building [Electron]: https://electronjs.org/ [Embedder's Guide]: https://v8.dev/docs/embed [Linking to libraries included with Node.js]: #linking-to-libraries-included-with-nodejs [Native Abstractions for Node.js]: https://github.com/nodejs/nan +[Node-API]: n-api.md [V8]: https://v8.dev/ +[`--experimental-addon-modules`]: cli.md#--experimental-addon-modules [`Worker`]: worker_threads.md#class-worker +[`libuv`]: https://github.com/libuv/libuv +[`node-gyp`]: https://github.com/nodejs/node-gyp +[`require()`]: modules.md#requireid [bindings]: https://github.com/TooTallNate/node-bindings [download]: https://github.com/nodejs/node-addon-examples [examples]: https://github.com/nodejs/nan/tree/HEAD/examples/ -[implementation considerations]: n-api.md#implications-of-abi-stability [installation instructions]: https://github.com/nodejs/node-gyp#installation -[libuv]: https://github.com/libuv/libuv -[node-gyp]: https://github.com/nodejs/node-gyp -[require]: modules.md#requireid [v8-docs]: https://v8docs.nodesource.com/