Skip to content

agracio/edge-js

Repository files navigation

Edge.js: .NET and Node.js in-process

Actions Status Git Issues Closed Issues


This library is based on https://github.com/tjanczuk/edge all credit for original work goes to Tomasz Janczuk.


Overview

Edge.js allows you to run Node.js and .NET code in one process on Windows, macOS, and Linux

You can call .NET functions from Node.js and Node.js functions from .NET.
Edge.js takes care of marshaling data between CLR and V8. Edge.js also reconciles threading models of single-threaded V8 and multi-threaded CLR.
Edge.js ensures correct lifetime of objects on V8 and CLR heaps.
The CLR code can be pre-compiled or specified as C#, F#, Python (IronPython), or PowerShell source: Edge.js can compile CLR scripts at runtime.
Edge can be extended to support other CLR languages or DSLs.

Edge.js interop model

Edge.js provides an asynchronous, in-process mechanism for interoperability between Node.js and .NET. You can use this mechanism to:

  • script Node.js from a .NET application on Windows using .NET Framework
  • script C# from a Node.js application on Windows, macOS, and Linux using .NET Framework/.NET Core
  • use CLR multi-threading from Node.js for CPU intensive work more...
  • write native extensions to Node.js in C# instead of C/C++
  • integrate existing .NET components into Node.js applications
  • access MS SQL from Node.js using ADO.NET
  • script F# from Node.js
  • script Powershel from Node.js
  • script Python (IronPython) from Node.js

Read more about the background and motivations of the project here.

Updates

  • Support for new versions of Node.Js.
  • Support for .NET Core 3.1 - 9.x on Windows/Linux/macOS.
  • Fixes AccessViolationException when running Node.js code from C# PR #573.
  • Fixes StackOverflowException PR #566 that occurs when underlying C# code throws complex exception.
  • Fixes issues #469, #713
  • Other PRs: PR #725, PR #640
  • Multiple bug fixes and improvements to the original code.

NPM package edge-js

NuGet package EdgeJs


Electron

For use with Electron electron-edge-js

VS Code extensions

VS Code uses Electron shell, to write extensions for it using Edge.js use electron-edge-js

Quick start

Sample app that shows how to work with .NET Core using inline code and compiled C# libraries.
https://github.com/agracio/edge-js-quick-start

Pre-requisites

Node.js Support

edge-js support policy

  • Windows supports 4 latest LTS or candidate LTS releases (even numbered).
  • Windows supports up to 1 "Current" (odd numbered) release and drops it when superseeded by new LTS candidate.
  • macOS comes precompiled with same releases as Windows. When using Node.js version that is not pre-compiled edge-js binaries will be compiled during npm install using node-gyp.
  • Linux will always compile edge-js binaries during npm install using node-gyp.

Windows

Version x86 x64 arm64
18.x ✔️ ✔️
20.x ✔️ ✔️ ✔️
22.x ✔️ ✔️ ✔️
24.x ✔️ ✔️
25.x ✔️ ✔️

macOS binaries pre-compiled for

Version x64 arm64
18.x ✔️ ✔️
20.x ✔️ ✔️
22.x ✔️ ✔️
24.x ✔️ ✔️
25.x ✔️ ✔️

Supports

Version x64 arm64
16.x - 25.x ✔️ ✔️

Linux

Version x64 arm64
16.x - 25.x ✔️ ✔️

Other Linux architectures might work but have not been tested.

Scripting CLR from Node.js and Node.js from CRL

Script CLR from Node.js Script Node.js from CLR
.NET 4.5 Mono 6.x CoreCLR
Windows ✔️ ✔️* ✔️
Linux ✔️* ✔️
macOS ✔️ ✔️
.NET 4.5 Mono CoreCLR
Windows ✔️
Linux
macOS

Mono

Mono is no longer actively supported. Existing code will remain In Edge.Js but focus will be on .NET Core.

* Edge.js does not have a flag to force it to run in Mono environment on Windows, it would only be possible to run if you have Windows without .NET Framework present.
* On Linux there is a known bug that will throw exception when executing C# code under certain conditions. Use one of the following options.

  1. Set LD_PRELOAD env variable before running Edge.js with Mono
EXPORT LD_PRELOAD="libmono-2.0.so libmonosgen-2.0.so libstdc++.so.6"
  1. Set LD_PRELOAD in your Node.js app entry point using Javascript
