API do incorporador C++
O Node.js fornece várias APIs C++ que podem ser usadas para executar JavaScript em um ambiente Node.js a partir de outro software C++.
A documentação dessas APIs pode ser encontrada em src/node.h na árvore de origem do Node.js. Além das APIs expostas pelo Node.js, alguns conceitos necessários são fornecidos pela API do incorporador V8.
Como usar o Node.js como uma biblioteca incorporada é diferente de escrever código que é executado pelo Node.js, as alterações interruptivas não seguem a política de depreciação típica do Node.js e podem ocorrer em cada versão semver-major sem aviso prévio.
Exemplo de aplicação de incorporação
As seções a seguir fornecerão uma visão geral de como usar essas APIs para criar um aplicativo do zero que executará o equivalente a node -e \<code\>
, ou seja, que pegará um pedaço de JavaScript e o executará em um ambiente específico do Node.js.
O código completo pode ser encontrado na árvore de origem do Node.js.
Configurando um estado por processo
O Node.js requer algum gerenciamento de estado por processo para ser executado:
- Análise de argumentos para opções CLI do Node.js,
- Requisitos por processo V8, como uma instância
v8::Platform
.
O exemplo a seguir mostra como eles podem ser configurados. Alguns nomes de classe são dos namespaces C++ node
e v8
, respectivamente.
int main(int argc, char** argv) {
argv = uv_setup_args(argc, argv);
std::vector<std::string> args(argv, argv + argc);
// Analisa as opções CLI do Node.js e imprime quaisquer erros que ocorreram ao
// tentar analisá-las.
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();
}
// Cria uma instância v8::Platform. `MultiIsolatePlatform::Create()` é uma maneira
// de criar uma instância v8::Platform que o Node.js pode usar ao criar
// threads Worker. Quando nenhuma instância `MultiIsolatePlatform` está presente,
// as threads Worker são desabilitadas.
std::unique_ptr<MultiIsolatePlatform> platform =
MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();
// Veja abaixo o conteúdo desta função.
int ret = RunNodeInstance(
platform.get(), result->args(), result->exec_args());
V8::Dispose();
V8::DisposePlatform();
node::TearDownOncePerProcess();
return ret;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Configurando um estado por instância
[Histórico]
Versão | Alterações |
---|---|
v15.0.0 | Os utilitários CommonEnvironmentSetup e SpinEventLoop foram adicionados. |
O Node.js tem um conceito de "instância Node.js", que é comumente referida como node::Environment
. Cada node::Environment
está associado a:
- Exatamente um
v8::Isolate
, ou seja, uma instância do motor JS, - Exatamente um
uv_loop_t
, ou seja, um loop de eventos, - Um número de
v8::Context
s, mas exatamente umv8::Context
principal, e - Uma instância
node::IsolateData
que contém informações que podem ser compartilhadas por váriosnode::Environment
s. O incorporador deve garantir que onode::IsolateData
seja compartilhado apenas entrenode::Environment
s que usam o mesmov8::Isolate
, o Node.js não realiza esta verificação.
Para configurar um v8::Isolate
, um v8::ArrayBuffer::Allocator
precisa ser fornecido. Uma escolha possível é o alocador Node.js padrão, que pode ser criado através de node::ArrayBufferAllocator::Create()
. Usar o alocador Node.js permite pequenas otimizações de desempenho quando os addons usam a API C++ Buffer
do Node.js, e é necessário para rastrear a memória ArrayBuffer
em process.memoryUsage()
.
Além disso, cada v8::Isolate
que é usado para uma instância Node.js precisa ser registrado e desregistrado com a instância MultiIsolatePlatform
, se uma estiver sendo usada, para que a plataforma saiba qual loop de eventos usar para tarefas agendadas pelo v8::Isolate
.
A função auxiliar node::NewIsolate()
cria um v8::Isolate
, configura-o com alguns hooks específicos do Node.js (por exemplo, o manipulador de erros Node.js) e o registra na plataforma automaticamente.
int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
// Configurar um loop de eventos libuv, v8::Isolate e Ambiente Node.js.
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);
// O v8::Context precisa ser inserido quando node::CreateEnvironment() e
// node::LoadEnvironment() estão sendo chamados.
Context::Scope context_scope(setup->context());
// Configurar a instância Node.js para execução e executar o código dentro dela.
// Há também uma variante que recebe um callback e fornece os objetos
// `require` e `process`, para que ele possa compilar e executar scripts
// manualmente, conforme necessário.
// A função `require` dentro deste script *não* acessa o sistema de arquivos
// e só pode carregar módulos Node.js embutidos.
// `module.createRequire()` está sendo usado para criar um que é capaz de
// carregar arquivos do disco e usa o carregador de arquivos CommonJS padrão
// em vez da função `require` apenas interna.
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()) // Houve uma exceção JS.
return 1;
exit_code = node::SpinEventLoop(env).FromMaybe(1);
// node::Stop() pode ser usado para parar explicitamente o loop de eventos
// e impedir que mais JavaScript seja executado. Ele pode ser chamado de
// qualquer thread e agirá como worker.terminate() se chamado de outra thread.
node::Stop(env);
}
return exit_code;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55