From 6085157790124e08899ef597c1938404850903cb Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 5 Jun 2026 17:39:27 +0200 Subject: [PATCH 1/3] src: officially deprecate `node::ObjectWrap` This interface has long been unmaintained and doesn't match current best practices for C++ object management. It's also simply not a good idea to have this header be part of Node.js core; since it is an inline header, user code will depend on the Node.js version it was compiled with, and there is no way to adopt changes to this header independently of changing the target Node.js version. Better userland alternatives exist and have long been more popular, notably, `node-addon-api` and, for use cases where direct V8 API access is required, `nan`. Signed-off-by: Anna Henningsen --- src/node_object_wrap.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index 03c986689fd294..f2a619b6fade51 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -27,7 +27,10 @@ namespace node { -class ObjectWrap { +// Legacy interface for tying C++ objects to JavaScript objects. +// This is not intended to be used by new code, and userland alternatives +// should be preferred. +class NODE_DEPRECATED("Use userland alternatives such as node-addon-api or nan instead", ObjectWrap) { public: ObjectWrap() { refs_ = 0; From 05d80ba2e7f7ed057f6ba5b0018fb3ad1d1fc74b Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 5 Jun 2026 17:50:54 +0200 Subject: [PATCH 2/3] fixup! src: officially deprecate `node::ObjectWrap` --- src/node_object_wrap.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index f2a619b6fade51..0002c25e054261 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -30,7 +30,9 @@ namespace node { // Legacy interface for tying C++ objects to JavaScript objects. // This is not intended to be used by new code, and userland alternatives // should be preferred. -class NODE_DEPRECATED("Use userland alternatives such as node-addon-api or nan instead", ObjectWrap) { +class NODE_DEPRECATED( + "Use userland alternatives such as node-addon-api or nan instead", + ObjectWrap) { public: ObjectWrap() { refs_ = 0; From 450d97ac11efba84d2922b690cfab0c6c1e9d371 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 10 Jun 2026 17:32:59 +0200 Subject: [PATCH 3/3] fixup! src: officially deprecate `node::ObjectWrap` --- doc/api/addons.md | 98 ++++++++++++++++++++++++++++++++------ tools/doc/addon-verify.mjs | 17 ++++--- 2 files changed, 94 insertions(+), 21 deletions(-) diff --git a/doc/api/addons.md b/doc/api/addons.md index cc26c73cb99541..8272e1e77c0ae8 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -12,7 +12,7 @@ There are three options for implementing addons: * [Node-API][] (recommended) * `nan` ([Native Abstractions for Node.js][]) -* direct use of internal V8, libuv, and Node.js libraries +* direct use of public V8, libuv, and Node.js interfaces This rest of this document focuses on the latter, requiring knowledge of multiple components and APIs: @@ -35,8 +35,8 @@ knowledge of multiple components and APIs: offloading work via libuv to non-blocking system operations, worker threads, or a custom use of libuv threads. -* Internal Node.js libraries: Node.js itself exports C++ APIs that addons can - use, the most important of which is the `node::ObjectWrap` class. +* Public Node.js interfaces: Node.js itself exports C++ APIs that addons + and embedders can make use of. * Other statically linked libraries (including OpenSSL): These other libraries are located in the `deps/` directory in the Node.js source @@ -815,7 +815,8 @@ NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll) } // namespace demo ``` -Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`: +Then, in `myobject.h`, the wrapper class inherits from a helper `ObjectWrap` +which handles tying the lifetime of the C++ object to the exposed JS object: @@ -825,11 +826,11 @@ Then, in `myobject.h`, the wrapper class inherits from `node::ObjectWrap`: #define MYOBJECT_H #include -#include +#include "object_wrap.h" namespace demo { -class MyObject : public node::ObjectWrap { +class MyObject : public ObjectWrap { public: static void Init(v8::Local exports); @@ -848,6 +849,68 @@ class MyObject : public node::ObjectWrap { #endif ``` +where `ObjectWrap` is defined as + + + +```cpp +// object_wrap.h +#ifndef OBJECTWRAP_H +#define OBJECTWRAP_H + +#include + +namespace demo { + +class ObjectWrap { + public: + ObjectWrap() : isolate_(v8::Isolate::GetCurrent()) { + node::AddEnvironmentCleanupHook(isolate_, CleanupHook, this); + } + + virtual ~ObjectWrap() { + node::RemoveEnvironmentCleanupHook(isolate_, CleanupHook, this); + } + + template + static T* Unwrap(v8::Local handle) { + void* ptr = handle->GetAlignedPointerFromInternalField( + 0, v8::kEmbedderDataTypeTagDefault); + ObjectWrap* wrap = static_cast(ptr); + return static_cast(wrap); + } + + v8::Local object() { + return handle_.Get(isolate_); + } + + protected: + inline void Wrap(v8::Local handle) { + handle->SetAlignedPointerInInternalField( + 0, this, v8::kEmbedderDataTypeTagDefault); + handle_.Reset(isolate_, handle); + } + + inline void MakeWeak() { + handle_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter); + } + + private: + static void WeakCallback( + const v8::WeakCallbackInfo& data) { + delete data.GetParameter(); + } + + static void CleanupHook(void* arg) { delete static_cast(arg); } + + v8::Global handle_; + v8::Isolate* isolate_; +}; + +} // namespace demo +#endif +``` + In `myobject.cc`, implement the various methods that are to be exposed. In the following code, the method `plusOne()` is exposed by adding it to the constructor's prototype: @@ -912,6 +975,7 @@ void MyObject::New(const FunctionCallbackInfo& args) { 0 : args[0]->NumberValue(context).FromMaybe(0); MyObject* obj = new MyObject(value); obj->Wrap(args.This()); + obj->MakeWeak(); args.GetReturnValue().Set(args.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. @@ -1039,11 +1103,11 @@ JavaScript: #define MYOBJECT_H #include -#include +#include "object_wrap.h" namespace demo { -class MyObject : public node::ObjectWrap { +class MyObject : public ObjectWrap { public: static void Init(); static void NewInstance(const v8::FunctionCallbackInfo& args); @@ -1063,7 +1127,8 @@ class MyObject : public node::ObjectWrap { #endif ``` -The implementation in `myobject.cc` is similar to the previous example: +Our `ObjectWrap` helper remains the same as in the previous example, +and implementation in `myobject.cc` is similar as well: @@ -1125,6 +1190,7 @@ void MyObject::New(const FunctionCallbackInfo& args) { 0 : args[0]->NumberValue(context).FromMaybe(0); MyObject* obj = new MyObject(value); obj->Wrap(args.This()); + obj->MakeWeak(); args.GetReturnValue().Set(args.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. @@ -1208,7 +1274,7 @@ console.log(obj2.plusOne()); In addition to wrapping and returning C++ objects, it is possible to pass wrapped objects around by unwrapping them with the Node.js helper function -`node::ObjectWrap::Unwrap`. The following examples shows a function `add()` +`ObjectWrap::Unwrap`. The following examples shows a function `add()` that can take two `MyObject` objects as input arguments: @@ -1238,9 +1304,9 @@ void Add(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); - MyObject* obj1 = node::ObjectWrap::Unwrap( + MyObject* obj1 = ObjectWrap::Unwrap( args[0]->ToObject(context).ToLocalChecked()); - MyObject* obj2 = node::ObjectWrap::Unwrap( + MyObject* obj2 = ObjectWrap::Unwrap( args[1]->ToObject(context).ToLocalChecked()); double sum = obj1->value() + obj2->value(); @@ -1270,11 +1336,11 @@ after unwrapping the object. #define MYOBJECT_H #include -#include +#include "object_wrap.h" namespace demo { -class MyObject : public node::ObjectWrap { +class MyObject : public ObjectWrap { public: static void Init(); static void NewInstance(const v8::FunctionCallbackInfo& args); @@ -1294,7 +1360,8 @@ class MyObject : public node::ObjectWrap { #endif ``` -The implementation of `myobject.cc` remains similar to the previous version: +Our `ObjectWrap` helper remains the same as in the previous example, +and implementation in `myobject.cc` is similar as well: @@ -1352,6 +1419,7 @@ void MyObject::New(const FunctionCallbackInfo& args) { 0 : args[0]->NumberValue(context).FromMaybe(0); MyObject* obj = new MyObject(value); obj->Wrap(args.This()); + obj->MakeWeak(); args.GetReturnValue().Set(args.This()); } else { // Invoked as plain function `MyObject(...)`, turn into construct call. diff --git a/tools/doc/addon-verify.mjs b/tools/doc/addon-verify.mjs index 4fcf1848d7d342..1338fd5ad74187 100755 --- a/tools/doc/addon-verify.mjs +++ b/tools/doc/addon-verify.mjs @@ -5,7 +5,7 @@ // // Each code block to extract is preceded by a marker in the markdown: // -// +// // ```cpp // #include // ... @@ -33,7 +33,7 @@ if (!values.input || !values.output) { const src = await open(values.input, 'r'); -const MARKER_RE = /^$/; +const MARKER_RE = /^$/; const entries = []; let nextBlockIsAddonVerifyFile = false; @@ -47,7 +47,7 @@ for await (const line of src.readLines()) { continue; } - entries.at(-1).content += `${line}\n`; + entries.at(-1).contentRef.content += `${line}\n`; } if (nextBlockIsAddonVerifyFile) { if (line) { @@ -59,8 +59,13 @@ for await (const line of src.readLines()) { const match = MARKER_RE.exec(line); if (match) { nextBlockIsAddonVerifyFile = true; - const [, dir, name] = match; - entries.push({ dir, name, content: '' }); + const { filenames } = match.groups; + // Use a single contentRef for files with identical contents + const contentRef = { content: '' }; + for (const filename of filenames.split(/\s+/).filter(Boolean)) { + const [dir, name] = filename.split('/'); + entries.push({ dir, name, contentRef }); + } } } @@ -74,7 +79,7 @@ for (const [name, files] of sections) { mkdirSync(dir, { recursive: true }); for (const file of files) { - let content = file.content; + let { content } = file.contentRef; if (file.name === 'test.js') { content = "'use strict';\n" +