如何把 Node.js 嵌入自己的项目中

Node.js 不仅可以单独运行,还可以以库的方式被使用,本文介绍下如何把 Node.js 嵌入到自己项目中。
首先第一步下载 Node.js 源码,然后根据 Node.js 的文档进行编译安装。这样我们就可以拿到 Node.js 提供的头文件和库文件了。接下来根据官方的 demo 写一个测试程序。

#include "node.h"
#include "uv.h"
#include <assert.h>

using node::CommonEnvironmentSetup;
using node::Environment;
using node::MultiIsolatePlatform;
using v8::Context;
using v8::HandleScope;
using v8::Isolate;
using v8::Locker;
using v8::MaybeLocal;
using v8::V8;
using v8::Value;

static int RunNodeInstance(MultiIsolatePlatform* platform,
                           const std::vector<std::string>& args,
                           const std::vector<std::string>& exec_args);

int main(int argc, char** argv) {
  argv = uv_setup_args(argc, argv);
  std::vector<std::string> args(argv, argv + argc);
  std::vector<std::string> exec_args;
  std::vector<std::string> errors;
  int exit_code = node::InitializeNodeWithArgs(&args, &exec_args, &errors);
  for (const std::string& error : errors)
    fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
  if (exit_code != 0) {
    return exit_code;
  }

  std::unique_ptr<MultiIsolatePlatform> platform =
      MultiIsolatePlatform::Create(4);
  V8::InitializePlatform(platform.get());
  V8::Initialize();

  int ret = RunNodeInstance(platform.get(), args, exec_args);

  V8::Dispose();
//   V8::DisposePlatform();
  return ret;
}

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

  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);
    Context::Scope context_scope(setup->context());

    MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
        env,
        "const publicRequire ="
    "  require('module').createRequire(process.cwd() + '/');"
        "globalThis.require = publicRequire;"
        "publicRequire('./test')");

    if (loadenv_ret.IsEmpty())  // There has been a JS exception.
      return 1;

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

    node::Stop(env);
  }

  return exit_code;
}

大部分都是默认的流程,我们可以先不用关注,重点是 LoadEnvironment 函数的逻辑。LoadEnvironment 最后会执行我们传入的字符串代码。这段代码中,前面是 Node.js 提供的 demo,后面一句是我加的,test.js 里简单输出 hello world。下面来编译一下。

g++ Node.cc src/node_code_cache_stub.cc src/node_snapshot_stub.cc -I/node/v17.9.0/include/node -std=c++14 -L /node/out/Release -l v8_base_without_compiler -l v8_compiler -l v8_init -l v8_initializers -l v8_libbase -l v8_libplatform -l v8_snapshot -l brotli -l cares -l gtest -l gtest_main -l histogram -l icudata -l icui18n -l icutools -l icuucx -l llhttp -l nghttp2 -l nghttp3 -l ngtcp2  -l openssl -l torque_base -l uv -l uvwasi -l v8_zlib -l zlib -l pthread -l node

因为 code cache 和 快照函数的符号找不到的问题,这里先曲线救国一下,从 Node.js 源码里引入这两个文件,后续再去研究具体方案。编译完后就拿到了一个 a.out 文件,执行该文件就可以看到输出 hello world。cool,我们已经实现了把 Node.js 嵌入到我们的项目。下面具体来看一下涉及到的一些逻辑。从 LoadEnvironment 看起。

MaybeLocal<Value> LoadEnvironment(
    Environment* env,
    const char* main_script_source_utf8) {
  Isolate* isolate = env->isolate();
  return LoadEnvironment(
      env,
      [&](const StartExecutionCallbackInfo& info) -> MaybeLocal<Value> {
        // 一会分析
      });
}

LoadEnvironment 进一步调了另一个 LoadEnvironment。

MaybeLocal<Value> LoadEnvironment(
    Environment* env,
    StartExecutionCallback cb) {
  env->InitializeLibuv();
  env->InitializeDiagnostics();
  return StartExecution(env, cb);
}

LoadEnvironment 进行了一些初始化,接着调 StartExecution。

MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
  InternalCallbackScope callback_scope(
      env,
      Object::New(env->isolate()),
      { 1, 0 },
      InternalCallbackScope::kSkipAsyncHooks);

  if (cb != nullptr) {
    EscapableHandleScope scope(env->isolate());

    if (StartExecution(env, "internal/bootstrap/environment").IsEmpty())
      return {};

    StartExecutionCallbackInfo info = {
      env->process_object(),
      env->native_module_require(),
    };

    return scope.EscapeMaybe(cb(info));
  }
}

StartExecution 最终执行了 第一个 LoadEnvironment 传入到回调,并传入了 process 和原生 JS 模块加载器。接着看回调函数的逻辑。

	[&](const StartExecutionCallbackInfo& info) -> MaybeLocal<Value> {
		// main_script_source_utf8 为我们传入到字符串代码
        Local<String> str =
            String::NewFromUtf8(isolate,
                                main_script_source_utf8).ToLocalChecked();
        auto main_utf16 = std::make_unique<String::Value>(isolate, str);

        std::string name = "embedder_main_" + std::to_string(env->thread_id());
        // 插入原生 JS 模块代码中
        native_module::NativeModuleEnv::Add(
            name.c_str(),
            UnionBytes(**main_utf16, main_utf16->length()));
        env->set_main_utf16(std::move(main_utf16));
        std::vector<Local<String>> params = {
            env->process_string(),
            env->require_string()};
        std::vector<Local<Value>> args = {
            env->process_object(),
            env->native_module_require()};
        // 执行我们的代码    
        return ExecuteBootstrapper(env, name.c_str(), &params, &args);
      }

回调函数通过 ExecuteBootstrapper 执行我们传入的代码。

MaybeLocal<Value> ExecuteBootstrapper(Environment* env,
                                      const char* id,
                                      std::vector<Local<String>>* parameters,
                                      std::vector<Local<Value>>* arguments) {
  EscapableHandleScope scope(env->isolate());
  // 从原生 JS 模块代码中找到我们的代码
  MaybeLocal<Function> maybe_fn =
      NativeModuleEnv::LookupAndCompile(env->context(), id, parameters, env);

  Local<Function> fn;
  if (!maybe_fn.ToLocal(&fn)) {
    return MaybeLocal<Value>();
  }
  // 执行我们的代码
  MaybeLocal<Value> result = fn->Call(env->context(),
                                      Undefined(env->isolate()),
                                      arguments->size(),
                                      arguments->data());
}

再回过头来看看我们的代码。

const publicRequire = require('module').createRequire(process.cwd() + '/');
globalThis.require = publicRequire;
publicRequire('./test');

require 函数是原生 JS 模块加载器,可以用来加载 Node.js 原生 JS 模块。通过 module 模块可以创建一个用户 JS 模块加载器。通过用户 JS 模块加载器,我们就可以把我们的代码串起来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值