Node.js предоставляет ряд C++ API для выполнения JavaScript в среде Node.js из другого ПО на C++.
Документация по этим API находится в файле src/node.h в дереве исходного кода Node.js. Помимо API Node.js, часть необходимых понятий задаётся API встраивания V8.
Поскольку использование Node.js как встраиваемой библиотеки отличается от кода, который запускает сам Node.js, несовместимые изменения не следуют обычной политике устаревания Node.js и могут появляться в каждом мажорном релизе semver без предварительного предупреждения.
В следующих разделах кратко описано, как с помощью этих API с нуля собрать приложение, эквивалентное node -e <code>, то есть принимающее фрагмент JavaScript и выполняющее его в среде, характерной для Node.js.
intmain(intargc,char**argv){argv=uv_setup_args(argc,argv);std::vector<std::string>args(argv,argv+argc);// Разбор опций CLI Node.js и вывод ошибок, возникших при разборе.std::unique_ptr<node::InitializationResult>result=node::InitializeOncePerProcess(args,{node::ProcessInitializationFlags::kNoInitializeV8,node::ProcessInitializationFlags::kNoInitializeNodeV8Platform});for(conststd::string&error:result->errors())fprintf(stderr,"%s: %s\n",args[0].c_str(),error.c_str());if(result->early_return()!=0){returnresult->exit_code();}// Создать экземпляр v8::Platform. MultiIsolatePlatform::Create() —// способ получить v8::Platform, который Node.js использует при создании// потоков Worker. Без экземпляра MultiIsolatePlatform потоки Worker отключены.std::unique_ptr<MultiIsolatePlatform>platform=MultiIsolatePlatform::Create(4);V8::InitializePlatform(platform.get());V8::Initialize();// Содержимое этой функции — ниже.intret=RunNodeInstance(platform.get(),result->args(),result->exec_args());V8::Dispose();V8::DisposePlatform();node::TearDownOncePerProcess();returnret;}
Добавлены утилиты CommonEnvironmentSetup и SpinEventLoop.
В Node.js есть понятие «экземпляр Node.js», обычно обозначаемый как node::Environment. С каждым node::Environment связано:
ровно один v8::Isolate (один экземпляр JS-движка);
ровно один uv_loop_t (один цикл событий);
несколько v8::Context, но ровно один основной v8::Context;
один node::IsolateData с данными, которые могут разделяться несколькими node::Environment. Встраивающий код должен гарантировать, что node::IsolateData разделяется только между node::Environment, использующими один и тот же v8::Isolate; Node.js эту проверку не выполняет.
Чтобы настроить v8::Isolate, нужен v8::ArrayBuffer::Allocator. Вариант по умолчанию — аллокатор Node.js через node::ArrayBufferAllocator::Create(). Он даёт небольшой выигрыш в производительности, когда аддоны используют C++ API Buffer Node.js, и нужен для учёта памяти ArrayBuffer в process.memoryUsage().
Кроме того, каждый v8::Isolate, используемый экземпляром Node.js, нужно регистрировать и снимать с регистрации на MultiIsolatePlatform, если он используется, чтобы платформа знала, какой цикл событий применять для задач, планируемых этим v8::Isolate.
Вспомогательная функция node::NewIsolate() создаёт v8::Isolate, настраивает его хуками Node.js (включая обработчик ошибок Node.js) и регистрирует на платформе автоматически.
intRunNodeInstance(MultiIsolatePlatform*platform,conststd::vector<std::string>&args,conststd::vector<std::string>&exec_args){intexit_code=0;// Цикл libuv, v8::Isolate и окружение Node.js.std::vector<std::string>errors;std::unique_ptr<CommonEnvironmentSetup>setup=CommonEnvironmentSetup::Create(platform,&errors,args,exec_args);if(!setup){for(conststd::string&err:errors)fprintf(stderr,"%s: %s\n",args[0].c_str(),err.c_str());return1;}Isolate*isolate=setup->isolate();Environment*env=setup->env();{Lockerlocker(isolate);Isolate::Scopeisolate_scope(isolate);HandleScopehandle_scope(isolate);// v8::Context нужно войти при вызовах node::CreateEnvironment() и node::LoadEnvironment().Context::Scopecontext_scope(setup->context());// Настроить экземпляр Node.js и выполнить код.// Есть вариант с колбэком, которому передаются объекты `require` и `process`// для ручной компиляции и запуска скриптов.// `require` в этом скрипте *не* обращается к файловой системе и может подгружать// только встроенные модули Node.js.// `module.createRequire()` используется, чтобы создать функцию, которая может// загружать файлы с диска стандартным загрузчиком CommonJS вместо внутреннего `require`.MaybeLocal<Value>loadenv_ret=node::LoadEnvironment(env,"const publicRequire ="" require('node:module').createRequire(process.cwd() + '/');""globalThis.require = publicRequire;""require('node:vm').runInThisContext(process.argv[1]);");if(loadenv_ret.IsEmpty())// Произошло исключение JavaScript.return1;exit_code=node::SpinEventLoop(env).FromMaybe(1);// node::Stop() можно вызвать, чтобы явно остановить цикл событий и не давать// дальше выполнять JavaScript. Вызов допустим с любого потока и ведёт себя// как worker.terminate(), если вызван не из основного потока воркера.node::Stop(env);}returnexit_code;}