Skip to content

Commit 1a70820

Browse files
committed
doc: add basic embedding example documentation
Backport-PR-URL: #35241 PR-URL: #30467 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
1 parent 4af336d commit 1a70820

File tree

2 files changed

+230
-3
lines changed

2 files changed

+230
-3
lines changed

doc/api/embedding.md

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

doc/api/index.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
* [Assertion testing](assert.html)
1414
* [Async hooks](async_hooks.html)
1515
* [Buffer](buffer.html)
16-
* [C++ addons](addons.html)
17-
* [C/C++ addons with N-API](n-api.html)
18-
* [Child processes](child_process.html)
16+
* [C++ Addons](addons.html)
17+
* [C/C++ Addons with N-API](n-api.html)
18+
* [C++ Embedder API](embedding.html)
19+
* [Child Processes](child_process.html)
1920
* [Cluster](cluster.html)
2021
* [Command line options](cli.html)
2122
* [Console](console.html)

0 commit comments

Comments
 (0)