For a minimal example of this scenario, see ../examples/dotnet-module/.
-
Create a .NET Class library project that targets .NET 6 or later. (.NET 8 for AOT.)
mkdir ExampleModule cd ExampleModule dotnet new classlib --framework net6.0
-
Add a reference to the
Microsoft.JavaScript.NodeApi
andMicrosoft.JavaScript.NodeApi.Generator
packages:dotnet add package --prerelease Microsoft.JavaScript.NodeApi dotnet add package --prerelease Microsoft.JavaScript.NodeApi.Generator
⚠️ Until these packages are published, you'll need to build them from source.
Then add theout/pkg
directory as a local package source in yourNuGet.config
.Afterward you should have the two references in your project file:
<ItemGroup> <PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.7.*-*" /> <PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.7.*-*" /> </ItemGroup>
-
Add one or more public types to the project with the
[JSExport]
attribute. Only types tagged with this attribute exported to JavaScript. (It's also possible to export module-level properties and methods by using[JSExport]
onpublic static
properties or methods of a class that is otherwise not exported.)using Microsoft.JavaScript.NodeApi; [JSExport] public class Example {
-
Build the project, to produce the assembly (
.dll
) file.dotnet build
The build also automaticaly produces a
.d.ts
file with type definitions for APIs in the module that are exported to JavaScript.✨ If you're curious, you can check out the generated marshalling code for exported APIs at
obj\{Configuration}\{TargetFramerwork}\{RuntimeIdentifier}\generated\ Microsoft.JavaScript.NodeApi.Generator\Microsoft.JavaScript.NodeApi.Generator.ModuleGenerator
-
Switching over to the JavaScript project, add a dependency on the
node-api-dotnet
npm package:npm install node-api-dotnet
⚠️ Until this package is published, you'll need to build it from source.
Then get the package fromout/pkg/node-api-dotnet-{version}.tgz
. -
Import the
node-api-dotnet
package in your JavaScript or TypeScript code. The import syntax depends on the module system the current project is using.ES modules (TypeScript or JavaScript):
import dotnet from 'node-api-dotnet';
CommonJS modules (TypeScript):
import * as dotnet from 'node-api-dotnet';
CommonJS modules (JavaScript):
const dotnet = require('node-api-dotnet');
To load a specific version of .NET, append the target framework moniker to the module name.
import dotnet from 'node-api-dotnet/net6.0'
Currently the supported target frameworks are
net472
,net6.0
, andnet8.0
. -
Load your .NET module assembly from its path using the
dotnet.require()
function. Also provide a hint about type definitions (from the same path):/** @type {import('./bin/ExampleModule')} */ const ExampleModule = dotnet.require('./bin/ExampleModule');
-
Exported APIs in the assembly are projected as properties on the loaded module object. So then you can use those to call static methods, construct instances of classes, etc:
ExampleModule.StaticClass.ExampleMethod(); const exampleObj = new ExampleModule.ExampleClass(...args);
Of course you can access properites, pass arguments to methods, get return values, and so on. Most types get automatically marshalled between JavaScript and .NET as you'd expect. For details, see the type projections reference.
⚠️ Generic types and methods are not yet supported very well -- with the exception of generic collections which work great. -
Optional: Switch the node module to use .NET Native AOT compilation.
AOT compiled modules load more quickly and do not have any runtime dependency .NET. However, .NET Native AOT has some limitations, so you should understand the implications before starting on this path. Some of the considerations include:
- .NET 8 SDK or later is required at build time. (Not at run time.)
- AOT binaries are much larger: at least 4-10 MB depending on the platform.
- AOT code can only call other native code. That may include other .NET Native AOT assemblies, but NOT any managed .NET assemblies, because the .NET runtime is not loaded.
- No dynamic loading, reflection, or runtime code-generation is possible.
- Some .NET APIs and libraries are not compatible with AOT. For more details, see https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
To configure a .NET Node API module project for AOT, make sure the target framework is .NET 8 or later, and add the publishing properties to the project file:
<TargetFramework>net8.0</TargetFramework> <PublishAot>true</PublishAot> <PublishNodeModule>true</PublishNodeModule>
Then publish the project to produce the native module (with
.node
extension):dotnet publish
A native module does not depend on the
node-api-dotnet
package, so it can be removed from the JavaScript project'spackage.json
. Then update the JavaScript code torequire()
the .NET AOT module directly. Be sure to reference the published.node
file location, which might be different from the built.dll
location./** @type {import('./bin/ExampleModule')} */ const ExampleModule = require('./bin/ExampleModule');
Or if using ES modules:
/** @type {import('./bin/ExampleModule')} */ import ExampleModule from './bin/ExampleModule';