C++ エンベダー API#

Node.js は、他の C++ ソフトウェアから Node.js 環境で JavaScript を実行するために使用できる、多くの C++ API を提供しています。

これらの API のドキュメントは、Node.js ソースツリーの src/node.h にあります。Node.js によって公開される API に加えて、いくつかの必須の概念は V8 エンベダー API によって提供されます。

Node.js を組み込みライブラリとして使用することは、Node.js によって実行されるコードを書くこととは異なるため、破壊的変更は Node.js の典型的な非推奨ポリシーに従わず、事前の警告なしに各セマンティックバージョニングのメジャーリリースで行われる可能性があります。

埋め込みアプリケーションの例#

以下のセクションでは、これらの API を使用して node -e <code> と同等の処理を行うアプリケーション、つまり JavaScript コードを受け取って Node.js 固有の環境で実行するアプリケーションをゼロから作成する方法の概要を説明します。

完全なコードはNode.js のソースツリーにあります。

プロセスごとの状態設定#

Node.js を実行するためには、プロセスごとの状態管理がいくつか必要です。

  • Node.js のCLIオプションの引数解析
  • v8::Platform インスタンスなどの V8 のプロセスごとの要件

以下の例は、これらを設定する方法を示しています。いくつかのクラス名は、それぞれ nodev8 の C++ 名前空間のものです。

int main(int argc, char** argv) {
  argv = uv_setup_args(argc, argv);
  std::vector<std::string> args(argv, argv + argc);
  // Parse Node.js CLI options, and print any errors that have occurred while
  // trying to parse them.
  std::unique_ptr<node::InitializationResult> result =
      node::InitializeOncePerProcess(args, {
        node::ProcessInitializationFlags::kNoInitializeV8,
        node::ProcessInitializationFlags::kNoInitializeNodeV8Platform
      });

  for (const std::string& error : result->errors())
    fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
  if (result->early_return() != 0) {
    return result->exit_code();
  }

  // Create a v8::Platform instance. `MultiIsolatePlatform::Create()` is a way
  // to create a v8::Platform instance that Node.js can use when creating
  // Worker threads. When no `MultiIsolatePlatform` instance is present,
  // Worker threads are disabled.
  std::unique_ptr<MultiIsolatePlatform> platform =
      MultiIsolatePlatform::Create(4);
  V8::InitializePlatform(platform.get());
  V8::Initialize();

  // See below for the contents of this function.
  int ret = RunNodeInstance(
      platform.get(), result->args(), result->exec_args());

  V8::Dispose();
  V8::DisposePlatform();

  node::TearDownOncePerProcess();
  return ret;
} 

インスタンスごとの状態設定#

Node.js には「Node.js インスタンス」という概念があり、これは一般的に node::Environment と呼ばれます。各 node::Environment は以下に関連付けられています。

  • ちょうど 1 つの v8::Isolate、つまり 1 つの JS エンジンインスタンス
  • ちょうど 1 つの uv_loop_t、つまり 1 つのイベントループ
  • 複数の v8::Context ですが、メインの v8::Context はちょうど 1 つ
  • 複数の node::Environment で共有可能な情報を含む 1 つの node::IsolateData インスタンス。埋め込み側は、node::IsolateData が同じ v8::Isolate を使用する node::Environment 間でのみ共有されることを保証する必要があります。Node.js はこのチェックを行いません。

v8::Isolate をセットアップするためには、v8::ArrayBuffer::Allocator を提供する必要があります。選択肢の 1 つは、node::ArrayBufferAllocator::Create() を通じて作成できる、Node.js のデフォルトアロケータです。Node.js アロケータを使用すると、アドオンが Node.js C++ の Buffer API を使用する際にわずかなパフォーマンス最適化が可能になり、また process.memoryUsage()ArrayBuffer のメモリを追跡するために必要です。

さらに、Node.js インスタンスに使用される各 v8::Isolate は、MultiIsolatePlatform インスタンスが使用されている場合、プラットフォームが v8::Isolate によってスケジュールされたタスクにどのイベントループを使用するかを把握できるように、そのインスタンスに登録および登録解除される必要があります。

ヘルパー関数 node::NewIsolate()v8::Isolate を作成し、Node.js 固有のフック(例:Node.js のエラーハンドラ)でセットアップし、自動的にプラットフォームに登録します。

int RunNodeInstance(MultiIsolatePlatform* platform,
                    const std::vector<std::string>& args,
                    const std::vector<std::string>& exec_args) {
  int exit_code = 0;

  // Setup up a libuv event loop, v8::Isolate, and Node.js Environment.
  std::vector<std::string> errors;
  std::unique_ptr<CommonEnvironmentSetup> setup =
      CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
  if (!setup) {
    for (const std::string& err : errors)
      fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
    return 1;
  }

  Isolate* isolate = setup->isolate();
  Environment* env = setup->env();

  {
    Locker locker(isolate);
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);
    // The v8::Context needs to be entered when node::CreateEnvironment() and
    // node::LoadEnvironment() are being called.
    Context::Scope context_scope(setup->context());

    // Set up the Node.js instance for execution, and run code inside of it.
    // There is also a variant that takes a callback and provides it with
    // the `require` and `process` objects, so that it can manually compile
    // and run scripts as needed.
    // The `require` function inside this script does *not* access the file
    // system, and can only load built-in Node.js modules.
    // `module.createRequire()` is being used to create one that is able to
    // load files from the disk, and uses the standard CommonJS file loader
    // instead of the internal-only `require` function.
    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())  // There has been a JS exception.
      return 1;

    exit_code = node::SpinEventLoop(env).FromMaybe(1);

    // node::Stop() can be used to explicitly stop the event loop and keep
    // further JavaScript from running. It can be called from any thread,
    // and will act like worker.terminate() if called from another thread.
    node::Stop(env);
  }

  return exit_code;
}