На этой странице

Цель этого туториала — дать вам хорошее представление о необходимых шагах и доступных инструментах для миграции существующего нативного аддон-модуля Node на NAN в Node-API с помощью пакета node-addon-api.

Этот туториал использует инструмент конвертации, поставляемый с Node-API, чтобы дать вам фору в миграции. Однако инструмент конвертации доведёт вас лишь до определённой точки. Дополнительная ручная доработка всё же потребуется, как описано ниже.

Чтобы держать всё в некоторых рамках, этот туториал использует node-microtime — простой нативный аддон на основе NAN. Этот аддон делает системные вызовы, чтобы определить текущее время с точностью до микросекунд, если это поддерживается операционной системой.

Прежде чем начать, убедитесь, что у вас установлены все необходимые пре-реквизиты и инструменты.

Node-API стабилен во всех поддерживаемых в настоящее время релизах Node.js. Используйте релиз Active LTS или Maintenance LTS. Проверить, какую версию Node.js вы используете, можно командой node -v.

Первым шагом склонируйте GitHub-репозиторий node-microtime на вашу систему:

Прежде чем вносить наши изменения, хорошая идея — сначала собрать и протестировать node-microtime, чтобы убедиться, что необходимые инструменты разработки корректно установлены и настроены.

Поскольку node-microtime уже мигрировал на node-addon-api, вам нужно переключиться на тег v2.1.9, чтобы следовать этому туториалу.

cd node-microtime
git checkout tags/v2.1.9
npm install
npm test

Команда npm install запускает процесс сборки, а npm test выполняет код. Вы можете увидеть предупреждения компилятора, которые не влияют на возможность запуска кода. При успешной сборке и запуске вы должны увидеть вывод примерно такой:

microtime.now() = 1526334357974754
microtime.nowDouble() = 1526334357.976626
microtime.nowStruct() = [ 1526334357, 976748 ]

Guessing clock resolution...
Clock resolution observed: 1us

Как только базовая работа кода проверена, следующий шаг — запустить инструмент конвертации Node-API. Учтите, что инструмент конвертации заменяет файлы на месте. Никогда не запускайте инструмент конвертации на единственной копии вашего проекта. И, очевидно, вы хотите запустить его только один раз.

npm install --save node-addon-api
node ./node_modules/node-addon-api/tools/conversion.js ./

Для этого небольшого проекта инструмент конвертации отрабатывает очень быстро. К этому моменту инструмент конвертации изменил следующие файлы проекта:

  • binding.gyp
  • package.json
  • src/microtime.cc

Пересоберите сконвертированный код:

Как вы увидите, есть одна или несколько ошибок компиляции, которые нужно устранить. Порой их может быть довольно много, но ничего непреодолимого.

Инструмент конвертации не может предусмотреть каждую ситуацию в коде. Так что обычно будут проблемы, которые нужно решить вручную. Ниже — проблемы, с которыми вы, скорее всего, столкнётесь в этом проекте. Лучший подход — решать каждую проблему по одной и пытаться выполнить npm install после устранения каждой, пока ошибок не останется.

Эта ошибка, а также её аналог, где не удаётся найти napi.h, вызвана отсутствием кода в файле binding.gyp. Для этого проекта вы увидите такой код в binding.gyp:

Директории include для C/C++ всё ещё указывают на NAN. Вместо этого они должны указывать на Node-API. Строку выше следует заменить на такую:

В других проектах вы можете получить ошибку, где не удаётся найти napi.h. Причина та же. Свойство include_dirs каждого target'а должно включать ссылку на node-addon-api, как показано выше.

Три C++-функции — Now, NowDouble и NowStruct — каждая ссылается на переменную env, которая не определена. env призвана хранить переменную окружения Node-API, которая существенна почти для всех вызовов Node-API. Значение для env легко получить из аргумента Napi::CallbackInfo, передаваемого каждой C++-функции. Один из способов исправить эту ошибку — добавить следующий код первой строкой в тело каждой функции:

Альтернативой было бы заменить каждое вхождение env в трёх функциях на info.Env(). Выбор за вами.

Каждая из трёх C++-функций — Now, NowDouble и NowStruct — определена как возвращающая значение void. На самом деле каждая из них должна возвращать значение JavaScript. Лучший способ этого добиться — заменить void на Napi::Value. Это позволяет каждой из функций возвращать значение JavaScript неопределённого типа. Это может быть любое значение JavaScript, включая String, Number, Boolean, Array и т. д. Вот как они должны выглядеть:

Napi::Value Now(const Napi::CallbackInfo&info) {
Napi::Value NowDouble(const Napi::CallbackInfo&info) {
Napi::Value NowStruct(const Napi::CallbackInfo&info) {

Node-API использует другую технику определения объекта exports.

Код:

Nan::Export(target, "now", Now);
Nan::Export(target, "nowDouble", NowDouble);
Nan::Export(target, "nowStruct", NowStruct);

Нужно заменить на:

exports.Set(Napi::String::New(env,"now"), Napi::Function::New(env, Now));
exports.Set(Napi::String::New(env,"nowDouble"), Napi::Function::New(env, NowDouble));
exports.Set(Napi::String::New(env,"nowStruct"), Napi::Function::New(env, NowStruct));

exports — это Napi::Object, представляющий JavaScript-объект. Метод Set устанавливает значение свойств объекта и принимает два аргумента: имя свойства и его значение. Оба этих аргумента должны быть значениями JavaScript.

Ещё одно изменение критично для работы Node-API. Функция InitAll обязана возвращать переменную exports. Эта строка должна быть последней в теле функции:

Если не добавить эту строку, это, скорее всего, приведёт к ошибке segfault во время выполнения.

Объекта ErrnoException из NAN в Node-API не существует. Существующий код выглядит так:

Но его легко заменить кодом, который выглядит так:

std::string msg =  "gettimeofday: " + std::string(strerror(errno));
Napi::Error::New(env, msg).ThrowAsJavaScriptException();

Как только код компилируется без ошибок, протестируйте внесённые вами изменения:

Вы должны увидеть результаты, похожие на те, что были до миграции.

Поздравляем! Вы только что сконвертировали свой первый NAN-модуль в Node-API.

Признаться, этот туториал лишь слегка касается миграции NAN-модулей в Node-API. Однако базовый подход тот же. Запустите конвертацию, попробуйте скомпилировать, устраните ошибки, скомпилируйте снова. Намыливаем. Смываем. Повторяем.