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

开发 项目管理
require 函数是原生 JS 模块加载器,可以用来加载 Node.js 原生 JS 模块。通过 module 模块可以创建一个用户 JS 模块加载器。通过用户 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 模块加载器。接着看回调函数的逻辑。

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 模块加载器,我们就可以把我们的代码串起来了。

责任编辑:武晓燕 来源: 编程杂技
相关推荐

2011-09-09 14:23:13

Node.js

2021-05-27 09:00:00

Node.js开发线程

2022-09-04 15:54:10

Node.jsAPI技巧

2013-12-13 09:42:30

Node.js技术

2020-11-11 10:09:06

Node.jsPromise前端

2015-03-10 10:59:18

Node.js开发指南基础介绍

2013-11-01 09:34:56

Node.js技术

2020-05-29 15:33:28

Node.js框架JavaScript

2021-12-25 22:29:57

Node.js 微任务处理事件循环

2020-01-03 16:04:10

Node.js内存泄漏

2023-10-04 07:35:03

2023-03-02 23:09:53

Node.jsC++JS

2012-02-03 09:25:39

Node.js

2021-07-30 11:20:53

JavaScriptNode.jsWeb Develop

2022-08-22 07:26:32

Node.js微服务架构

2021-03-09 08:03:21

Node.js 线程JavaScript

2011-09-08 13:46:14

node.js

2011-11-01 10:30:36

Node.js

2011-09-02 14:47:48

Node

2012-10-24 14:56:30

IBMdw
点赞
收藏

51CTO技术栈公众号