Object.assign(process.env, {
    // Work around Mono problem: undefined symbol: mono_add_internal_call_with_flags
    LD_PRELOAD: 'libmono-2.0.so libmonosgen-2.0.so libstdc++.so.6',
});

Source: mono/mono#17079 (comment)

Edge.js uses this code in test setup:

edge-js/tools/test.js

Lines 19 to 24 in 8bab03a

if(process.platform === 'linux' && !process.env.EDGE_USE_CORECLR){
Object.assign(process.env, {
// Work around Mono problem: undefined symbol: mono_add_internal_call_with_flags
LD_PRELOAD: 'libmono-2.0.so libmonosgen-2.0.so libstdc++.so.6',
});
}

Node.js application packaging

When packaging your application using Webpack make sure that edge-js is specified as external module.

Webpack

module.exports = {
  target: 'node',
  externals: {
    'edge-js': 'commonjs2 edge-js',
    'edge-cs': 'commonjs2 edge-cs',
  },
  node: {
    __dirname: true,
    __filename: true,
  },
}

Next.js

next.config.js

const nextConfig = {
  serverExternalPackages: ['edge-js'],
}
 
module.exports = nextConfig

next.config.ts

const nextConfig: NextConfig = {
  serverExternalPackages: ['edge-js'],
};

export default nextConfig;

Node.js single executable application packaging

edge-js-pkg

Additional languages support

SQL scripting

Provides simple access to SQL without the need to write separate C# code.

Framework Platform NPM Package Language code Documentation
.NET 4.5 Windows edge-sql sql Script SQL in Node.js 🔗
CoreCLR Any edge-sql sql Script SQL in Node.js 🔗

Python (IronPython) scripting

NOTE This functionality requires IronPython 3.4

Framework Platform NPM Package Language code Documentation
.NET 4.5 Windows edge-py py Script Python in Node.js 🔗
CoreCLR Any edge-py py Script Python in Node.js 🔗

PowerShell scripting

NOTE CoreCLR requires dotnet 8

Framework Platform NPM Package Language code Documentation
.NET 4.5 Windows edge-ps ps Script PowerShell in Node.js 🔗
CoreCLR Windows edge-ps ps Script PowerShell in Node.js 🔗

F# scripting

Framework Platform NPM Package Language code Documentation
.NET 4.5 Windows edge-fs fs Script F# in Node.js 🔗
CoreCLR Windows edge-fs fs Script F# in Node.js 🔗

How to use

Scripting CLR from Node.js - full documentation

Scripting Node.js from CLR - full documentation

Scripting CLR from Node.js sample app https://github.com/agracio/edge-js-quick-start


Short guide

Inline C# code

ES5

var edge = require('edge-js');

var helloWorld = edge.func(function () {/*
    async (input) => { 
        return ".NET Welcomes " + input.ToString(); 
    }
*/});

helloWorld('JavaScript', function (error, result) {
    if (error) throw error;
    console.log(result);
});

ES6 with templated strings

var edge = require('edge-js');

var helloWorld = edge.func(`
    async (input) => { 
        return ".NET Welcomes " + input.ToString(); 
    }
`);

helloWorld('JavaScript', function (error, result) {
    if (error) throw error;
    console.log(result);
});

Passing parameters

var edge = require('edge-js');

var helloWorld = edge.func(function () {/*
    async (dynamic input) => { 
        return "Welcome " + input.name + " " + input.surname; 
    }
*/});

helloWorld({name: 'John', surname: 'Smith'}, function (error, result) {
    if (error) throw error;
    console.log(result);
});

Using C# class

var getPerson = edge.func({
    source: function () {/* 
        using System.Threading.Tasks;
        using System;

        public class Person
        {
            public Person(string name, string email, int age)
            {
                Id =  Guid.NewGuid();
                Name = name;
                Email = email;
                Age = age;
            }
            public Guid Id {get;set;}
            public string Name {get;set;}
            public string Email {get;set;}
            public int Age {get;set;}
        }

        public class Startup
        {
            public async Task<object> Invoke(dynamic input)
            {
                return new Person(input.name, input.email, input.age);
            }
        }
    */}
});

getPerson({name: 'John Smith', email: 'john.smith@myemailprovider', age: 35}, function(error, result) {
    if (error) throw error;
    console.log(result);
});

When using inline C# class code must include

public class Startup
{
    public async Task<object> Invoke(object|dynamic input)
    {
        // code
        // return results
    }
}

Using compiled assembly

// People.cs

using System;

