数日前、副業をしていて、いくつかのJavaScript機能を使う必要があることに気づきました。Node.jsやnpmを再び扱うことを考えると完全にやる気をなくし、代わりに.NETアプリケーション内でJavaScriptを実行する可能性を調べることにしました。クレイジーですよね?実際、驚くほど簡単でした。
1. なぜそんなことをするのか?
.NETエコシステムは大好きですが、JavaScriptエコシステムの方が優れていることもあります。その一つが「何でもライブラリが見つかる」こと、特にネットワーク関連です。
例えば、シンタックスハイライト。これはC#でも直接できますが、特にスムーズな体験ではありません。例えば、TextMateSharp プロジェクトはTextMate文法のインタプリタを提供しています。これらのファイルはVS Codeが言語に基本的なシンタックスハイライトを追加するために使用するものです。しかし、アプリケーションをデプロイしようとすると、ネイティブ依存関係 をラップしているため、複雑さが増します。
対照的に、JavaScriptには成熟したシンタックスハイライトライブラリが豊富にあります。ほんの一例として、highlight.js、Prism.js(このブログで使用)、shiki.js があります。特に最初の2つは非常に成熟しており、多数のプラグインやテーマがあり、APIもシンプルです。
.NET開発者にとって、JavaScriptの明らかな問題は、Node.jsやNPMと連携する完全に独立したツールチェーンを学び、選択する必要があることです。小さな機能を使うだけなのに、これは大きなオーバーヘッドに思えます。
したがって、私たちはジレンマに陥ります。C#(+ネイティブ)ルートを取るか、JavaScriptに切り替えるかです。
あるいは…….NETアプリケーションから直接JavaScriptを呼び出すのです 🤯
2. .NET で JavaScript を実行する
.NETコード内でJavaScriptを実行すると決めたら、いくつかの選択肢があります。JavaScriptエンジンを借りてきて、あなたの代わりにJavaScriptを実行させることもできますが、それでは本当に問題は解決せず、依然としてNode.jsをインストールする必要があります。
もう一つの選択肢は、ライブラリにJavaScriptエンジンを直接バンドルすることです。これは聞こえるほどクレイジーではありません。このアプローチを採用し、エンジンと対話するためのC#レイヤーを公開しているNuGetパッケージがいくつかあります。
以下は使用できるパッケージのリストです。
Jering.JavaScript.NodeJS
このライブラリは上記の最初のアプローチを採用しています。パッケージにNode.jsは含まれていません。代わりに、JavaScriptコードを実行するためのC# APIを提供し、マシンにインストールされているNode.jsを呼び出します。これは両方がインストールされていることがわかっている環境では有用かもしれませんが、避けたい問題を実際に解決しているわけではありません。
ChakraCore
ChakraCore は、EdgeがChromiumベースのエンジンに切り替わる前に使用していたJavaScriptエンジンです。GitHubプロジェクトによると:
ChakraCoreはC言語APIを持つJavaScriptエンジンで、C言語またはC言語互換プロジェクトにJavaScriptサポートを追加するために使用できます。Linux、macOS、Windows上でx64プロセッサ向けにコンパイルできます。x86とARMはWindowsのみ対応です。
したがって、ChakraCoreにはネイティブ依存関係が含まれていますが、C#はネイティブライブラリに P/Invoke できるため、それ自体は問題ではありません。ただし、デプロイに関するいくつかの課題が生じます。
ClearScript (V8)
Node.JS、Chromium、Chrome、および最新のEdgeはV8 JavaScriptエンジンを使用しています。Microsoft.ClearScript パッケージはこのライブラリのラッパーを提供し、V8ライブラリを呼び出すためのC#インターフェースを提供します。ChakraCore と同様に、V8エンジン自体はネイティブ依存関係です。ClearScript ライブラリは P/Invoke 呼び出しを処理し、優れたC# APIを提供しますが、ターゲットプラットフォームに正しいネイティブライブラリをデプロイしていることを確認する必要があります。
Jint
Jint は面白いことに、ネイティブ依存関係なしで完全に.NET内で動作するJavaScriptインタプリタです!ECMAScript 5.1 (ES5) を完全にサポートし、.NET Standard 2.0をサポートしているため、すべてのプロジェクトで使用できます!
Jurassic
Jurassic は、Jint と同様に.NET実装の別のJavaScriptエンジンです。また、Jint と同様にすべてのES5をサポートし、ES6も部分的にサポートしているようです。Jint とは異なり、Jurassic はインタプリタではなく、JavaScriptをILにコンパイルするため、非常に高速で、ネイティブ依存関係はありません。
では、これらすべての選択肢の中からどれを選ぶべきでしょうか?
3 JavaScriptEngineSwitcher: 1つのJSエンジンでは足りないとき
もう一つ素晴らしいプロジェクトがあり、上記のいずれかのライブラリを簡単に試すことができます。すべてのライブラリでJavaScriptを実行できますが、対話するためのC# APIはそれぞれ少しずつ異なります。そのため、各ライブラリごとに異なるAPIを学ぶ必要があり、比較するのが少し面倒になる可能性があります。
JavaScriptEngineSwitcher というライブラリは、私が言及したすべてのライブラリとそれ以上のもののラッパーを提供します:
- Jering.JavaScript.NodeJS
- ChakraCore
- Microsoft ClearScript.V8
- Jint
- Jurassic
- MSIE JavaScript Engine for .NET
- NiL.JS
- VroomJs
各ライブラリは個別のパッケージにあり(ネイティブ依存関係を持つエンジンには追加のネイティブパッケージが必要)、Coreパッケージは共通のAPIを提供します。JSエンジンを切り替える予定がなくても、後でエンジンを切り替える必要が生じたときに新しいAPIを理解する必要がないように、可能な限りJavaScriptEngineSwitcherラッパーライブラリを使用することをお勧めします。
.NETプロジェクトで使用するJavaScriptエンジンを変更することは、私の見解では十分に可能です。例えば、私はJintから始めましたが、より大きなスクリプトを実行する必要が生じたときにパフォーマンスの問題に遭遇し、Jurassic に切り替えました。JavaScriptEngineSwitcher を使えば、プロジェクトに新しいパッケージを追加し、いくつかの初期化コードを変更するだけで簡単に切り替えられます。
私は最近 JavaScriptEngineSwitcher ライブラリを発見しましたが、最新バージョンのダウンロード数は100万近くに達しており、.NET静的サイトジェネレーター Statiq で使用されています。この記事の最後のセクションでは、最も基本的な使用例を示します。
4 事例: JavaScriptEngineSwitcher を使ってコンソールアプリで prism.js を実行する
この記事の冒頭で、特定のシナリオ、つまりコードブロックのシンタックスハイライトについて説明しました。このセクションでは、prism.js を使用して小さなコード片をハイライトし、コンソールアプリケーション内で実行する方法を示します。
最初に JavaScriptEngineSwitcher.Jurassic NuGet パッケージへの参照を追加します。
dotnet add package JavaScriptEngineSwitcher.Jurassic
次に、実行したいJavaScriptファイルをダウンロードします。例えば、Prism.jsの公式サイトからprism.jsファイルをダウンロードし、C#をデフォルトでサポートするハイライト言語セットに追加しました。ファイルをプロジェクトフォルダのルートに置いた後、埋め込みリソースとして更新しました。IDEで行うか、プロジェクトファイルを手動で編集できます:
<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>
<!-- 👇 prism.js を埋め込みリソースにする -->
<ItemGroup>
<None Remove="prism.js" />
<EmbeddedResource Include="prism.js" />
</ItemGroup>
</Project>
あとは、プログラム内でスクリプトを実行するコードを書くだけです。次のコードスニペットは、JavaScriptエンジンをセットアップし、アセンブリから埋め込まれた prism.js ライブラリを読み込み、実行します。
using JavaScriptEngineSwitcher.Jurassic;
// JavaScriptエンジンのインスタンスを作成
IJsEngine engine = new JurassicJsEngine();
// 指定されたアセンブリから埋め込みリソース JsInDotnet.prism.js を実行
engine.ExecuteResource("JsInDotnet.prism.js", typeof(Program).Assembly);
これで、同じコンテキスト内で独自のJavaScriptコマンドを実行できます。SetVariableName、Execute、Evaluate を使用して、C#からJavaScriptエンジンに値を渡すことができます:
// ハイライトしたいコード
string code = @"
using System;
public class Test : ITest
{
public int ID { get; set; }
public string Name { get; set; }
}";
// JavaScript変数 "input" にC#変数 "code" の値を設定
engine.SetVariableValue("input", code);
// JavaScript変数 "lang" に文字列 "csharp" を設定
engine.SetVariableValue("lang", "csharp");
// Prism.highlight() 関数を実行し、結果を "highlighted" 変数に設定
engine.Execute($"highlighted = Prism.highlight(input, Prism.languages.csharp, lang)");
// JavaScriptの "highlighted" の値をC#に抽出
string result = engine.Evaluate<string>("highlighted");
Console.WriteLine(result);
これらを一緒に実行すると、ハイライトされたコードがコンソールに出力されます:
<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>
レンダリング後は次のようになります:

