Skip to content

Commit

Permalink
feat: Node-API migration (#2305)
Browse files Browse the repository at this point in the history
Migrate from NAN to N-API - this should allow an easier time working with electron and all versions of nodejs

* Change dependency to napi
* include napi
* update shared functionality to NAPI
* update unix methods to include napi_env
* Convert OpenBaton to AsyncWorker
* SetError within Execute()
* Replace napi_create_async_work for common methods
* remove redundant ByteSize Setting
* fix typo
* NODE_ADDON_API_ENABLE_MAYBE
* optional - remove warnings
* Remove OnError logic

BREAKING CHANGE: This release switches to NAPI which changes how many binaries are released and will potentially break your build system
  • Loading branch information
GazHank authored and reconbot committed Dec 11, 2021
1 parent e90a432 commit 2fe7d43
Show file tree
Hide file tree
Showing 18 changed files with 3,231 additions and 4,847 deletions.
5,751 changes: 2,392 additions & 3,359 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@
},
"devDependencies": {
"cc": "^3.0.1",
"chai-subset": "^1.6.0",
"chai": "^4.3.4",
"chai-subset": "^1.5.0",
"codecov": "^3.8.3",
"eslint": "^8.2.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-mocha": "^9.0.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^5.1.1",
"lerna": "^4.0.0",
"eslint": "^8.2.0",
"lerna-changelog": "^2.2.0",
"lerna": "^4.0.0",
"mocha": "^9.1.3",
"node-abi": "3.5.0",
"node-addon-api": "^4.2.0",
"nyc": "^15.1.0",
"plop": "^2.7.6",
"prebuild": "^11.0.0",
Expand Down
8 changes: 0 additions & 8 deletions packages/binding-mock/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions packages/bindings/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
'sources': [
'src/serialport.cpp'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
],
'include_dirs': ["<!(node -p \"require('node-addon-api').include_dir\")"],
'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
"defines": ["NAPI_CPP_EXCEPTIONS"],
'conditions': [
['OS=="win"',
{
Expand All @@ -16,7 +18,7 @@
],
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': '2',
'ExceptionHandling': '1',
'DisableSpecificWarnings': [ '4530', '4506' ],
}
}
Expand All @@ -30,6 +32,7 @@
'src/darwin_list.cpp'
],
'xcode_settings': {
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'MACOSX_DEPLOYMENT_TARGET': '10.9',
'OTHER_LDFLAGS': [
'-framework CoreFoundation -framework IOKit'
Expand Down
24 changes: 12 additions & 12 deletions packages/bindings/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions packages/bindings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"@serialport/parser-readline": "9.2.4",
"bindings": "^1.5.0",
"debug": "^4.3.2",
"nan": "^2.15.0",
"prebuild-install": "^7.0.0"
"prebuild-install": "^7.0.0",
"node-addon-api": "4.2.0"
},
"devDependencies": {
"@serialport/binding-mock": "9.2.4",
Expand All @@ -21,11 +21,9 @@
"node": ">=10.0.0"
},
"scripts": {
"install": "prebuild-install --tag-prefix @serialport/bindings@ || node-gyp rebuild",
"install": "prebuild-install --tag-prefix @serialport/bindings@ --runtime napi --target 4 --verbose || node-gyp rebuild",
"lint": "cc --verbose",
"prebuild": "npm run prebuild-node && npm run prebuild-electron",
"prebuild-node": "prebuild --force --strip --verbose --tag-prefix @serialport/bindings@ -t 12.0.0 -t 14.0.0 -t 16.0.0",
"prebuild-electron": "prebuild --force --strip --verbose --tag-prefix @serialport/bindings@ -r electron -t 13.0.0 -t 14.0.0 -t 15.0.0 -t 16.0.0",
"prebuild": "prebuild --runtime napi --target 4 --force --strip --verbose --tag-prefix @serialport/bindings@",
"rebuild": "node-gyp rebuild"
},
"publishConfig": {
Expand All @@ -48,5 +46,10 @@
],
"linelength": "120"
},
"funding": "https://opencollective.com/serialport/donate"
"funding": "https://opencollective.com/serialport/donate",
"binary": {
"napi_versions": [
4
]
}
}
70 changes: 16 additions & 54 deletions packages/bindings/src/darwin_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,28 @@
uv_mutex_t list_mutex;
Boolean lockInitialised = FALSE;