namespace People
{
    public class Person
    {
        public Person(string name, string email, int age)
        {
            Id =  Guid.NewGuid();
            Name = name;
            Email = email;
            Age = age;
        }
        public Guid Id {get;}
        public string Name {get;}
        public string Email {get;}
        public int Age {get;}
    }
}

// EdgeJsMethods.cs

using System.Threading.Tasks;
using People;

namespace EdgeJsMethods
{
    class Methods
    {
        public async Task<object> GetPerson(dynamic input)
        {
            return await Task.Run(() => new Person(input.name, input.email, input.age));
        }
    }
}
var edge = require('edge-js');

var getPerson = edge.func({
    assemblyFile: myDll, // path to compiled .dll
    typeName: 'EdgeJsMethods.Methods',
    methodName: 'GetPerson'
});

getPerson({name: 'John Smith', email: 'john.smith@myemailprovider', age: 35}, function(error, result) {
    if (error) throw error;
    console.log(result);
});

Edge.js C# method must have the following signature

public async Task<object> MyMethod(object|dynamic input)
{
    //return results sync/async;
}

CoreCLR

  • If not set Edge.js will run as .NET 4.5 on Windows.
  • On macOS and Linux Edge.js will default to Mono if it is installed otherwise will run as CoreCLR.
  • Can be set using js code below or as an environment variable SET EDGE_USE_CORECLR=1 or EXPORT EDGE_USE_CORECLR=1 depending on your platform.
  • Must be set before var edge = require('edge-js');
// set this variable before 
// var edge = require('edge-js');

process.env.EDGE_USE_CORECLR=1

var edge = require('edge-js');

var helloWorld = edge.func(function () {/*
    async (input) => { 
        return ".NET Welcomes " + input.ToString(); 
    }
*/});

helloWorld('JavaScript', function (error, result) {
    if (error) throw error;
    console.log(result);
});

Executing synchronously without function callback

If your C# implementation will complete synchronously you can call this function as any synchronous JavaScript function as follows:

var edge = require('edge-js');

var helloWorld = edge.func(function () {/*
    async (input) => { 
        return ".NET Welcomes " + input.ToString(); 
    }
*/});

var result = helloWorld('JavaScript', true);

Calling C# asynchronous implementation as a synchronous JavaScript function will fail

var edge = require('edge-js');

var helloWorld = edge.func(function () {/*
    async (input) => { 
        return await Task.Run(() => ".NET Welcomes " + input.ToString());
    }
*/});

// sync call will throw exception
var result = helloWorld('JavaScript', true);

Promises

var func = edge.func(function () {/*
    async (dynamic input) => { 
        return "Welcome " + input.name + " " + input.surname; 
    }
*/});

function helloWorld(){
    return new Promise((resolve, reject) =>{
        func({name: 'John', surname: 'Smith'}, function (error, result) {
            if(error) reject(error);
            else resolve(result);
        });
    });
}

Scripting Node.js from CLR

using System; 
using System.Threading.Tasks;
using EdgeJs;

class Program
{
    public static async Task Start()
    {
        var func = Edge.Func(@"
            return function (data, callback) {
                callback(null, 'Node.js welcomes ' + data);
            }
        ");

        Console.WriteLine(await func(".NET"));
    }

    static void Main(string[] args)
    {
        Start().Wait();
    }
}

More examples in tests DoubleEdge.cs


Docker

Dockerfile: Dockerfile
Docker Hub image: agracio/ubuntu-node-netcore

  • Based on Ununtu 24.04
  • User directory devvol

Pre-installed packages

  • Node.js 22
  • dotnet 9
  • git
  • build tools
  • sudo, curl, wget
  • node-gyp

Using container

  • Run interactive starting in devvol, set EDGE_USE_CORECLR=1 at container level
  • Git clone edge-js and enter cloned repo directory
  • npm install
  • Run tests
docker run -w /devvol -e EDGE_USE_CORECLR=1 -it agracio/ubuntu-node-netcore:latest
git clone https://github.com/agracio/edge-js.git && cd edge-js
npm i
npm test


Edge.js readme

❗ Some of the original Edge.js documentation is outdated ❗

What problems does Edge.js solve?

Ah, whatever problem you have. If you have this problem, this solves it.

--Scott Hanselman (@shanselman)

Before you dive in

Read the Edge.js introduction on InfoQ.
Listen to the Edge.js podcast on Herdingcode.

Contents

Scripting CLR from Node.js
    What you need
        Windows
        Linux