Node.js package using node-addon-api

Ref: https://github.com/nodejs/node-addon-api#api

Installation

mkdir test &&\
cd test &&\
npm init
sudo npm install -g node-gyp
npm install node-addon-api
<package>
├── binding.gyp
├── index.js
├── node_modules
│   └── node-addon-api
│       └── ...
├── package.json
├── package-lock.json
├── test
│   └── ...
├── .clang-format
└── c_src
    ├── funcs.cpp
    ├── funcs.h
    ├── funcs_wrapper.cpp
    ├── funcs_wrapper.h
    └── <target>.cpp

package.json

Ref: https://docs.npmjs.com/files/package.json

{
    "name": "<package>",
...
    "gypfile": true,
...
    "scripts": {
        "install": "node-gyp rebuild",
        "build": "node-gyp rebuild --verbose",
        "clean": "node-gyp clean",
        "cf": "clang-format -style=file -i -verbose c_src/*"
    },
...
    "dependencies": {
        "node-addon-api": "*",
    },
...
}

binding.gyp

Ref: https://github.com/nodejs/node-addon-api/blob/master/doc/setup.md

{
    "targets": [
        {
            "target_name": "<target>",
            "include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
            "dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
            "cflags!": ["-fno-exceptions"],
            "cflags_cc!": ["-fno-exceptions"],
            "cflags": [],
            "cflags_cc": [],
            "defines": [],
            "sources": [
                "c_src/funcs.cpp",
                "c_src/funcs.h",
                "c_src/funcs_wrapper.cpp",
                "c_src/funcs_wrapper.h",
                "c_src/<target>.cpp",
            ],
            "libraries": [
                # "/usr/lib/libxxx.so"
            ]
        }
    ]
}

target_name에 '-' 등은 들어갈 수 없습니다.

index.js

module.exports = require("./build/Release/<target>");

Wrapper

Ref: https://medium.com/@atulanand94/beginners-guide-to-writing-nodejs-addons-using-c-and-n-api-node-addon-api-9b3b718a9a7f

c_src/funcs.h

#pragma once

#include <string>

std::string hello( void );
int         add( int a, int b );

c_src/funcs.cpp

#include "funcs.h"

std::string hello( void )
{
    return "Hello World!";
}

int add( int a, int b )
{
    return a + b;
}

c_src/funcs_wrapper.h

#pragma once

#include <napi.h>

Napi::Object init_funcs( Napi::Env env, Napi::Object exports );

Napi::String hello_wrapper( const Napi::CallbackInfo &info );
Napi::Number add_wrapper( const Napi::CallbackInfo &info );

c_src/funcs_wrapper.cpp

#include "funcs_wrapper.h"
#include "funcs.h"

Napi::Object init_funcs( Napi::Env env, Napi::Object exports )
{
    exports.Set( "hello", Napi::Function::New( env, hello_wrapper ) );
    exports.Set( "add", Napi::Function::New( env, add_wrapper ) );

    return exports;
}

Napi::String hello_wrapper( const Napi::CallbackInfo &info )
{
    Napi::Env env = info.Env();

    return Napi::String::New( env, hello() );
}

Napi::Number add_wrapper( const Napi::CallbackInfo &info )
{
    Napi::Env env = info.Env();

    if( info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber() )
    {
        Napi::TypeError::New(
            env, "Arguments must be (int a, int b) such as (1, 2)." )
            .ThrowAsJavaScriptException();
    }

    // Implicit type conversion
    int a = info[0].As<Napi::Number>();
    int b = info[1].As<Napi::Number>();

    return Napi::Number::New( env, add( a, b ) );
}

c_src/<target>.cpp

#include <napi.h>

#include "funcs_wrapper.h"

Napi::Object init_all( Napi::Env env, Napi::Object exports )
{
    return init_funcs( env, exports );
}

NODE_API_MODULE( NODE_GYP_MODULE_NAME, init_all )

Build

npm run build

test/test.js

const addon = require("../build/Release/<target>");

console.log("wrapper info", addon);

console.log(addon.hello());
console.log(addon.add(1, 2));
node test/test.js
wrapper info { hello: [Function], add: [Function] }
Hello World!
3