Alethic.AspNetCore.EcmaScript.Node 0.2.0

dotnet add package Alethic.AspNetCore.EcmaScript.Node --version 0.2.0
                    
NuGet\Install-Package Alethic.AspNetCore.EcmaScript.Node -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Alethic.AspNetCore.EcmaScript.Node" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Alethic.AspNetCore.EcmaScript.Node" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Alethic.AspNetCore.EcmaScript.Node" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Alethic.AspNetCore.EcmaScript.Node --version 0.2.0
                    
#r "nuget: Alethic.AspNetCore.EcmaScript.Node, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Alethic.AspNetCore.EcmaScript.Node@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Alethic.AspNetCore.EcmaScript.Node&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Alethic.AspNetCore.EcmaScript.Node&version=0.2.0
                    
Install as a Cake Tool

Alethic.AspNetCore.EcmaScript.Node

Server-side rendering on a real Node runtime embedded in the .NET process, through node-api-dotnet. No sidecar process, no HTTP hop.

Two things register into DI: the engine pool, and rendering engines on it.

builder.Services.AddNodeEnginePool(o =>
{
    o.EngineCount = 4;                 // must track the CPU limit; see remarks on the option
    o.MaxConcurrencyPerEngine = 4;     // backpressure, not mutual exclusion
});
builder.Services.AddNodeRenderEngine(o =>
{
    o.Module = NodeModuleSource.FromFile("ssr/server.cjs");
    o.Environment["ApiBaseUri"] = "http://api.internal:8080/";
});

var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();

app.MapRenderEngine();                 // from Alethic.AspNetCore.EcmaScript

await app.RunAsync();

Both registrations are keyable, for applications running several pools or engines; a rendering engine names its pool with PoolKey.

The application module

A self-contained CommonJS bundle following the module-worker convention — the export default { fetch } shape shared by Cloudflare Workers, Deno, Bun, and the frameworks that target them. Everything is optional except fetch:

export default {
    async init(env) { /* optional: awaited once per engine, before anything else */ },
    fetch(request, env) { /* return a Response; sync or async */ },
    routes() {
        return [
            { pattern: '/parks/:parkRef', renderMode: 'Server' },
            { pattern: '/profile', renderMode: 'Client' },   // never touches the engine
        ];
    },
};
  • fetch(request, env) — the Web-standard handler. A bare function as the default export is accepted too, which is what createRequestHandler-style factories produce. env carries the values supplied through NodeRenderEngineOptions.Environment — what only the host knows.
  • init(env) — awaited once per engine before the first render. It exists because a CommonJS bundle cannot top-level-await; a failing init fails the deployment.
  • routes() — the manifest, patterns in URLPattern pathname syntax. Frameworks with entries in this shape already (Hono and friends) work verbatim; the rest wrap in a few lines.

The pool on its own

The pool is a concrete facility, not an abstraction: it is libnode, on purpose, and usable for any JavaScript work. A one-shot puts you on an engine's thread writing ordinary node-api-dotnet:

var pool = provider.GetRequiredService<NodeEnginePool>();

var result = await pool.RunAsync(NodeModuleSource.FromFile("tool.cjs"), async exports =>
    (int)await ((JSPromise)exports.CallMethod("transform", input)).AsTask());

For several steps that must share one engine — per-engine module state, or a claim that outlives a single call — take a lease instead: await using var lease = await pool.AcquireAsync(); and run against it. A lease is a capacity claim and an affinity pin, not exclusivity; engines overlap many concurrent calls regardless.

Constraints worth knowing

  • Reference the RID-specific Microsoft.JavaScript.LibNode.<rid> package for each runtime you deploy to. The umbrella package depends on every platform at once and lands ~640 MB of native libraries in the output.
  • Engine count must be configured, never derived. Inside a container the processor count reports the host's cores, not the quota. One engine already overlaps many concurrent renders, because everything a module awaits yields to its event loop; engines exist for CPU parallelism.
  • CommonJS only. The embedded runtime registers no dynamic-import callback, so ES modules and import() do not resolve. Bundle fully static — for esbuild, --format=cjs with code splitting off.
  • Responses stream. A failure after the first byte can truncate the body but cannot change the status.
  • Cancellation aborts the render, through AbortSignal on the request, not merely the wait.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 81 8/23/2026
0.1.1 74 8/23/2026
0.1.0 76 8/23/2026