tl/dr - figuring out how to run an npm package in c# beyond hello world example in edge.js, specifically tslint.

having ones cake and eating it...

cake eating

So nuget is my goto place for bits and pieces when I'm butchering away in c#. Its simple, convenient and I don't give it much thought.

There are times though when the thing you really want isn't on nuget. It's on npm in the world of node.js. so I peer over the metaphoric candy-cane fence at the rainbows, chocolate rivers and gingerbread houses in the kingdom of node.js that look so inviting.

Shiny shiny

Lately I've been eyeing some of the goodies in npm for building, linting, bundling, minifying and transpiling with interest.

I haven't spent much time butchering around in node.js but I like the idea of being able to use npm packages in C#.

There are a few ways to achieve this I know of, but I'll focus on the following two.

  • create a new thread and call the node packages .cmd file.
  • use edge.js

Inspiration

inspiration

In my previous post, I talked about my battles with deploying typescript and in fact the deployment process in general. I think this is one area where the node.js ecosystem looks much richer than .Net.

I really like the choices that build tooling like grunt or gulp gives you. The linting, bundling, minification and transpiling all seem very pluggable and straightforward.

I use WebEssentials in Visual Studio for linting typescript and javascript. For the most part it does this well but it does cause VS to lag if you have lots of files to lint and a modest set of rules for each.

This uses node.js under the covers and it served as my starting point and inspiration for this post so thanks to Mads Kristensen and the github contributors for this great open source project.

The premise

I have some hooks that run code inspections with tools like stylecop against commits. I wanted to extend the hooks to linting .css, .js and .ts files.

guts...

guts So under the covers webessentials is running npm packages for tslint and jshint by creating new threads and calling the .cmd for each package.

It works fine but its slower to spin up a new process each time (for tslint parsing is per file) and then, either parse the response from stdout, or in webessentials case, read an output file from disk.

We currently use this approach in the hooks and they work ok but it got me wondering how edge.js would get on with this task and whether it could increase the performance.

so what is edge.js

Well in a nutshell it hosts the .net CLR and node.js in the same process allowing c# to call an instance of node.js and more besides.

For a more in-depth look at edge.js you can check out the docs.
Iris Classon has also posted some video tutorials about getting started

Lets get Func<Y>

Funky

I've seen plenty of 'hello world' examples but nothing more involved and specifically I want to test calling tslint using edge.js in C#.

So it all starts with a class to load a proxy script with some options, then pass in some clr object to Execute and return a task.

public class EdgeProxyLoader {  
  public EdgeProxyLoader(string proxyFileName, string options)
  {
    var proxyScript = File.ReadAllText(proxyFileName);
    proxyScript = proxyScript.Replace("{options}", options);
    this.edgeCall = Edge.Func(proxyScript);
  }

  public Task<object> Execute(object data)
  {
    return this.edgeCall(data);
  }
}

In edge.js the clr object uses value reflection so as long as Property names match up from your data parameter to your proxy script, they will be passed correctly.

Why load from a script? Well for tslint I only wanted to load the options once rather than pass them to each lint() call.

Here's what the script file looks like. I just called string.replace for the options so for each proxyScript I can separate global from function scope requirements provided I use this convention.

// global, stuff that doesn't change per function call
var Linter = require("tslint");  
var fs = require('fs');  
var options = {options};

// edge.js function
return function(data, callback) {  
  // data object for any arguments required 
  // can be complex .net type
  var tsLinter = new Linter(data.fileName, data.contents, options);
  var result = tsLinter.lint();
  callback(null, result);
}

So for each npm package I want to use, I can create a class to setup the edge proxy Func with my global stuff.

The async Execute call is where I can customise my options and result types per package.

public class TsLinter  
{
  private static readonly string basePath = AppDomain.CurrentDomain.BaseDirectory;
  private static readonly TsLintOptions options = new TsLintOptions(basePath + "\\tslint.json");
  private static readonly EdgeProxyLoader loader = new EdgeProxyLoader(basePath + "\\proxy\\tsLintProxy.js", options.ToString());

  public static async Task<TsLintResult> Execute(string fileName)
  {
    var contents = File.ReadAllText(fileName);
    var task = await loader.Execute(new { fileName, contents });
    return new TsLintResult(task);
  }
}

So putting it all together into a console app, I grab the names of some files to lint (I know it's tslint but .js are valid .ts files right?!).

I then async call TsLinter.Execute(f) on multiple threads and wait for the results.

public class Program  
{
  private static readonly string basePath = AppDomain.CurrentDomain.BaseDirectory;

  public static async void Start()
  {
    var files = Directory.GetFiles(basePath + "scripts", "*.js").ToArray();
    var tasks = files.AsParallel().Select(async f => await TsLinter.Execute(f));
    var result = tasks.Select(r => r.Result).ToArray();

    Array.ForEach(result, Console.WriteLine);
    Console.WriteLine("Press any key to exit");
    Console.ReadKey();
  }

  public static void Main(string[] args)
  {
    Task.Run((Action)Start).Wait();
  }
}

And there you have it, calling an npm package in C# using edge.js.

The summary bit

So it was an interesting experiment. Whilst the tasks are set up on separate threads, the callbacks appear to be executed on the thread that edge.js is running on.

This makes sense as it is what edge.js is doing under the covers to run the single threaded node.js and seamlessly work with multi-threaded .net code.

In the end I think that the single threading in edge.js is a limitation for this particular experiment with tslint.

Whilst I had edge.js callback to C# code and quickly reflect results into clr objects, rather than read from disk; I could not scale it to multiple threads to quickly lint lots of files. It simply doesn't match the ability to spin up lots of node.js processes and have them lint files in parallel.

So the full sourcecode is up on github and I'd love to hear your comments and feedback on anything daft I did or improvements that could be made.