Skip to content
main
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

DotNetJS

NuGet npm CodeFactor codecov CodeQL

This project is dedicated to providing user-friendly workflow for consuming .NET C# programs and libraries in any JavaScript environment, be it web browsers, Node.js or custom restricted spaces, like web extensions for VS Code.

The solution is based on two main components:

  • JavaScript library — dotnet-runtime. Consumes compiled C# assemblies and .NET runtime WebAssembly module to provide C# interoperability layer in JavaScript. The library is environment-agnostic — it doesn't depend on platform-specific APIs, like browser DOM or node modules and can be consumed as CommonJS or ECMAScript module or imported via script tag in browsers.
  • NuGet C# package — DotNetJS. Provides JavaScript interoperability layer in C# and packs project output into single-file JavaScript library via MSBuild task. The packed library uses embeded assemblies to initialize dotnet-runtime library. Can optionally emit declarations and type definions to bootstrap the interoperability.

Quick Start

In C# project configuration file specify Microsoft.NET.Sdk.BlazorWebAssembly SDK and import DotNetJS NuGet package:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <LangVersion>10</LangVersion>
    </PropertyGroup>

    <ItemGroup>
        <!-- Make sure to specify the latest available version. -->
        <PackageReference Include="DotNetJS" Version="0.2.5"/>
    </ItemGroup>

</Project>

To invoke a JavaScript function in C# use JS.Invoke(functionName, args) method. To expose a C# method to JavaScript, use [JSInvokable] attribute:

using System;
using DotNetJS;
using Microsoft.JSInterop;

// Entry assembly name defines generated JavaScript file and
// main export object names. Can be changed in build configuration.
namespace HelloWorld;

class Program
{
    // Entry point is invoked by the JavaScript runtime on boot.
    void Main ()
    {
        // Invoking 'getName()' JavaScript function.
        var hostName = JS.Invoke<string>("getName");
        // Writing to JavaScript host console.
        Console.WriteLine($"Hello {hostName}, DotNet here!");
    }

    [JSInvokable] // The method is invoked from JavaScript.
    public static string GetName () => "DotNet";
}

Publish the project with dotnet publish. A single-file JavaScript library will be produced at the "bin" directory. Consume the library depending on the environment:

Browser

<!-- Import as a global 'HelloWorld' object via script tag. -->
<script src="HelloWorld.js"></script>

<script>
    
    // This function is invoked from C#.
    window.getName = () => "Browser";
    
    window.onload = async function () {
        // Booting the DotNet runtime and invoking entry point.
        await HelloWorld.boot();
        // Invoking 'GetName()' C# method.
        const guestName = HelloWorld.invoke("GetName");
        console.log(`Welcome, ${guestName}! Enjoy your global space.`);
    };
    
</script>

Node.js

// Import as CommonJS module.
const HelloWorld = require("HelloWorld");
// ... or as ECMAScript module in node v17 or later.
import HelloWorld from "HelloWorld.js";

// This function is invoked from C#.
global.getName = () => "Node.js";

(async function () {
    // Booting the DotNet runtime and invoking entry point.
    await HelloWorld.boot();
    // Invoking 'GetName()' C# method.
    const guestName = HelloWorld.invoke("GetName");
    console.log(`Welcome, ${guestName}! Enjoy your module space.`);
})();

Example Projects

Find the following sample projects in this repository:

  • Hello World — Consume the produced library as a global import in browser, CommonJS or ES module in node.
  • Web Extension — Consume the library in VS Code web extension, which works in both web and standalone versions of the IDE.
  • Runtime Tests — Integration tests featuring various usage scenarios: async method invocations, interop with instances, sending raw byte arrays, streaming, etc.

Build Properties

Specify following optional properties in .csproj to customize the build:

  • <Clean>false<Clean> — do not clean the build output folders.
  • <LibraryName>CustomName</LibraryName> — specify a custom name for the generated library file and export object.

For example, following configuration will preserve the build artifacts and produce my-dotnet-lib.js library with my-dotnet-lib export object:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <LangVersion>10</LangVersion>
        <Clean>false</Clean>
        <LibraryName>my-dotnet-lib</LibraryName>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="DotNetJS" Version="0.1.0"/>
    </ItemGroup>

</Project>

Compiling Runtime

To compile and test the runtime run the following in order (under Runtime folder):

scripts/install-emsdk.sh
scripts/compile-runtime.sh
scripts/compile-test.sh
npm build
npm test

Publishing Runtime

A memo for the publishing process after modifying JS runtime.

  1. Bump NPM version on ./Runtime/package.json and:
  • npm run build
  • scripts/publish-package.sh
  1. Bump NuGet version on ./DotNetJS/DotNetJS.csproj and:
  • dotnet pack -c Release --output bin
  • dotnet nuget push bin/DotNetJS.{VER}.nupkg --api-key {KEY} --source https://api.nuget.org/v3/index.json
  1. Wait for the package indexing, bump NuGet version on ./Runtime/test/Test.csproj and:
  • script/compile-test.sh
  1. Remind myself that this should be automated.

About

Use C# in any JavaScript environment: browsers, Node.js, VS Code web extensions, etc.

Topics

Resources

License

Sponsor this project