前幾天我在做一個副業,意識到我需要使用一些 JavaScript 功能。一想到要再次處理 Node.js 和 npm,我就完全放棄了,所以我決定研究一下在 .NET 應用程式中執行 JavaScript 的可能性。很瘋狂吧?實際上,這出乎意料地簡單。
1. 你為什麼要這麼做?
儘管我很喜歡 .NET 生態系統,但有些事情,JavaScript 生態系統做得更好。其中之一就是任何事都能找到一個函式庫,特別是涉及到網路時。
以語法高亮為例。這可以直接用 C# 來做,但這不是一個特別流暢的體驗。例如,TextMateSharp 專案為 TextMate 語法提供了一個直譯器。這些檔案是 VS Code 用來為一種語言加入基本語法高亮的。然而,如果你想部署應用程式,它包裝了一個本地依賴,這就增加了一些複雜性。
相比之下,JavaScript 有大量成熟的語法高亮函式庫。僅舉幾例,有 highlight.js、Prism.js(在本部落格中使用)和 shiki.js。尤其是前兩個,非常成熟,有多個外掛和主題,而且有簡單的 API。
作為一個 .NET 開發者,JavaScript 的明顯問題是,你需要學習並選擇進入一個完整的獨立工具鏈,與 Node.js 和 NPM 一起工作。這似乎是一個很大的開銷,只是為了使用一個小功能。
因此,我們陷入了一個困境。我們要嘛走 C#(+ Native)路線,要嘛就得轉用 JavaScript。
或者......我們直接從我們的 .NET 應用程式中呼叫 JavaScript 🤯
2. 在 .NET 中執行 JavaScript
一旦你決定在你的 .NET 程式碼中執行 JavaScript,你就會考慮幾個選擇。你可以借用 JavaScript 引擎,讓它為你執行你的 JavaScript,但你並沒有真正解決問題,你仍然需要安裝 Node.js。
另一個選擇是在你的函式庫中直接捆綁 JavaScript 引擎。這並不像聽起來那麼瘋狂,有幾個 NuGet 套件採用了這種方法,然後公開一個 C# 層來與引擎進行互動。
下面是你可以使用的一些套件的清單。
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 是另一個 JavaScript 引擎的 .NET 實作,類似於 Jint。也和 Jint 類似,它支援所有的 ES5,而且似乎也部分支援 ES6。與 Jint 不同的是,Jurassic 不是一個直譯器,它將 JavaScript 編譯成 IL,這使得它的速度非常快,而且它沒有本地的依賴性。
那麼,在所有這些選擇中,你應該選擇哪一個?
3 JavaScriptEngineSwitcher:當一個 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 引擎,我也傾向於盡可能地使用 JavaScriptEngineSwitcher 封裝函式庫,這樣你就不必在以後需要切換引擎時弄清楚一個新的 API 了。
在 .NET 專案中改變使用的 JavaScript 引擎在我看來是完全可能的。例如,我開始使用 Jint,但當我需要執行更大的腳本時,我遇到了效能問題,於是換成了 Jurassic。JavaScriptEngineSwitcher 讓這一切變得很簡單,只需在我的專案中新增一個新的套件並改變一些初始化程式碼即可。
我最近才發現 JavaScriptEngineSwitcher 這個函式庫,但最新版本的下載量已接近一百萬,它被用於 .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>
<!-- 👇 Make prism.js an embedded resource -->
<ItemGroup>
<None Remove="prism.js" />
<EmbeddedResource Include="prism.js" />
</ItemGroup>
</Project>
剩下的就是撰寫程式碼,在我們的程式中執行腳本。下面的程式碼片段設定了 JavaScript 引擎,從組件中載入嵌入的 prism.js 函式庫,並執行它。
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);
現在我們可以在同一個上下文中執行我們自己的 JavaScript 命令。我們可以透過使用 SetVariableName、Execute 和 Evaluate 從 C# 向 JavaScript 引擎傳遞數值:
// 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);
當你把它們放在一起執行時,高亮的程式碼會列印到主控台:
<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 應用程式內部執行 Prims.js 程式碼高亮函式庫。
原文:https://andrewlock.net/running-javascript-in-a-dotnet-app-with-javascriptengineswitcher/
作者:Andrew Lock
翻譯:精緻碼農