API C++ per l'incorporamento
Node.js fornisce una serie di API C++ che possono essere utilizzate per eseguire JavaScript in un ambiente Node.js da altri software C++.
La documentazione di queste API è disponibile in src/node.h nell'albero dei sorgenti di Node.js. Oltre alle API esposte da Node.js, alcuni concetti richiesti sono forniti dall'API di incorporamento V8.
Poiché l'utilizzo di Node.js come libreria incorporata è diverso dalla scrittura di codice eseguito da Node.js, le modifiche che causano interruzioni non seguono la tipica politica di deprecazione di Node.js e possono verificarsi in ogni rilascio semver-major senza preavviso.
Esempio di applicazione di incorporamento
Le seguenti sezioni forniranno una panoramica su come utilizzare queste API per creare un'applicazione da zero che eseguirà l'equivalente di node -e \<code\>
, ovvero che prenderà un pezzo di JavaScript e lo eseguirà in un ambiente specifico di Node.js.
Il codice completo è disponibile nell'albero dei sorgenti di Node.js.
Impostazione di uno stato per processo
Node.js richiede una certa gestione dello stato per processo per poter essere eseguito:
- Analisi degli argomenti per le opzioni CLI di Node.js,
- Requisiti per processo di V8, come un'istanza di
v8::Platform
.
L'esempio seguente mostra come questi possono essere impostati. Alcuni nomi di classe provengono rispettivamente dagli spazi dei nomi C++ node
e v8
.
int main(int argc, char** argv) {
argv = uv_setup_args(argc, argv);
std::vector<std::string> args(argv, argv + argc);
// Analizza le opzioni CLI di Node.js e stampa eventuali errori che si sono verificati durante
// il tentativo di analizzarle.
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();
}
// Crea un'istanza di v8::Platform. `MultiIsolatePlatform::Create()` è un modo
// per creare un'istanza di v8::Platform che Node.js può utilizzare durante la creazione
// di thread Worker. Quando non è presente un'istanza di `MultiIsolatePlatform`,
// i thread Worker sono disabilitati.
std::unique_ptr<MultiIsolatePlatform> platform =
MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();
// Vedere di seguito per il contenuto di questa funzione.
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
Impostazione di uno stato per istanza
[Cronologia]
Versione | Modifiche |
---|---|
v15.0.0 | Sono state aggiunte le utility CommonEnvironmentSetup e SpinEventLoop . |
Node.js ha un concetto di "istanza Node.js", che viene comunemente indicato come node::Environment
. Ogni node::Environment
è associato a:
- Esattamente un
v8::Isolate
, ovvero un'istanza di JS Engine, - Esattamente un
uv_loop_t
, ovvero un ciclo di eventi, - Un numero di
v8::Context
, ma esattamente unv8::Context
principale, e - Un'istanza
node::IsolateData
che contiene informazioni che potrebbero essere condivise da piùnode::Environment
. L'embedder deve assicurarsi chenode::IsolateData
sia condiviso solo tranode::Environment
che utilizzano lo stessov8::Isolate
, Node.js non esegue questo controllo.
Per impostare un v8::Isolate
, è necessario fornire un v8::ArrayBuffer::Allocator
. Una possibile scelta è l'allocatore predefinito di Node.js, che può essere creato tramite node::ArrayBufferAllocator::Create()
. L'utilizzo dell'allocatore Node.js consente piccole ottimizzazioni delle prestazioni quando gli addon utilizzano l'API C++ Buffer
di Node.js ed è necessario per tracciare la memoria ArrayBuffer
in process.memoryUsage()
.
Inoltre, ogni v8::Isolate
utilizzato per un'istanza Node.js deve essere registrato e deregistrato con l'istanza MultiIsolatePlatform
, se ne viene utilizzata una, affinché la piattaforma sappia quale ciclo di eventi utilizzare per le attività pianificate da v8::Isolate
.
La funzione helper node::NewIsolate()
crea un v8::Isolate
, lo imposta con alcuni hook specifici di Node.js (ad esempio, il gestore degli errori di Node.js) e lo registra automaticamente con la piattaforma.
int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
// Imposta un ciclo di eventi libuv, v8::Isolate e 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);
// Il v8::Context deve essere inserito quando vengono chiamati node::CreateEnvironment() e
// node::LoadEnvironment().
Context::Scope context_scope(setup->context());
// Imposta l'istanza Node.js per l'esecuzione ed esegue il codice al suo interno.
// Esiste anche una variante che accetta un callback e gli fornisce
// gli oggetti `require` e `process`, in modo che possa compilare manualmente
// ed eseguire script secondo necessità.
// La funzione `require` all'interno di questo script *non* accede al file
// system e può caricare solo i moduli Node.js integrati.
// `module.createRequire()` viene utilizzato per crearne uno in grado di
// caricare file dal disco e utilizza il loader di file CommonJS standard
// invece della funzione `require` solo 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()) // Si è verificata un'eccezione JS.
return 1;
exit_code = node::SpinEventLoop(env).FromMaybe(1);
// node::Stop() può essere utilizzato per arrestare esplicitamente il ciclo di eventi e impedire
// l'ulteriore esecuzione di JavaScript. Può essere chiamato da qualsiasi thread,
// e si comporterà come worker.terminate() se chiamato da un altro 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