プロセス全体のシンプルさに驚きました。JavaScriptエンジンを起動し、prism.js ファイルを読み込み、カスタムコードを実行するのがとてもスムーズでした。これは私が直面した問題に対する完璧な解決策でした。
すべてのアプリケーションでこれを行うことを明らかに推奨するわけではありません。大量のJavaScriptを実行する必要がある場合は、Node.js エコシステムとツールを直接使用する方が簡単でしょう。しかし、小さなスタンドアロンツール(prims.js など)だけを利用したい場合は、これは良い選択肢です。
5 まとめ
この記事では、JavaScriptEngineSwitcher NuGet パッケージを使用して、.NETアプリケーション内でJavaScriptを実行する方法を示しました。このパッケージは、多くの異なるJavaScriptエンジンに対して一貫したインターフェースを提供します。これらのエンジンの一部(Chakra Core や V8 など)はネイティブコンポーネントに依存しますが、他のエンジン(Jint や Jurassic など)はマネージドコードのみを使用します。最後に、JavaScriptEngineSwitcher を使用して .NETアプリケーション内で Prism.js コードハイライトライブラリを実行する方法を示しました。
原文:https://andrewlock.net/running-javascript-in-a-dotnet-app-with-javascriptengineswitcher/
作者:Andrew Lock
翻訳:精致码农