C++ embedder API#

Node.js provides a number of C++ APIs that can be used to execute JavaScriptin a Node.js environment from other C++ software.

The documentation for these APIs can be found insrc/node.h in the Node.jssource tree. In addition to the APIs exposed by Node.js, some required conceptsare provided by the V8 embedder API.

Because using Node.js as an embedded library is different from writing codethat is executed by Node.js, breaking changes do not follow typical Node.jsdeprecation policy and may occur on each semver-major release without priorwarning.

Example embedding application#

The following sections will provide an overview over how to use these APIsto create an application from scratch that will perform the equivalent ofnode -e <code>, i.e. that will take a piece of JavaScript and run it ina Node.js-specific environment.

The full code can be foundin the Node.js source tree.

Setting up a per-process state#

Node.js requires some per-process state management in order to run:

  • Arguments parsing for Node.jsCLI options,
  • V8 per-process requirements, such as av8::Platform instance.

The following example shows how these can be set up. Some class names are fromthenode andv8 C++ namespaces, respectively.

intmain(int argc,char** argv){  argv =uv_setup_args(argc, argv);std::vector<std::string>args(argv, argv + argc);// Parse Node.js CLI options, and print any errors that have occurred while// trying to parse them.  std::unique_ptr<node::InitializationResult> result =      node::InitializeOncePerProcess(args, {        node::ProcessInitializationFlags::kNoInitializeV8,        node::ProcessInitializationFlags::kNoInitializeNodeV8Platform      });for (const std::string& error : result->errors())fprintf(stderr,"%s: %s\n", args[0].c_str(), error.c_str());if (result->early_return() !=0) {return result->exit_code();  }// Create a v8::Platform instance. `MultiIsolatePlatform::Create()` is a way// to create a v8::Platform instance that Node.js can use when creating// Worker threads. When no `MultiIsolatePlatform` instance is present,// Worker threads are disabled.  std::unique_ptr<MultiIsolatePlatform> platform =      MultiIsolatePlatform::Create(4);  V8::InitializePlatform(platform.get());  V8::Initialize();// See below for the contents of this function.int ret =RunNodeInstance(      platform.get(), result->args(), result->exec_args());  V8::Dispose();  V8::DisposePlatform();  node::TearDownOncePerProcess();return ret;}

Setting up a per-instance state#

History
VersionChanges
v15.0.0

TheCommonEnvironmentSetup andSpinEventLoop utilities were added.

Node.js has a concept of a “Node.js instance”, that is commonly being referredto asnode::Environment. Eachnode::Environment is associated with:

  • Exactly onev8::Isolate, i.e. one JS Engine instance,
  • Exactly oneuv_loop_t, i.e. one event loop,
  • A number ofv8::Contexts, but exactly one mainv8::Context, and
  • Onenode::IsolateData instance that contains information that could beshared by multiplenode::Environments. The embedder should make surethatnode::IsolateData is shared only amongnode::Environments thatuse the samev8::Isolate, Node.js does not perform this check.

In order to set up av8::Isolate, anv8::ArrayBuffer::Allocator needsto be provided. One possible choice is the default Node.js allocator, whichcan be created throughnode::ArrayBufferAllocator::Create(). Using the Node.jsallocator allows minor performance optimizations when addons use the Node.jsC++Buffer API, and is required in order to trackArrayBuffer memory inprocess.memoryUsage().

Additionally, eachv8::Isolate that is used for a Node.js instance needs tobe registered and unregistered with theMultiIsolatePlatform instance, if oneis being used, in order for the platform to know which event loop to usefor tasks scheduled by thev8::Isolate.

Thenode::NewIsolate() helper function creates av8::Isolate,sets it up with some Node.js-specific hooks (e.g. the Node.js error handler),and registers it with the platform automatically.

intRunNodeInstance(MultiIsolatePlatform* platform,const std::vector<std::string>& args,const std::vector<std::string>& exec_args){int exit_code =0;// Setup up a libuv event loop, v8::Isolate, and Node.js Environment.  std::vector<std::string> errors;  std::unique_ptr<CommonEnvironmentSetup> setup =      CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);if (!setup) {for (const std::string& err : errors)fprintf(stderr,"%s: %s\n", args[0].c_str(), err.c_str());return1;  }  Isolate* isolate = setup->isolate();  Environment* env = setup->env();  {Lockerlocker(isolate);Isolate::Scopeisolate_scope(isolate);HandleScopehandle_scope(isolate);// The v8::Context needs to be entered when node::CreateEnvironment() and// node::LoadEnvironment() are being called.Context::Scopecontext_scope(setup->context());// Set up the Node.js instance for execution, and run code inside of it.// There is also a variant that takes a callback and provides it with// the `require` and `process` objects, so that it can manually compile// and run scripts as needed.// The `require` function inside this script does *not* access the file// system, and can only load built-in Node.js modules.// `module.createRequire()` is being used to create one that is able to// load files from the disk, and uses the standard CommonJS file loader// instead of the internal-only `require` function.    MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(        env,"const publicRequire =""  require('node:module').createRequire(process.cwd() + '/');""globalThis.require = publicRequire;""require('node:vm').runInThisContext(process.argv[1]);");if (loadenv_ret.IsEmpty())// There has been a JS exception.return1;    exit_code = node::SpinEventLoop(env).FromMaybe(1);// node::Stop() can be used to explicitly stop the event loop and keep// further JavaScript from running. It can be called from any thread,// and will act like worker.terminate() if called from another thread.    node::Stop(env);  }return exit_code;}