Skip to content

Commit

Permalink
Update NodeEmbedding docs (#435)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmoroz authored Mar 5, 2025
1 parent 7610664 commit bd029e4
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 50 deletions.
2 changes: 1 addition & 1 deletion docs/features/js-threading-async.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ JavaScript engines have a single-threaded execution model. That means all access
data or operations must be performed from the JavaScript thread.
(It is possible run multiple JavaScript execution threads in a process using
[Web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) or the
[`NodejsEnvironment`](../reference/dotnet/Microsoft.JavaScript.NodeApi.Runtime/NodejsEnvironment)
[`NodeEmbeddingRuntime`](../reference/dotnet/Microsoft.JavaScript.NodeApi.Runtime/NodeEmbeddingRuntime)
class, but the thread affinity rules still apply.)

## Invalid thread access
Expand Down
89 changes: 40 additions & 49 deletions docs/scenarios/dotnet-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,90 +5,81 @@ This functionality is still experimental. It works, but the process is not as st
should be.
:::

## Building the required `libnode` binary
This project depends on a [PR to Node.js](https://github.com/nodejs/node/pull/43542) that adds
simpler ABI-stable embedding APIs to `libnode`. Until that PR is merged, you'll need to build your
own binary from a branch. And even after it's merged, the Node.js project does not currently and
has no plans to publish official `libnode` binaries.

1. Install the [prerequisites for building Node.js](https://github.com/nodejs/node/blob/main/BUILDING.md#building-nodejs-on-supported-platforms).
(On Windows this is basically Python 3 and either VS 2022 or the C++ build tools.)

2. Clone the napi-libnode repo/branch:

```shell
mkdir libnode
cd libnode
git clone https://github.com/jasongin/nodejs -b napi-libnode-v20.9.0 --single-branch .
```

3. Build in shared-library mode:
::: code-group
```shell [Windows]
.\vcbuild.bat x64 release dll openssl-no-asm
```
```shell [Mac / Linux]
./configure --shared; make -j4
```
:::
The build may take an hour or more depending on the speed of your system.

4. A successful build produces a binary in the `out/Release` directory:
- `libnode.dll` (Windows)
- `libnode.dylib` (Mac)
- `libnode.???.so` (Linux)
## Acquiring the the required `libnode` binary
This project depends on a [PR to Node.js](https://github.com/nodejs/node/pull/54660) that adds
simpler ABI-stable embedding APIs to `libnode`. Until that PR is merged and the Node.js project
starts building shared `libnode`, we offer the
[`Microsoft.JavaScript.LibNode](https://www.nuget.org/packages/Microsoft.JavaScript.LibNode) NuGet
package that installs pre-built `libnode` for Windows, MacOSX, and Linux (Ubuntu). This package
depends on the runtime ID specific NuGet packages which can be used directly if needed.

Since the PR for the ABI-stable embedding API is still work in progress, the built `libnode`
will have breaking changes between versions. See the `Directory.Packages.props` file in the
root of the `node-api-dotnet` project for the matching version of the `Microsoft.JavaScript.LibNode`
package.

## Importing JS modules into .NET

1. Load `libnode` and initialize a Node.js "platform" and "environment" instance:
1. Load `libnode` and initialize a Node.js "platform" and "runtime" instance:
```C#
// Find the path to the libnode binary for the current platform.
string baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
string libnodePath = Path.Combine(baseDir, "libnode.dll");
NodejsPlatform nodejsPlatform = new(libnodePath);
var nodejs = nodejsPlatform.CreateEnvironment(baseDir);
NodeEmbeddingPlatform nodejsPlatform = new(libnodePath, null);
NodeEmbeddingThreadRuntime nodejsRuntime = nodejsPlatform.CreateThreadRuntime(baseDir,
new NodeEmbeddingRuntimeSettings
{
MainScript =
"globalThis.require = require('module').createRequire(process.execPath);\n"
});
```

There can only be one `NodejsPlatform` instance per process, so typically it would be stored
in a static variable. From the platform, multiple `NodejsEnvironment` instances may be created
and disposed.
There can only be one
[`NodeEmbeddingPlatform`](../reference/dotnet/Microsoft.JavaScript.NodeApi.Runtime/NodeEmbeddingPlatform)
instance per process, so typically it would be stored in a static variable. From the platform,
multiple
[`NodeEmbeddingRuntime`](../reference/dotnet/Microsoft.JavaScript.NodeApi.Runtime/NodeEmbeddingRuntime)
or
[`NodeEmbeddingThreadRuntime`](../reference/dotnet/Microsoft.JavaScript.NodeApi.Runtime/NodeEmbeddingThreadRuntime)
instances may be created and disposed.

The directory provided to the environment instance is the base for package resolution. Any packages
or modules imported in the next step must be installed (in a `node_modules` directory) in that base
directory or a parent directory.
The directory provided to the
[`NodeEmbeddingThreadRuntime`](../reference/dotnet/Microsoft.JavaScript.NodeApi.Runtime/NodeEmbeddingThreadRuntime)
instance is the base for package resolution. Any packages or modules imported in the next step must
be installed (in a `node_modules` directory) in that base directory or a parent directory.

2. Invoke a simple script from C#:
```C#
await nodejs.RunAsync(() =>
await nodejsRuntime.RunAsync(() =>
{
JSValue.RunScript("console.log('Hello from JS!');");
});
```

Be aware of JavaScript's single-threaded execution model. **All JavaScript operations must be
performed from the JS environment thread**, unless otherwise noted. Use any of the `Run()`,
`RunAsync()`, `Post()`, or `PostAsync()` methods on the JS environment instance to switch to the
JS thread. Also keep in mind any JavaScript values of type `JSValue` (or any of the more specific
JS value struct types) are not valid after switching off the JS thread. A `JSReference` can hold
on to a JS value for future use. See also
`RunAsync()`, `Post()`, or `PostAsync()` methods on the JS `NodeEmbeddingThreadRuntime` instance
to switch to the JS thread. Also keep in mind any JavaScript values of type `JSValue` (or any of
the more specific JS value struct types) are not valid after switching off the JS thread.
A `JSReference` can hold on to a JS value for future use. See also
[JS Threading and Async Continuations](../features/js-threading-async) and
[JS References](../features/js-references).

3. Import modules or module properties:
```C#
// Import * from the `fluid-framework` module. Items exported from the module will be
// available as properties on the returned JS object.
JSValue fluidPackage = nodejs.Import("fluid-framework");
JSValue fluidPackage = nodejsRuntime.Import("fluid-framework");

// Import just the `SharedString` class from the `fluid-framework` module.
JSValue sharedStringClass = nodejs.Import("fluid-framework", "SharedString");
JSValue sharedStringClass = nodejsRuntime.Import("fluid-framework", "SharedString");
```
As explained above, importing modules must be done on the JS thread.

## Debugging the JavaScript code in a .NET process
```C#
int pid = Process.GetCurrentProcess().Id;
Uri inspectionUri = nodejs.StartInspector();
Uri inspectionUri = nodejsRuntime.StartInspector();
Debug.WriteLine($"Node.js ({pid}) inspector listening at {inspectionUri}");
```
Then attach a JavaScript debugger such as VS Code or Chrome to the inspection URI.

0 comments on commit bd029e4

Please sign in to comment.