• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# C++ embedder API
2
3<!--introduced_in=v12.19.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  // Parse Node.js CLI options, and print any errors that have occurred while
41  // trying to parse them.
42  std::unique_ptr<node::InitializationResult> result =
43      node::InitializeOncePerProcess(args, {
44        node::ProcessInitializationFlags::kNoInitializeV8,
45        node::ProcessInitializationFlags::kNoInitializeNodeV8Platform
46      });
47
48  for (const std::string& error : result->errors())
49    fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
50  if (result->early_return() != 0) {
51    return result->exit_code();
52  }
53
54  // Create a v8::Platform instance. `MultiIsolatePlatform::Create()` is a way
55  // to create a v8::Platform instance that Node.js can use when creating
56  // Worker threads. When no `MultiIsolatePlatform` instance is present,
57  // Worker threads are disabled.
58  std::unique_ptr<MultiIsolatePlatform> platform =
59      MultiIsolatePlatform::Create(4);
60  V8::InitializePlatform(platform.get());
61  V8::Initialize();
62
63  // See below for the contents of this function.
64  int ret = RunNodeInstance(
65      platform.get(), result->args(), result->exec_args());
66
67  V8::Dispose();
68  V8::DisposePlatform();
69
70  node::TearDownOncePerProcess();
71  return ret;
72}
73```
74
75### Per-instance state
76
77<!-- YAML
78changes:
79  - version: v15.0.0
80    pr-url: https://github.com/nodejs/node/pull/35597
81    description:
82      The `CommonEnvironmentSetup` and `SpinEventLoop` utilities were added.
83-->
84
85Node.js has a concept of a “Node.js instance”, that is commonly being referred
86to as `node::Environment`. Each `node::Environment` is associated with:
87
88* Exactly one `v8::Isolate`, i.e. one JS Engine instance,
89* Exactly one `uv_loop_t`, i.e. one event loop, and
90* A number of `v8::Context`s, but exactly one main `v8::Context`.
91* One `node::IsolateData` instance that contains information that could be
92  shared by multiple `node::Environment`s that use the same `v8::Isolate`.
93  Currently, no testing if performed for this scenario.
94
95In order to set up a `v8::Isolate`, an `v8::ArrayBuffer::Allocator` needs
96to be provided. One possible choice is the default Node.js allocator, which
97can be created through `node::ArrayBufferAllocator::Create()`. Using the Node.js
98allocator allows minor performance optimizations when addons use the Node.js
99C++ `Buffer` API, and is required in order to track `ArrayBuffer` memory in
100[`process.memoryUsage()`][].
101
102Additionally, each `v8::Isolate` that is used for a Node.js instance needs to
103be registered and unregistered with the `MultiIsolatePlatform` instance, if one
104is being used, in order for the platform to know which event loop to use
105for tasks scheduled by the `v8::Isolate`.
106
107The `node::NewIsolate()` helper function creates a `v8::Isolate`,
108sets it up with some Node.js-specific hooks (e.g. the Node.js error handler),
109and registers it with the platform automatically.
110
111```cpp
112int RunNodeInstance(MultiIsolatePlatform* platform,
113                    const std::vector<std::string>& args,
114                    const std::vector<std::string>& exec_args) {
115  int exit_code = 0;
116
117  // Setup up a libuv event loop, v8::Isolate, and Node.js Environment.
118  std::vector<std::string> errors;
119  std::unique_ptr<CommonEnvironmentSetup> setup =
120      CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
121  if (!setup) {
122    for (const std::string& err : errors)
123      fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
124    return 1;
125  }
126
127  Isolate* isolate = setup->isolate();
128  Environment* env = setup->env();
129
130  {
131    Locker locker(isolate);
132    Isolate::Scope isolate_scope(isolate);
133    HandleScope handle_scope(isolate);
134    // The v8::Context needs to be entered when node::CreateEnvironment() and
135    // node::LoadEnvironment() are being called.
136    Context::Scope context_scope(setup->context());
137
138    // Set up the Node.js instance for execution, and run code inside of it.
139    // There is also a variant that takes a callback and provides it with
140    // the `require` and `process` objects, so that it can manually compile
141    // and run scripts as needed.
142    // The `require` function inside this script does *not* access the file
143    // system, and can only load built-in Node.js modules.
144    // `module.createRequire()` is being used to create one that is able to
145    // load files from the disk, and uses the standard CommonJS file loader
146    // instead of the internal-only `require` function.
147    MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
148        env,
149        "const publicRequire ="
150        "  require('node:module').createRequire(process.cwd() + '/');"
151        "globalThis.require = publicRequire;"
152        "require('node:vm').runInThisContext(process.argv[1]);");
153
154    if (loadenv_ret.IsEmpty())  // There has been a JS exception.
155      return 1;
156
157    exit_code = node::SpinEventLoop(env).FromMaybe(1);
158
159    // node::Stop() can be used to explicitly stop the event loop and keep
160    // further JavaScript from running. It can be called from any thread,
161    // and will act like worker.terminate() if called from another thread.
162    node::Stop(env);
163  }
164
165  return exit_code;
166}
167```
168
169[CLI options]: cli.md
170[`process.memoryUsage()`]: process.md#processmemoryusage
171[deprecation policy]: deprecations.md
172[embedtest.cc]: https://github.com/nodejs/node/blob/HEAD/test/embedding/embedtest.cc
173[src/node.h]: https://github.com/nodejs/node/blob/HEAD/src/node.h
174