I was working on a side project the other day, and realised I really needed to use some JavaScript functionality. The thought of dealing with Node.js and npm again totally put me off, so I decided to look into the possibility of running JavaScript inside a .NET application. Madness right? It's actually surprisingly easy!
Why would you do this?
As much as I like the .NET ecosystem, there are some things that the JavaScript ecosystem just does better. One of those things is having a library for everything, especially when it comes to the web.
Take syntax highlighting for example. This is possible to do with C# directly, but it's not an especially smooth experience. The TextMateSharp project, for example, provides an interpreter for TextMate grammar files. These are the files that VS Code uses to add basic syntax highlighting for a language. However it wraps a native dependency which adds some complexities if you're looking to deploy the app.
In contrast, JavaScript has a plethora of mature syntax highlighting libraries. To name a few, there's highlight.js, Prism.js (used on this blog), and shiki.js. The first two, in particular, are very mature, with multiple plugins and themes, and with simple APIs.
The obvious trouble with JavaScript as a .NET developer is that you need to learn and opt in to a whole separate tool chain, working with Node.js and NPM. That seems like a big overhead just to use one small feature.
So we are in a bit of a bind. We can either go the C# (+ native) route, or we have to jump out to JavaScript.
Or… we call JavaScript directly from our .NET app 🤯
Approaches to running JavaScript inside .NET
Once you've accepted that you want to run JavaScript from your .NET code, a couple of options come to mind. You could shell-out to a JavaScript engine (like Node.js) and ask it to run your JavaScript for you, but then you haven't really solved the problem; you would still need Node.js installed.
Another option is to bundle the JavaScript engine inside your library directly. This isn't quite as crazy as it sounds, and there are several NuGet packages that take this approach, which then expose a C# layer for interacting with the engine. The following is a collection of just some of the packages you could use to.
Jering.Javascript.NodeJS
This library takes the first of the above approaches. It doesn't include Node.js in the package. Instead, it provides a C# API for executing JavaScript code, and it calls out to the Node.js installed on your machine. This can be useful in environments where you know both are installed, but it doesn't really solve the logistics problem I was trying to avoid.
ChakraCore
ChakraCore was the original JavaScript engine used by Microsoft Edge, before Edge moved to be based on Chromium. According to the GitHub project:
ChakraCore is a JavaScript engine with a C API you can use to add support for JavaScript to any C or C compatible project. It can be compiled for x64 processors on Linux macOS and Windows. And x86 and ARM for Windows only.
So ChakraCore includes a native dependency, but as C# can P/Invoke into native libraries, that's not a problem per-se. But it can provide some deployment challenges.
ClearScript (V8)
The V8 JavaScript engine is what powers Node.JS, Chromium, Chrome, and the latest Edge. The Microsoft.ClearScript package provides a wrapper around the library, providing a C# interface for calling into the V8 library. Just as with ChakraCore, the V8 engine itself is a native dependency. The ClearScript library takes care of the P/Invoke calls, providing a nice C# API, but you still have to make sure you're deploying the correct native libraries based on your target platform.
Jint
Jint is interesting, as it's a JavaScript interpreter that runs entirely in .NET; there's no native dependencies to manage! It has full support for ECMAScript 5.1 (ES5) and supports .NET Standard 2.0, so you can use it in all your projects!
Jurassic
Jurassic is another .NET implementation of a JavaScript engine, similar to Jint. Also similar to Jint, it supports all of ES5, and it also appears to have partial support for ES6. In contrast to Jint, Jurassic is not an interpreter; it compiles JavaScript into IL, which makes it very fast, and it has no native dependencies!
So with all of these options, which should you choose?
JavaScriptEngineSwitcher: for when one JS engine isn't enough
I've been burying the lede a bit here, as there's another great project that makes it simple to try out any of them. While all of the libraries will allow you to run JavaScript, they all have slightly different C# APIs for interacting with them. That can make comparing them a bit of a pain, as you have to learn a different API for each one.
Enter JavaScriptEngineSwitcher. This library provides wrapper packages for all of the libraries I mentioned above and more:
- Jering.Javascript.NodeJS
- ChakraCore
- Microsoft ClearScript.V8
- Jint
- Jurassic
- MSIE JavaScript Engine for .NET
- NiL.JS
- VroomJs
Each of the libraries is supported in a separate package (with an additional native package required for engines with native dependencies), and a "Core" package, which provides the common API surface. Even if you have no intention of switching JS engines, I would be inclined to use the JavaScriptEngineSwitcher wrapper libraries where possible, just so that you don't have to figure out a new API if you need to switch engines later.
Unlike the old trope of "how often do you change your database", changing the JavaScript engine you use in your .NET project seems perfectly feasible to me. For example, I started with Jint, but when I needed to execute larger scripts I ran into performance problems and switched to Jurassic. JavaScriptEngineSwitcher made that as simple as adding a new package to my project and changing some initialization code.
I only discovered JavaScriptEngineSwitcher recently, but the latest version has nearly a million downloads, and it's used in the .NET static site builder Statiq. In the last part of this post, I'll give a quick example of the most basic usage.
A case study: running prism.js in a console app with JavaScriptEngineSwitcher
I started this post by discussing a specific scenario - syntax highlighting of code blocks. In this section I'll show how to highlight a small snippet of code using prism.js, running inside a console app.
To get started, add a reference to the JavaScriptEngineSwitcher.Jurassic NuGet package:
dotnet add package JavaScriptEngineSwitcher.Jurassic
Next, download the JavaScript file you want to run. For example, I downloaded the prism.js file from their website, and added c# to the default set of supported languages. After dropping the file in the root of the project folder, I updated the file to be an embedded resource. You can do this from your IDE, or manually by editing the project file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JavaScriptEngineSwitcher.Jurassic" Version="3.17.4" />
</ItemGroup>
<!-- 👇 Make prism.js an embedded resource -->
<ItemGroup>
<None Remove="prism.js" />
<EmbeddedResource Include="prism.js" />
</ItemGroup>
</Project>
All that remains is to write the code to run the script in our program. The following snippet sets up the JavaScript engine, loads the embedded prism.js
library from the assembly, and executes it.
using JavaScriptEngineSwitcher.Jurassic;
// Create an instance of the JavaScript engine
IJsEngine engine = new JurassicJsEngine();
// Execute the embedded resource called JsInDotnet.prism.js from the provided assembly
engine.ExecuteResource("JsInDotnet.prism.js", typeof(Program).Assembly);
Now we can run our own JavaScript commands within the same context. We can pass values from C# to the JavaScript engine by using SetVariableName
, Execute
, and Evaluate
:
// This is the code we want to highlight
string code = @"
using System;
public class Test : ITest
{
public int ID { get; set; }
public string Name { get; set; }
}";
// set the JavaScript variable called "input" to the value of the c# variable "code"
engine.SetVariableValue("input", code);
// set the JavaScript variable called "lang" to the string "csharp"
engine.SetVariableValue("lang", "csharp");
// run the Prism.highlight() function, and set the result to the "highlighed" variable
engine.Execute($"highlighted = Prism.highlight(input, Prism.languages.csharp, lang)");
// "extract the value of "highlighted" from JavaScript to C#
string result = engine.Evaluate<string>("highlighted");
Console.WriteLine(result);
When you put it all together, the highlighted code is printed to the console:
<span class="token keyword">using</span> <span class="token namespace">System</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ITest</span></span>
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> ID <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
Which, when rendered, looks something like this:
using System;
public class Test : ITest
{
public int ID { get; set; }
public string Name { get; set; }
}
I was amazed by how simple this whole process was. Spinning up a new JavaScript engine, loading the prism.js file, and executing our custom code was so smooth. It was the perfect solution to my scenario.
I obviously wouldn't suggest doing this for all applications. If you need to run a lot of JavaScript then it's probably easier to use the idioms and tools from the Node.js ecosystem directly. But if you're just trying to leverage a small, self-contained tool (like prims.js) then this is a great option.
Summary
In this post I showed how you can use the JavaScriptEngineSwitcher NuGet package to run JavaScript from inside a .NET application. This package provides a consistent interface to many different JavaScript engines. Some of the engines (such as Chakra Core and V8) have a native component, while others (such as Jint and Jurassic) use managed code only. Finally, I showed how you could use JavaScriptEngineSwitcher to run the Prims.js code-highlighting library from inside a .NET app.