NAN_METHOD(List) {
Napi::Value List(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// callback
if (!info[0]->IsFunction()) {
Nan::ThrowTypeError("First argument must be a function");
return;
if (!info[0].IsFunction()) {
Napi::TypeError::New(env, "First argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}

ListBaton* baton = new ListBaton();
Napi::Function callback = info[0].As<Napi::Function>();
ListBaton* baton = new ListBaton(callback);
snprintf(baton->errorString, sizeof(baton->errorString), "");
baton->callback.Reset(info[0].As<v8::Function>());

uv_work_t* req = new uv_work_t();
req->data = baton;
uv_queue_work(uv_default_loop(), req, EIO_List, (uv_after_work_cb)EIO_AfterList);
baton->Queue();
return env.Undefined();
}

void setIfNotEmpty(v8::Local<v8::Object> item, std::string key, const char *value) {
v8::Local<v8::String> v8key = Nan::New<v8::String>(key).ToLocalChecked();
void setIfNotEmpty(Napi::Object item, std::string key, const char *value) {
Napi::Env env = item.Env();
Napi::String v8key = Napi::String::New(env, key);
if (strlen(value) > 0) {
Nan::Set(item, v8key, Nan::New<v8::String>(value).ToLocalChecked());
(item).Set(v8key, Napi::String::New(env, value));
} else {
Nan::Set(item, v8key, Nan::Undefined());
(item).Set(v8key, env.Undefined());
}
}

Expand Down Expand Up @@ -271,8 +271,7 @@ static stDeviceListItem* GetSerialDevices() {
return devices;
}

void EIO_List(uv_work_t* req) {
ListBaton* data = static_cast<ListBaton*>(req->data);
void ListBaton::Execute() {

if (!lockInitialised) {
uv_mutex_init(&list_mutex);
Expand Down Expand Up @@ -304,7 +303,7 @@ void EIO_List(uv_work_t* req) {
if (*device.serialNumber) {
resultItem->serialNumber = device.serialNumber;
}
data->results.push_back(resultItem);
results.push_back(resultItem);

stDeviceListItem* current = next;

Expand All @@ -316,40 +315,3 @@ void EIO_List(uv_work_t* req) {
}
}
}

void EIO_AfterList(uv_work_t* req) {
Nan::HandleScope scope;

ListBaton* data = static_cast<ListBaton*>(req->data);

v8::Local<v8::Value> argv[2];
if (data->errorString[0]) {
argv[0] = v8::Exception::Error(Nan::New<v8::String>(data->errorString).ToLocalChecked());
argv[1] = Nan::Undefined();
} else {
v8::Local<v8::Array> results = Nan::New<v8::Array>();
int i = 0;
for (std::list<ListResultItem*>::iterator it = data->results.begin(); it != data->results.end(); ++it, i++) {
v8::Local<v8::Object> item = Nan::New<v8::Object>();

setIfNotEmpty(item, "path", (*it)->path.c_str());
setIfNotEmpty(item, "manufacturer", (*it)->manufacturer.c_str());
setIfNotEmpty(item, "serialNumber", (*it)->serialNumber.c_str());
setIfNotEmpty(item, "pnpId", (*it)->pnpId.c_str());
setIfNotEmpty(item, "locationId", (*it)->locationId.c_str());
setIfNotEmpty(item, "vendorId", (*it)->vendorId.c_str());
setIfNotEmpty(item, "productId", (*it)->productId.c_str());

Nan::Set(results, i, item);
}
argv[0] = Nan::Null();
argv[1] = results;
}
data->callback.Call(2, argv, data);

for (std::list<ListResultItem*>::iterator it = data->results.begin(); it != data->results.end(); ++it) {
delete *it;
}
delete data;
delete req;
}
38 changes: 30 additions & 8 deletions packages/bindings/src/darwin_list.h
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#ifndef PACKAGES_SERIALPORT_SRC_DARWIN_LIST_H_
#define PACKAGES_SERIALPORT_SRC_DARWIN_LIST_H_
#include <sys/param.h> // For MAXPATHLEN
#include <nan.h>
#include <napi.h>
#include <uv.h>
#include <list>
#include <string>

#define ERROR_STRING_SIZE 1024
#define ERROR_STRING_SIZE 1088

NAN_METHOD(List);
void EIO_List(uv_work_t* req);
void EIO_AfterList(uv_work_t* req);
Napi::Value List(const Napi::CallbackInfo& info);
void setIfNotEmpty(Napi::Object item, std::string key, const char *value);

struct ListResultItem {
std::string path;
Expand All @@ -21,11 +21,33 @@ struct ListResultItem {
std::string productId;
};

struct ListBaton : public Nan::AsyncResource {
ListBaton() : AsyncResource("node-serialport:ListBaton"), errorString() {}
Nan::Callback callback;
struct ListBaton : public Napi::AsyncWorker {
ListBaton(Napi::Function& callback) : Napi::AsyncWorker(callback, "node-serialport:ListBaton"),
errorString() {}
std::list<ListResultItem*> results;
char errorString[ERROR_STRING_SIZE];
void Execute() override;

void OnOK() override {
Napi::Env env = Env();
Napi::HandleScope scope(env);
Napi::Array result = Napi::Array::New(env);
int i = 0;
for (std::list<ListResultItem*>::iterator it = results.begin(); it != results.end(); ++it, i++) {
Napi::Object item = Napi::Object::New(env);

setIfNotEmpty(item, "path", (*it)->path.c_str());
setIfNotEmpty(item, "manufacturer", (*it)->manufacturer.c_str());
setIfNotEmpty(item, "serialNumber", (*it)->serialNumber.c_str());
setIfNotEmpty(item, "pnpId", (*it)->pnpId.c_str());
setIfNotEmpty(item, "locationId", (*it)->locationId.c_str());
setIfNotEmpty(item, "vendorId", (*it)->vendorId.c_str());
setIfNotEmpty(item, "productId", (*it)->productId.c_str());

(result).Set(i, item);
}
Callback().Call({env.Null(), result});
}
};

typedef struct SerialDevice {
Expand Down

0 comments on commit 2fe7d43

Please sign in to comment.