1# C++ embedder API 2 3<!--introduced_in=v14.0.0--> 4 5Node.js provides a number of C++ APIs that can be used to execute JavaScript 6in a Node.js environment from other C++ software. 7 8The documentation for these APIs can be found in [src/node.h][] in the Node.js 9source tree. In addition to the APIs exposed by Node.js, some required concepts 10are provided by the V8 embedder API. 11 12Because using Node.js as an embedded library is different from writing code 13that is executed by Node.js, breaking changes do not follow typical Node.js 14[deprecation policy][] and may occur on each semver-major release without prior 15warning. 16 17## Example embedding application 18 19The following sections will provide an overview over how to use these APIs 20to create an application from scratch that will perform the equivalent of 21`node -e <code>`, i.e. that will take a piece of JavaScript and run it in 22a Node.js-specific environment. 23 24The full code can be found [in the Node.js source tree][embedtest.cc]. 25 26### Setting up per-process state 27 28Node.js requires some per-process state management in order to run: 29 30* Arguments parsing for Node.js [CLI options][], 31* V8 per-process requirements, such as a `v8::Platform` instance. 32 33The following example shows how these can be set up. Some class names are from 34the `node` and `v8` C++ namespaces, respectively. 35 36```cpp 37int main(int argc, char** argv) { 38 argv = uv_setup_args(argc, argv); 39 std::vector<std::string> args(argv, argv + argc); 40 std::vector<std::string> exec_args; 41 std::vector<std::string> errors; 42 // Parse Node.js CLI options, and print any errors that have occurred while 43 // trying to parse them. 44 int exit_code = node::InitializeNodeWithArgs(&args, &exec_args, &errors); 45 for (const std::string& error : errors) 46 fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str()); 47 if (exit_code != 0) { 48 return exit_code; 49 } 50 51 // Create a v8::Platform instance. `MultiIsolatePlatform::Create()` is a way 52 // to create a v8::Platform instance that Node.js can use when creating 53 // Worker threads. When no `MultiIsolatePlatform` instance is present, 54 // Worker threads are disabled. 55 std::unique_ptr<MultiIsolatePlatform> platform = 56 MultiIsolatePlatform::Create(4); 57 V8::InitializePlatform(platform.get()); 58 V8::Initialize(); 59 60 // See below for the contents of this function. 61 int ret = RunNodeInstance(platform.get(), args, exec_args); 62 63 V8::Dispose(); 64 V8::ShutdownPlatform(); 65 return ret; 66} 67``` 68 69### Per-instance state 70 71Node.js has a concept of a “Node.js instance”, that is commonly being referred 72to as `node::Environment`. Each `node::Environment` is associated with: 73 74* Exactly one `v8::Isolate`, i.e. one JS Engine instance, 75* Exactly one `uv_loop_t`, i.e. one event loop, and 76* A number of `v8::Context`s, but exactly one main `v8::Context`. 77* One `node::IsolateData` instance that contains information that could be 78 shared by multiple `node::Environment`s that use the same `v8::Isolate`. 79 Currently, no testing if performed for this scenario. 80 81In order to set up a `v8::Isolate`, an `v8::ArrayBuffer::Allocator` needs 82to be provided. One possible choice is the default Node.js allocator, which 83can be created through `node::ArrayBufferAllocator::Create()`. Using the Node.js 84allocator allows minor performance optimizations when addons use the Node.js 85C++ `Buffer` API, and is required in order to track `ArrayBuffer` memory in 86[`process.memoryUsage()`][]. 87 88Additionally, each `v8::Isolate` that is used for a Node.js instance needs to 89be registered and unregistered with the `MultiIsolatePlatform` instance, if one 90is being used, in order for the platform to know which event loop to use 91for tasks scheduled by the `v8::Isolate`. 92 93The `node::NewIsolate()` helper function creates a `v8::Isolate`, 94sets it up with some Node.js-specific hooks (e.g. the Node.js error handler), 95and registers it with the platform automatically. 96 97```cpp 98int RunNodeInstance(MultiIsolatePlatform* platform, 99 const std::vector<std::string>& args, 100 const std::vector<std::string>& exec_args) { 101 int exit_code = 0; 102 // Set up a libuv event loop. 103 uv_loop_t loop; 104 int ret = uv_loop_init(&loop); 105 if (ret != 0) { 106 fprintf(stderr, "%s: Failed to initialize loop: %s\n", 107 args[0].c_str(), 108 uv_err_name(ret)); 109 return 1; 110 } 111 112 std::shared_ptr<ArrayBufferAllocator> allocator = 113 ArrayBufferAllocator::Create(); 114 115 Isolate* isolate = NewIsolate(allocator, &loop, platform); 116 if (isolate == nullptr) { 117 fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str()); 118 return 1; 119 } 120 121 { 122 Locker locker(isolate); 123 Isolate::Scope isolate_scope(isolate); 124 125 // Create a node::IsolateData instance that will later be released using 126 // node::FreeIsolateData(). 127 std::unique_ptr<IsolateData, decltype(&node::FreeIsolateData)> isolate_data( 128 node::CreateIsolateData(isolate, &loop, platform, allocator.get()), 129 node::FreeIsolateData); 130 131 // Set up a new v8::Context. 132 HandleScope handle_scope(isolate); 133 Local<Context> context = node::NewContext(isolate); 134 if (context.IsEmpty()) { 135 fprintf(stderr, "%s: Failed to initialize V8 Context\n", args[0].c_str()); 136 return 1; 137 } 138 139 // The v8::Context needs to be entered when node::CreateEnvironment() and 140 // node::LoadEnvironment() are being called. 141 Context::Scope context_scope(context); 142 143 // Create a node::Environment instance that will later be released using 144 // node::FreeEnvironment(). 145 std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env( 146 node::CreateEnvironment(isolate_data.get(), context, args, exec_args), 147 node::FreeEnvironment); 148 149 // Set up the Node.js instance for execution, and run code inside of it. 150 // There is also a variant that takes a callback and provides it with 151 // the `require` and `process` objects, so that it can manually compile 152 // and run scripts as needed. 153 // The `require` function inside this script does *not* access the file 154 // system, and can only load built-in Node.js modules. 155 // `module.createRequire()` is being used to create one that is able to 156 // load files from the disk, and uses the standard CommonJS file loader 157 // instead of the internal-only `require` function. 158 MaybeLocal<Value> loadenv_ret = node::LoadEnvironment( 159 env.get(), 160 "const publicRequire =" 161 " require('module').createRequire(process.cwd() + '/');" 162 "globalThis.require = publicRequire;" 163 "require('vm').runInThisContext(process.argv[1]);"); 164 165 if (loadenv_ret.IsEmpty()) // There has been a JS exception. 166 return 1; 167 168 { 169 // SealHandleScope protects against handle leaks from callbacks. 170 SealHandleScope seal(isolate); 171 bool more; 172 do { 173 uv_run(&loop, UV_RUN_DEFAULT); 174 175 // V8 tasks on background threads may end up scheduling new tasks in the 176 // foreground, which in turn can keep the event loop going. For example, 177 // WebAssembly.compile() may do so. 178 platform->DrainTasks(isolate); 179 180 // If there are new tasks, continue. 181 more = uv_loop_alive(&loop); 182 if (more) continue; 183 184 // node::EmitProcessBeforeExit() is used to emit the 'beforeExit' event 185 // on the `process` object. 186 if (node::EmitProcessBeforeExit(env.get()).IsNothing()) 187 break; 188 189 // 'beforeExit' can also schedule new work that keeps the event loop 190 // running. 191 more = uv_loop_alive(&loop); 192 } while (more == true); 193 } 194 195 // node::EmitProcessExit() returns the current exit code. 196 exit_code = node::EmitProcessExit(env.get()).FromMaybe(1); 197 198 // node::Stop() can be used to explicitly stop the event loop and keep 199 // further JavaScript from running. It can be called from any thread, 200 // and will act like worker.terminate() if called from another thread. 201 node::Stop(env.get()); 202 } 203 204 // Unregister the Isolate with the platform and add a listener that is called 205 // when the Platform is done cleaning up any state it had associated with 206 // the Isolate. 207 bool platform_finished = false; 208 platform->AddIsolateFinishedCallback(isolate, [](void* data) { 209 *static_cast<bool*>(data) = true; 210 }, &platform_finished); 211 platform->UnregisterIsolate(isolate); 212 isolate->Dispose(); 213 214 // Wait until the platform has cleaned up all relevant resources. 215 while (!platform_finished) 216 uv_run(&loop, UV_RUN_ONCE); 217 int err = uv_loop_close(&loop); 218 assert(err == 0); 219 220 return exit_code; 221} 222``` 223 224[CLI options]: cli.md 225[`process.memoryUsage()`]: process.md#process_process_memoryusage 226[deprecation policy]: deprecations.md 227[embedtest.cc]: https://github.com/nodejs/node/blob/HEAD/test/embedding/embedtest.cc 228[src/node.h]: https://github.com/nodejs/node/blob/HEAD/src/node.h 229