v3.2.0
ES-module execution now follows standard JS semantics — top-level code runs once per unique script on a given engine, exports are cached. Unlocks a ~115× speed-up for repeated import-based scripts on pooled engines. New PrepareModule API mirrors the existing Prepare for ES modules.
Added
JsEngine.PrepareModule(string)→JsPreparedModule— pre-parse an ES module script once, reuse across calls. Thread-safe, cacheable globally.JsEngine.ExecuteAsync(JsPreparedModule)— overload accepting a pre-parsed module. Cached by reference identity.
Changed (behavioural)
ExecuteAsync(string)/ExecuteAsync(JsPreparedModule)follow standard ES-module semantics. Top-level code runs once per unique script content per engine. Repeated calls return the cached module namespace instead of re-parsing + re-evaluating. Matches Node / browsers / Deno.- Migration: scripts relying on top-level re-execution (e.g.
export const id = Math.random()yielding a new id per call) must expose per-call work as exported functions invoked viaInvokeFunction. For true per-call re-execution, use the lightweight path (Evaluate/EvaluateAsync) which has no module system. - Module registry memory leak fixed — the previous
__main_0__,__main_1__, … naming accumulated one entry per call in Jint's module dict. Now O(unique scripts) instead of O(calls).
Fixed
- Benchmark harness — scoped
JsEnginewas resolved from the root provider + disposed after each iteration, so every iteration after the first hitObjectDisposedExceptionon async paths. All benchmarks now open a fresh DI scope per iteration. Side effect:EngineBenchmarks.AsyncAwaitandValueBenchmarks.TaskInteropnow produce real numbers instead ofNA; "Engine creation (cold start)" now measures ~9.8 µs instead of the ~1.5 µs DI cache lookup.
Performance (pooled engine, hot loop)
| Scenario | Before | After | Factor |
|---|---|---|---|
ExecuteAsync(string) — repeated same script |
14 µs | 123 ns | 115× |
ExecuteAsync(prepared) — repeated |
12 µs | 1.3 µs | 9× |
Fresh-engine paths unchanged (nothing to cache on first use). Full table in PERFORMANCE-COMPARISON.md.
Example
// Prepare once (cacheable, thread-safe)
var preparedModule = JsEngine.PrepareModule("""
import * as common from 'common';
export function newGuid() { return common.Guid.New().toString(); }
""");
// Per request (pooled engine via scoped DI)
await engine.ExecuteAsync(preparedModule); // 35 µs first call, 1.3 µs subsequent
var id = engine.InvokeFunction("newGuid"); // fresh GUID each call — as intendedTests: 329 green (Engine 148, Linq 134, TypeScript 29, Modules 18).