diff --git a/src/api/environment.cc b/src/api/environment.cc
index f56ee8d12bd1c5..239b14e44add88 100644
--- a/src/api/environment.cc
+++ b/src/api/environment.cc
@@ -833,7 +833,9 @@ void AddLinkedBinding(Environment* env, const node_module& mod) {
 }
 
 void AddLinkedBinding(Environment* env, const napi_module& mod) {
-  AddLinkedBinding(env, napi_module_to_node_module(&mod));
+  node_module node_mod = napi_module_to_node_module(&mod);
+  node_mod.nm_flags = NM_F_LINKED;
+  AddLinkedBinding(env, node_mod);
 }
 
 void AddLinkedBinding(Environment* env,
@@ -854,6 +856,32 @@ void AddLinkedBinding(Environment* env,
   AddLinkedBinding(env, mod);
 }
 
+void AddLinkedBinding(Environment* env,
+                      const char* name,
+                      napi_addon_register_func fn) {
+  node_module mod = {
+      -1,
+      NM_F_LINKED,
+      nullptr,  // nm_dso_handle
+      nullptr,  // nm_filename
+      nullptr,  // nm_register_func
+      [](v8::Local<v8::Object> exports,
+         v8::Local<v8::Value> module,
+         v8::Local<v8::Context> context,
+         void* priv) {
+        napi_module_register_by_symbol(
+            exports,
+            module,
+            context,
+            reinterpret_cast<napi_addon_register_func>(priv));
+      },
+      name,
+      reinterpret_cast<void*>(fn),
+      nullptr  // nm_link
+  };
+  AddLinkedBinding(env, mod);
+}
+
 static std::atomic<uint64_t> next_thread_id{0};
 
 ThreadId AllocateEnvironmentThreadId() {
diff --git a/src/node.h b/src/node.h
index fc2531f8677e70..e5b23f30d9530b 100644
--- a/src/node.h
+++ b/src/node.h
@@ -75,6 +75,9 @@
 #include "v8-platform.h"  // NOLINT(build/include_order)
 #include "node_version.h"  // NODE_MODULE_VERSION
 
+#define NAPI_EXPERIMENTAL
+#include "node_api.h"
+
 #include <functional>
 #include <memory>
 #include <ostream>
@@ -121,8 +124,6 @@
 // Forward-declare libuv loop
 struct uv_loop_s;
 
-struct napi_module;
-
 // Forward-declare these functions now to stop MSVS from becoming
 // terminally confused when it's done in node_internals.h
 namespace node {
@@ -1235,6 +1236,9 @@ NODE_EXTERN void AddLinkedBinding(Environment* env,
                                   const char* name,
                                   addon_context_register_func fn,
                                   void* priv);
+NODE_EXTERN void AddLinkedBinding(Environment* env,
+                                  const char* name,
+                                  napi_addon_register_func fn);
 
 /* Registers a callback with the passed-in Environment instance. The callback
  * is called after the event loop exits, but before the VM is disposed.
diff --git a/src/node_api.h b/src/node_api.h
index caf987dbd8dd8b..4d1b50414e2e02 100644
--- a/src/node_api.h
+++ b/src/node_api.h
@@ -31,6 +31,7 @@ struct uv_loop_s;  // Forward declaration.
 typedef napi_value(NAPI_CDECL* napi_addon_register_func)(napi_env env,
                                                          napi_value exports);
 
+// Used by deprecated registration method napi_module_register.
 typedef struct napi_module {
   int nm_version;
   unsigned int nm_flags;
@@ -43,70 +44,15 @@ typedef struct napi_module {
 
 #define NAPI_MODULE_VERSION 1
 
-#if defined(_MSC_VER)
-#if defined(__cplusplus)
-#define NAPI_C_CTOR(fn)                                                        \
-  static void NAPI_CDECL fn(void);                                             \
-  namespace {                                                                  \
-  struct fn##_ {                                                               \
-    fn##_() { fn(); }                                                          \
-  } fn##_v_;                                                                   \
-  }                                                                            \
-  static void NAPI_CDECL fn(void)
-#else  // !defined(__cplusplus)
-#pragma section(".CRT$XCU", read)
-// The NAPI_C_CTOR macro defines a function fn that is called during CRT
-// initialization.
-// C does not support dynamic initialization of static variables and this code
-// simulates C++ behavior. Exporting the function pointer prevents it from being
-// optimized. See for details:
-// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170
-#define NAPI_C_CTOR(fn)                                                        \
-  static void NAPI_CDECL fn(void);                                             \
-  __declspec(dllexport, allocate(".CRT$XCU")) void(NAPI_CDECL * fn##_)(void) = \
-      fn;                                                                      \
-  static void NAPI_CDECL fn(void)
-#endif  // defined(__cplusplus)
-#else
-#define NAPI_C_CTOR(fn)                                                        \
-  static void fn(void) __attribute__((constructor));                           \
-  static void fn(void)
-#endif
-
-#define NAPI_MODULE_X(modname, regfunc, priv, flags)                           \
-  EXTERN_C_START                                                               \
-  static napi_module _module = {                                               \
-      NAPI_MODULE_VERSION,                                                     \
-      flags,                                                                   \
-      __FILE__,                                                                \
-      regfunc,                                                                 \
-      #modname,                                                                \
-      priv,                                                                    \
-      {0},                                                                     \
-  };                                                                           \
-  NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); }         \
-  EXTERN_C_END
-
 #define NAPI_MODULE_INITIALIZER_X(base, version)                               \
   NAPI_MODULE_INITIALIZER_X_HELPER(base, version)
 #define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version
 
 #ifdef __wasm32__
-#define NAPI_WASM_INITIALIZER                                                  \
-  NAPI_MODULE_INITIALIZER_X(napi_register_wasm_v, NAPI_MODULE_VERSION)
-#define NAPI_MODULE(modname, regfunc)                                          \
-  EXTERN_C_START                                                               \
-  NAPI_MODULE_EXPORT napi_value NAPI_WASM_INITIALIZER(napi_env env,            \
-                                                      napi_value exports) {    \
-    return regfunc(env, exports);                                              \
-  }                                                                            \
-  EXTERN_C_END
+#define NAPI_MODULE_INITIALIZER_BASE napi_register_wasm_v
 #else
-#define NAPI_MODULE(modname, regfunc)                                          \
-  NAPI_MODULE_X(modname, regfunc, NULL, 0)  // NOLINT (readability/null_usage)
-#endif
-
 #define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v
+#endif
 
 #define NAPI_MODULE_INITIALIZER                                                \
   NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, NAPI_MODULE_VERSION)
@@ -116,12 +62,24 @@ typedef struct napi_module {
   NAPI_MODULE_EXPORT napi_value NAPI_MODULE_INITIALIZER(napi_env env,          \
                                                         napi_value exports);   \
   EXTERN_C_END                                                                 \
-  NAPI_MODULE(NODE_GYP_MODULE_NAME, NAPI_MODULE_INITIALIZER)                   \
   napi_value NAPI_MODULE_INITIALIZER(napi_env env, napi_value exports)
 
+#define NAPI_MODULE(modname, regfunc)                                          \
+  NAPI_MODULE_INIT() { return regfunc(env, exports); }
+
+// Deprecated. Use NAPI_MODULE.
+#define NAPI_MODULE_X(modname, regfunc, priv, flags)                           \
+  NAPI_MODULE(modname, regfunc)
+
 EXTERN_C_START
 
-NAPI_EXTERN void NAPI_CDECL napi_module_register(napi_module* mod);
+// Deprecated. Replaced by symbol-based registration defined by NAPI_MODULE
+// and NAPI_MODULE_INIT macros.
+#if defined(__cplusplus) && __cplusplus >= 201402L
+[[deprecated]]
+#endif
+NAPI_EXTERN void NAPI_CDECL
+napi_module_register(napi_module* mod);
 
 NAPI_EXTERN NAPI_NO_RETURN void NAPI_CDECL
 napi_fatal_error(const char* location,
diff --git a/test/cctest/test_linked_binding.cc b/test/cctest/test_linked_binding.cc
index 7e40068b5db799..1a4e6a838b5d72 100644
--- a/test/cctest/test_linked_binding.cc
+++ b/test/cctest/test_linked_binding.cc
@@ -1,19 +1,19 @@
-#include "node_test_fixture.h"
 #include "node_api.h"
+#include "node_test_fixture.h"
 
 void InitializeBinding(v8::Local<v8::Object> exports,
                        v8::Local<v8::Value> module,
                        v8::Local<v8::Context> context,
                        void* priv) {
   v8::Isolate* isolate = context->GetIsolate();
-  exports->Set(
-      context,
-      v8::String::NewFromOneByte(isolate,
-                                 reinterpret_cast<const uint8_t*>("key"))
-                                 .ToLocalChecked(),
-      v8::String::NewFromOneByte(isolate,
-                                 reinterpret_cast<const uint8_t*>("value"))
-                                 .ToLocalChecked())
+  exports
+      ->Set(context,
+            v8::String::NewFromOneByte(isolate,
+                                       reinterpret_cast<const uint8_t*>("key"))
+                .ToLocalChecked(),
+            v8::String::NewFromOneByte(
+                isolate, reinterpret_cast<const uint8_t*>("value"))
+                .ToLocalChecked())
       .FromJust();
 }
 
@@ -24,18 +24,18 @@ class LinkedBindingTest : public EnvironmentTestFixture {};
 TEST_F(LinkedBindingTest, SimpleTest) {
   const v8::HandleScope handle_scope(isolate_);
   const Argv argv;
-  Env test_env {handle_scope, argv};
+  Env test_env{handle_scope, argv};
 
   v8::Local<v8::Context> context = isolate_->GetCurrentContext();
 
-  const char* run_script =
-      "process._linkedBinding('cctest_linkedbinding').key";
-  v8::Local<v8::Script> script = v8::Script::Compile(
-      context,
-      v8::String::NewFromOneByte(isolate_,
-                                 reinterpret_cast<const uint8_t*>(run_script))
-                                 .ToLocalChecked())
-      .ToLocalChecked();
+  const char* run_script = "process._linkedBinding('cctest_linkedbinding').key";
+  v8::Local<v8::Script> script =
+      v8::Script::Compile(
+          context,
+          v8::String::NewFromOneByte(
+              isolate_, reinterpret_cast<const uint8_t*>(run_script))
+              .ToLocalChecked())
+          .ToLocalChecked();
   v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
   v8::String::Utf8Value utf8val(isolate_, completion_value);
   CHECK_NOT_NULL(*utf8val);
@@ -48,35 +48,35 @@ void InitializeLocalBinding(v8::Local<v8::Object> exports,
                             void* priv) {
   ++*static_cast<int*>(priv);
   v8::Isolate* isolate = context->GetIsolate();
-  exports->Set(
-      context,
-      v8::String::NewFromOneByte(isolate,
-                                 reinterpret_cast<const uint8_t*>("key"))
-                                 .ToLocalChecked(),
-      v8::String::NewFromOneByte(isolate,
-                                 reinterpret_cast<const uint8_t*>("value"))
-                                 .ToLocalChecked())
+  exports
+      ->Set(context,
+            v8::String::NewFromOneByte(isolate,
+                                       reinterpret_cast<const uint8_t*>("key"))
+                .ToLocalChecked(),
+            v8::String::NewFromOneByte(
+                isolate, reinterpret_cast<const uint8_t*>("value"))
+                .ToLocalChecked())
       .FromJust();
 }
 
 TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingTest) {
   const v8::HandleScope handle_scope(isolate_);
   const Argv argv;
-  Env test_env {handle_scope, argv};
+  Env test_env{handle_scope, argv};
 
   int calls = 0;
   AddLinkedBinding(*test_env, "local_linked", InitializeLocalBinding, &calls);
 
   v8::Local<v8::Context> context = isolate_->GetCurrentContext();
 
-  const char* run_script =
-      "process._linkedBinding('local_linked').key";
-  v8::Local<v8::Script> script = v8::Script::Compile(
-      context,
-      v8::String::NewFromOneByte(isolate_,
-                                 reinterpret_cast<const uint8_t*>(run_script))
-                                 .ToLocalChecked())
-      .ToLocalChecked();
+  const char* run_script = "process._linkedBinding('local_linked').key";
+  v8::Local<v8::Script> script =
+      v8::Script::Compile(
+          context,
+          v8::String::NewFromOneByte(
+              isolate_, reinterpret_cast<const uint8_t*>(run_script))
+              .ToLocalChecked())
+          .ToLocalChecked();
   v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
   v8::String::Utf8Value utf8val(isolate_, completion_value);
   CHECK_NOT_NULL(*utf8val);
@@ -86,41 +86,64 @@ TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingTest) {
 
 napi_value InitializeLocalNapiBinding(napi_env env, napi_value exports) {
   napi_value key, value;
-  CHECK_EQ(
-      napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &key), napi_ok);
-  CHECK_EQ(
-      napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &value), napi_ok);
+  CHECK_EQ(napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &key),
+           napi_ok);
+  CHECK_EQ(napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &value),
+           napi_ok);
   CHECK_EQ(napi_set_property(env, exports, key, value), napi_ok);
   return nullptr;
 }
 
 static napi_module local_linked_napi = {
-  NAPI_MODULE_VERSION,
-  node::ModuleFlags::kLinked,
-  nullptr,
-  InitializeLocalNapiBinding,
-  "local_linked_napi",
-  nullptr,
-  {0},
+    NAPI_MODULE_VERSION,
+    node::ModuleFlags::kLinked,
+    nullptr,
+    InitializeLocalNapiBinding,
+    "local_linked_napi",
+    nullptr,
+    {0},
 };
 
 TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiTest) {
   const v8::HandleScope handle_scope(isolate_);
   const Argv argv;
-  Env test_env {handle_scope, argv};
+  Env test_env{handle_scope, argv};
 
   AddLinkedBinding(*test_env, local_linked_napi);
 
   v8::Local<v8::Context> context = isolate_->GetCurrentContext();
 
-  const char* run_script =
-      "process._linkedBinding('local_linked_napi').hello";
-  v8::Local<v8::Script> script = v8::Script::Compile(
-      context,
-      v8::String::NewFromOneByte(isolate_,
-                                 reinterpret_cast<const uint8_t*>(run_script))
-                                 .ToLocalChecked())
-      .ToLocalChecked();
+  const char* run_script = "process._linkedBinding('local_linked_napi').hello";
+  v8::Local<v8::Script> script =
+      v8::Script::Compile(
+          context,
+          v8::String::NewFromOneByte(
+              isolate_, reinterpret_cast<const uint8_t*>(run_script))
+              .ToLocalChecked())
+          .ToLocalChecked();
+  v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
+  v8::String::Utf8Value utf8val(isolate_, completion_value);
+  CHECK_NOT_NULL(*utf8val);
+  CHECK_EQ(strcmp(*utf8val, "world"), 0);
+}
+
+TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiCallbackTest) {
+  const v8::HandleScope handle_scope(isolate_);
+  const Argv argv;
+  Env test_env{handle_scope, argv};
+
+  AddLinkedBinding(*test_env, "local_linked_napi", InitializeLocalNapiBinding);
+
+  v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+
+  const char* run_script = "process._linkedBinding('local_linked_napi').hello";
+  v8::Local<v8::Script> script =
+      v8::Script::Compile(
+          context,
+          v8::String::NewFromOneByte(
+              isolate_, reinterpret_cast<const uint8_t*>(run_script))
+              .ToLocalChecked())
+          .ToLocalChecked();
   v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
   v8::String::Utf8Value utf8val(isolate_, completion_value);
   CHECK_NOT_NULL(*utf8val);
@@ -129,33 +152,32 @@ TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiTest) {
 
 napi_value NapiLinkedWithInstanceData(napi_env env, napi_value exports) {
   int* instance_data = new int(0);
-  CHECK_EQ(
-      napi_set_instance_data(
-          env,
-          instance_data,
-          [](napi_env env, void* data, void* hint) {
-            ++*static_cast<int*>(data);
-          }, nullptr),
-      napi_ok);
+  CHECK_EQ(napi_set_instance_data(
+               env,
+               instance_data,
+               [](napi_env env, void* data, void* hint) {
+                 ++*static_cast<int*>(data);
+               },
+               nullptr),
+           napi_ok);
 
   napi_value key, value;
-  CHECK_EQ(
-      napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &key), napi_ok);
-  CHECK_EQ(
-      napi_create_external(env, instance_data, nullptr, nullptr, &value),
-      napi_ok);
+  CHECK_EQ(napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &key),
+           napi_ok);
+  CHECK_EQ(napi_create_external(env, instance_data, nullptr, nullptr, &value),
+           napi_ok);
   CHECK_EQ(napi_set_property(env, exports, key, value), napi_ok);
   return nullptr;
 }
 
 static napi_module local_linked_napi_id = {
-  NAPI_MODULE_VERSION,
-  node::ModuleFlags::kLinked,
-  nullptr,
-  NapiLinkedWithInstanceData,
-  "local_linked_napi_id",
-  nullptr,
-  {0},
+    NAPI_MODULE_VERSION,
+    node::ModuleFlags::kLinked,
+    nullptr,
+    NapiLinkedWithInstanceData,
+    "local_linked_napi_id",
+    nullptr,
+    {0},
 };
 
 TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiInstanceDataTest) {
@@ -164,7 +186,7 @@ TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiInstanceDataTest) {
 
   {
     const Argv argv;
-    Env test_env {handle_scope, argv};
+    Env test_env{handle_scope, argv};
 
     AddLinkedBinding(*test_env, local_linked_napi_id);
 
@@ -172,17 +194,54 @@ TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiInstanceDataTest) {
 
     const char* run_script =
         "process._linkedBinding('local_linked_napi_id').hello";
-    v8::Local<v8::Script> script = v8::Script::Compile(
-        context,
-        v8::String::NewFromOneByte(isolate_,
-                                   reinterpret_cast<const uint8_t*>(run_script))
-                                   .ToLocalChecked())
-        .ToLocalChecked();
+    v8::Local<v8::Script> script =
+        v8::Script::Compile(
+            context,
+            v8::String::NewFromOneByte(
+                isolate_, reinterpret_cast<const uint8_t*>(run_script))
+                .ToLocalChecked())
+            .ToLocalChecked();
     v8::Local<v8::Value> completion_value =
         script->Run(context).ToLocalChecked();
     CHECK(completion_value->IsExternal());
-    instance_data = static_cast<int*>(
-        completion_value.As<v8::External>()->Value());
+    instance_data =
+        static_cast<int*>(completion_value.As<v8::External>()->Value());
+    CHECK_NE(instance_data, nullptr);
+    CHECK_EQ(*instance_data, 0);
+  }
+
+  CHECK_EQ(*instance_data, 1);
+  delete instance_data;
+}
+
+TEST_F(LinkedBindingTest,
+       LocallyDefinedLinkedBindingNapiCallbackInstanceDataTest) {
+  const v8::HandleScope handle_scope(isolate_);
+  int* instance_data = nullptr;
+
+  {
+    const Argv argv;
+    Env test_env{handle_scope, argv};
+
+    AddLinkedBinding(
+        *test_env, "local_linked_napi_id", NapiLinkedWithInstanceData);
+
+    v8::Local<v8::Context> context = isolate_->GetCurrentContext();
+
+    const char* run_script =
+        "process._linkedBinding('local_linked_napi_id').hello";
+    v8::Local<v8::Script> script =
+        v8::Script::Compile(
+            context,
+            v8::String::NewFromOneByte(
+                isolate_, reinterpret_cast<const uint8_t*>(run_script))
+                .ToLocalChecked())
+            .ToLocalChecked();
+    v8::Local<v8::Value> completion_value =
+        script->Run(context).ToLocalChecked();
+    CHECK(completion_value->IsExternal());
+    instance_data =
+        static_cast<int*>(completion_value.As<v8::External>()->Value());
     CHECK_NE(instance_data, nullptr);
     CHECK_EQ(*instance_data, 0);
   }
@@ -194,7 +253,7 @@ TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiInstanceDataTest) {
 TEST_F(LinkedBindingTest, ManyBindingsTest) {
   const v8::HandleScope handle_scope(isolate_);
   const Argv argv;
-  Env test_env {handle_scope, argv};
+  Env test_env{handle_scope, argv};
 
   int calls = 0;
   AddLinkedBinding(*test_env, "local_linked1", InitializeLocalBinding, &calls);
@@ -209,16 +268,16 @@ TEST_F(LinkedBindingTest, ManyBindingsTest) {
   const char* run_script =
       "for (let i = 1; i <= 5; i++)process._linkedBinding(`local_linked${i}`);"
       "process._linkedBinding('local_linked_napi').hello";
-  v8::Local<v8::Script> script = v8::Script::Compile(
-      context,
-      v8::String::NewFromOneByte(isolate_,
-                                 reinterpret_cast<const uint8_t*>(run_script))
-                                 .ToLocalChecked())
-      .ToLocalChecked();
+  v8::Local<v8::Script> script =
+      v8::Script::Compile(
+          context,
+          v8::String::NewFromOneByte(
+              isolate_, reinterpret_cast<const uint8_t*>(run_script))
+              .ToLocalChecked())
+          .ToLocalChecked();
   v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
   v8::String::Utf8Value utf8val(isolate_, completion_value);
   CHECK_NOT_NULL(*utf8val);
   CHECK_EQ(strcmp(*utf8val, "world"), 0);
   CHECK_EQ(calls, 5);
 }
-
diff --git a/test/node-api/test_null_init/test_null_init.c b/test/node-api/test_null_init/test_null_init.c
index d9d2200488ce41..28c283b89240f7 100644
--- a/test/node-api/test_null_init/test_null_init.c
+++ b/test/node-api/test_null_init/test_null_init.c
@@ -1,3 +1,53 @@
 #include <node_api.h>
 
-NAPI_MODULE(NODE_GYP_MODULE_NAME, NULL)
+// This test uses old module initialization style deprecated in current code.
+// The goal is to see that all previously compiled code continues to work the
+// same way as before.
+// The test has a copy of previous macro definitions which are removed from
+// the node_api.h file.
+
+#if defined(_MSC_VER)
+#if defined(__cplusplus)
+#define NAPI_C_CTOR(fn)                                                        \
+  static void NAPI_CDECL fn(void);                                             \
+  namespace {                                                                  \
+  struct fn##_ {                                                               \
+    fn##_() { fn(); }                                                          \
+  } fn##_v_;                                                                   \
+  }                                                                            \
+  static void NAPI_CDECL fn(void)
+#else  // !defined(__cplusplus)
+#pragma section(".CRT$XCU", read)
+// The NAPI_C_CTOR macro defines a function fn that is called during CRT
+// initialization.
+// C does not support dynamic initialization of static variables and this code
+// simulates C++ behavior. Exporting the function pointer prevents it from being
+// optimized. See for details:
+// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170
+#define NAPI_C_CTOR(fn)                                                        \
+  static void NAPI_CDECL fn(void);                                             \
+  __declspec(dllexport, allocate(".CRT$XCU")) void(NAPI_CDECL * fn##_)(void) = \
+      fn;                                                                      \
+  static void NAPI_CDECL fn(void)
+#endif  // defined(__cplusplus)
+#else
+#define NAPI_C_CTOR(fn)                                                        \
+  static void fn(void) __attribute__((constructor));                           \
+  static void fn(void)
+#endif
+
+#define NAPI_MODULE_TEST(modname, regfunc)                                     \
+  EXTERN_C_START                                                               \
+  static napi_module _module = {                                               \
+      NAPI_MODULE_VERSION,                                                     \
+      0,                                                                       \
+      __FILE__,                                                                \
+      regfunc,                                                                 \
+      #modname,                                                                \
+      NULL,                                                                    \
+      {0},                                                                     \
+  };                                                                           \
+  NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); }         \
+  EXTERN_C_END
+
+NAPI_MODULE_TEST(NODE_GYP_MODULE_NAME, NULL)