Jint JavaScript Interpreter

repository·main·Indexed 26 days ago

https://github.com/sebastienros/jint

A high-performance JavaScript interpreter for .NET that supports ECMAScript standards from ES6 through ES2025. Jint is designed for embedding in .NET applications to provide sandboxed scripting, user customization, and seamless interop between .NET objects and JavaScript code. It supports .NET Standard 2.0 and .NET 4.6.2 targets and later.

Tokens
7.4K
Snippets
16
Records
34
Agent score
87%

What's inside Jint

  1. Overview of Jint JavaScript Interpreter

    main

    Jint is a JavaScript interpreter for .NET that runs on any modern .NET platform. It supports .NET Standard 2.0 and .NET 4.6.2 targets and later.

    Key use cases include:

    • Running JavaScript in a safe, sandboxed environment within .NET applications.
    • Exposing native .NET objects and functions to JavaScript (e.g., returning database results as JSON or calling .NET methods).
    • Providing scripting capabilities to allow users to customize applications.
  2. Understand Script ↔ Host Interop Performance

    main

    When embedding a JavaScript engine, the performance of the boundary between the script and the host application (interop) is often more critical than pure computation.

    Key Interop Characteristics for Jint:

    • Managed Dispatch: Unlike native engines (like V8 via ClearScript) that must cross a managed ↔ native boundary and marshal arguments, Jint is a managed engine that dispatches host calls in-process.
    • String Passing: Jint leads in string passing performance among the compared engines.
    • Low Allocation: Jint allocates significantly less memory than its competitors during interop operations (roughly 4×–12× less than the nearest managed competitor).
    • Collection Traversal: Since version 4.14.0, Jint uses ArrayConversionMode.LiveView by default. This exposes host arrays as a live view instead of re-copying them on every read, significantly improving performance and reducing allocations for collection traversal tasks. The previous workaround of hoisting arrays into local variables is no longer necessary.
  3. Handle sparse data with NullPropagatingReferenceResolver

    main

    When scripts access deep optional chains (e.g., input.Address.City), use an IReferenceResolver to prevent exceptions on nullish bases.

    For optimal performance, register the built-in NullPropagatingReferenceResolver.Instance and pass ReferenceResolverInterests.NullishPropertyBase. This allows the engine to handle propagation via an optimized inline lane rather than an interface call.

  4. Optimize JavaScript execution performance

    main

    To achieve high performance when running JavaScript in Jint, follow these best practices:

    1. Pre-prepare scripts: If you are repeatedly running the same script, do not pass the raw string to the engine every time. Instead, use Engine.PrepareScript or Engine.PrepareModule to create a Prepared<...> object. Cache this object and pass it to the engine for subsequent executions.
    2. Use Strict Mode: Running the engine in strict mode improves execution performance.
  5. Reuse prepared scripts across multiple engines

    main

    To improve startup performance, use Engine.PrepareScript or Engine.PrepareModule. These return a Prepared<T> object that is both reusable and thread-safe. You can prepare a script once and feed it to many different engine instances across multiple threads.

    Note: While the prepared script is thread-safe, the engine's per-node caches are engine-owned and only engage on the second evaluation of a script on a specific engine instance.

  6. Secure the JavaScript environment with sandboxing

    main

    Jint provides several features to create a secure, sandboxed environment for running untrusted user scripts:

    • Memory Limits: Define limits to prevent allocations from depleting system memory.
    • BCL Access Control: Enable or disable usage of the Base Class Library (BCL) to prevent scripts from invoking .NET code.
    • Statement Limits: Limit the number of statements to prevent infinite loops.
    • Call Depth Limits: Limit the depth of calls to prevent deep recursion.
    • Timeouts: Define a timeout to prevent scripts from running for too long.
  7. Implement custom Temporal and Intl providers

    main

    You can extend Jint's Temporal and Intl capabilities by implementing custom providers. The repository provides working examples that can be copied into your own projects:

    • Temporal TimeZoneProvider: Use NodaTimeZoneProvider.cs as a template. This requires the NodaTime NuGet package.
    • Intl CLDRProvider: Use IcuCldrProvider.cs as a template. This requires the ICU4N NuGet package.

    To use these providers, register them on the Engine.Options properties:

  8. Extend Temporal and Intl with custom providers

    main

    Jint uses minimal defaults for Temporal and Intl to keep the binary size small. To support full IANA timezone history or non-English CLDR data (locales, currencies, etc.), you must provide custom implementations of ITimeZoneProvider and ICldrProvider.

    Commonly used implementations include NodaTimeZoneProvider (using NodaTime) and IcuCldrProvider (using ICU4N).

    var engine = new Engine(options =>
    {
        options.Temporal.TimeZoneProvider = new NodaTimeZoneProvider();
        options.Intl.CldrProvider          = new IcuCldrProvider();
    });
  9. Run the Engine Comparison Benchmarks

    main

    To benchmark Jint against other .NET JavaScript engines (NiL.JS, Okojo, YantraJS, and ClearScript), run the following command from the Jint.Benchmark directory. The -- separator is required to forward arguments to BenchmarkDotNet.

    Use the following flags to control the scope of the benchmark:

    • --allCategories EngineComparison: Runs both the script suite and the interop suite.
    • --allCategories EngineComparisonInterop: Runs only the interop suite.
    • --filter "*EngineComparisonBenchmark*": Runs only the script suite.
    • --filter "*EngineComparisonBenchmark.[EngineName]*": Runs a single engine (e.g., --filter "*EngineComparisonBenchmark.Okojo*").
    dotnet run -c Release -- --allCategories EngineComparison
  10. Run Engine Comparison Interop Benchmarks

    main

    To run the benchmarks specifically focused on the script ↔ host interop boundary, use the following command:

    --allCategories EngineComparisonInterop

    This benchmark suite executes four identical scripts to measure:

    1. Host method-call loop
    2. Host property read/write loop
    3. Strings crossing the boundary
    4. Traversal of a host int[]
  11. Optimize global setup using Global Snapshots

    main

    If you need a clean global state for each evaluation but want to avoid the cost of re-running setup code, use CaptureGlobalSnapshot() and RestoreGlobalSnapshot(snapshot).

    1. Perform all SetValue calls and module setups.
    2. Call engine.Advanced.CaptureGlobalSnapshot() to save the state.
    3. Between evaluations, call RestoreGlobalSnapshot(snapshot).

    Important Considerations:

    • Isolation: This is a configuration-reuse primitive, NOT an isolation boundary. Mutations to Object.prototype, registered modules, or host CLR state will survive a restore. Use separate engines for mutually distrusting scripts.
    • Async/Promises: Restoring a snapshot discards queued jobs. Any promise registered before the restore will be dropped when it settles.
    • Error Handling: Always wrap the restore in a finally block to ensure the engine is reset even if a script throws.
  12. Apply execution constraints to scripts

    main

    To prevent scripts from consuming excessive resources, you can apply execution constraints such as memory limits, timeouts, or statement counts.

    Constraints can be configured via built-in options or by implementing a custom Constraint class and overriding Reset() and Check().

    var engine = new Engine(options => {
        options.LimitMemory(4_000_000); // 4 MB
        options.TimeoutInterval(TimeSpan.FromSeconds(4));
        options.MaxStatements(1000);
        options.CancellationToken(cancellationToken);
    });
    
    // Using a custom constraint
    public abstract class Constraint
    {
        public abstract void Reset();
        public abstract void Check(); // Throws exception if requirements aren't met
    }
    
    var engine = new Engine(options =>
    {
        options.Constraint(new MyCPUConstraint());
    });