class: center, middle # Navigating Node.js C++ code The quick version
(Terms and conditions apply)
Anna Henningsen
she/her
@addaleax
--- # Layers (almost accurate)
Is called …
Is a …
Owns e.g. …
Relates to …
v8::Context
Set of JS builtins
Array.prototype
n
Context
s per 1
Environment
node::Environment
Set of Node.js API instances
net
module
n (≈ 1)
Environment
s per 1
Isolate
v8::Isolate
JS engine runtime
JS heap, GC
n (≈ 1)
Isolate
s per 1 thread
Thread
Execution context
Event loop
n threads per process
--- # V8 API concepts ```c++ /* ... */ v8::Isolate* isolate = ...; v8::HandleScope handle_scope(isolate); v8::Local
context = isolate->GetCurrentContext(); v8::Local
str = OneByteString(isolate, "foobar"); { v8::HandleScope inner_handle_scope(isolate); v8::Local
obj = v8::Object::New(isolate); // Empty (“Nothing”) Maybe/MaybeLocal = pending exception → Return to JS! if (obj->Set(context, str, v8::Integer::New(isolate, 42)).IsNothing()) return; } // `obj` can be GC'ed now, `str` is still there ``` --- # Node.js binding function ```c++ void ErrName(const FunctionCallbackInfo
& args) { // Implicit HandleScope // args = { env | return-value-placeholder | args.This() | args[0], args[1], ... } Environment* env = Environment::GetCurrent(args); int err; // Int32Value() returns Maybe
if (!args[0]->Int32Value(env->context()).To(&err)) return; CHECK_LT(err, 0); // Super-easy way to crash Node const char* name = uv_err_name(err); args.GetReturnValue().Set(OneByteString(env->isolate(), name)); } ``` --- # Binding to JS ```c++ void Initialize(Local
target, // exports Local
unused, // module Local
context) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); target->Set(FIXED_ONE_BYTE_STRING(isolate, "errname"), env->NewFunctionTemplate(ErrName) ->GetFunction(env->context()) .ToLocalChecked()); // Same thing, but nicer: env->SetMethod(target, "errname", ErrName); } NODE_MODULE_CONTEXT_AWARE_INTERNAL(uv, node::Initialize) ``` --- # A new `setImmediate()` ```js const { Immediate } = internalBinding('fakeSetImmediate'); function setImmediate(cb, ...args) { const immediate = new Immediate(); immediate.ondone = () => cb(...args); immediate.start(); } ``` ```c++ void Initialize(Local
target, Local
unused, Local
context) { Environment* env = Environment::GetCurrent(context); Local
tmpl = env->NewFunctionTemplate(ImmediateWrap::New); tmpl->InstanceTemplate()->SetInternalFieldCount(1); env->SetProtoMethod(tmpl, "start", ImmediateWrap::Start); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "Immediate"), tmpl->GetFunction(env->context()).ToLocalChecked()); } NODE_MODULE_CONTEXT_AWARE_INTERNAL(fakeSetImmediate, Initialize) ``` --- ```c++ class ImmediateWrap : public AsyncWrap { // AsyncWrap = BaseObject + async context ImmediateWrap(Environment* env, Local
object) : AsyncWrap(env, object) { MakeWeak(); } static void New(const FunctionCallbackInfo
& args) { // 'public' JS constructor CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); new ImmediateWrap(env, args.This()); // Store instance internally on `this` } static void Start(const FunctionCallbackInfo
& args) { // prototype method ImmediateWrap* immediate = Unwrap
(args.This()); // similar to ASSIGN_OR_RETURN_UNWRAP immediate->env()->SetImmediate([](Environment* env, void* data) { static_cast
(data)->RunCallback(); }, static_cast
(immediate)); ClearWeak(); } void RunCallback() { MakeWeak(); Local
args[1] = { env()->GetNow() }; MakeCallback(env()->ondone_string(), arraysize(args), args); // terrible function name :( } }; ``` --- # Thank you! Slides @ https://addaleax.net/node-cpp-vancouver