SpawnDev.BlazorJS

repository·main·Indexed 41 days ago

https://github.com/lostbeard/spawndev.blazorjs

A library providing over 1,000 strongly typed C# wrappers for browser APIs, allowing Blazor WebAssembly developers to interact with the DOM, WebGL, WebCrypto, WebGPU, and WebRTC using C# instead of JavaScript interop. Supports .NET 8, 9, and 10. Features include the BlazorJSRuntime service, JSObject base classes, and type-safe event subscriptions via ActionEvent.

Tokens
446.2K
Snippets
1.3K
Records
1.8K
Agent score
99%

What's inside SpawnDev.BlazorJS

  1. Use VideoEncoder for hardware-accelerated video encoding

    main
    The VideoEncoder interface (part of the WebCodecs API) is used to encode VideoFrame objects into encoded video chunks. It provides hardware-accelerated encoding suitable for real-time applications. It is available in the SpawnDev.BlazorJS.JSObjects namespace and inherits from JSObject.
  2. Use HTMLImageElement to manipulate <img> elements

    main

    The HTMLImageElement class in the SpawnDev.BlazorJS.JSObjects namespace represents an HTML <img> element. It allows you to create, configure, and manipulate images within the DOM using C#.

    Key Concepts

    • Namespace: SpawnDev.BlazorJS.JSObjects
    • Inheritance: Inherits from HTMLElement.
    • C# Conventions: While the underlying Web API uses camelCase, SpawnDev.BlazorJS uses PascalCase for properties and methods (e.g., src becomes Src, decode() becomes Decode()).
    • Async Pattern: Methods returning JavaScript Promises return Task or Task<T> in C#.
    • Lifecycle: Objects should be disposed using using statements to manage resources correctly.
    using SpawnDev.BlazorJS.JSObjects;
    
    // Example of creating an image element
    var img = new HTMLImageElement();
    img.Src = "image1.png";
    img.Alt = "description";
    // Note: You would typically append this to a parent element via its DOM methods
  3. What is HeapView and how does it enable zero-copy data sharing?

    main

    A HeapView pins .NET arrays or strings in WASM linear memory using GCHandle. It then creates JavaScript TypedArray or DataView views that point directly at that pinned memory. This allows for zero-copy data sharing: when JavaScript reads or writes to the view, it is modifying the exact same bytes used by the .NET array. This is the primary mechanism for high-performance data transfer in SpawnDev.BlazorJS.

    // Pin a float array for zero-copy JS access
    float[] data = new float[] { 1.0f, 2.0f, 3.0f, 4.0f };
    using var heap = new HeapView<float>(data);
    
    // Zero-copy view - JS reads/writes the same memory as .NET
    using var jsView = heap.As<Float32Array>();
    // jsView[0] === 1.0, jsView[1] === 2.0, etc.
  4. Overview of SpawnDev.BlazorJS Core Types

    main

    The library provides several foundational types to facilitate seamless C# to JavaScript interop:

    • BlazorJSRuntime: The central service for global JS access, scope detection, and script loading.
    • JSObject: The base class for all 1,021+ typed JavaScript wrappers. It handles constructors, properties, methods, and disposal.
    • Callback: Allows .NET methods to be called from JavaScript. It is ref-counted and disposable.
    • ActionEvent / FuncEvent: Provides type-safe event subscription using standard C# += and -= operators.
    • Union<T1...T8>: Implements TypeScript-style discriminated unions with Match, Map, and Reduce capabilities.
    • Undefinable<T>: A utility to distinguish between JavaScript null and undefined.
    • EnumString: Enables bidirectional mapping between C# enums and JavaScript string constants.
    • DynamicJSObject: A DynamicObject wrapper that allows for JavaScript-like dynamic property access in C#.
  5. What is a Union and how to use it

    main

    The Union type provides TypeScript-style discriminated union types for C#. A Union<T1, T2, ...> can hold exactly one value of any of the specified types. This is particularly useful for interacting with JavaScript APIs that accept multiple types for a single parameter (e.g., a function that accepts either a string or a Node).

    Unions support:

    • Implicit conversions: You can assign a value of type T1 directly to a Union<T1, T2>.
    • Explicit casts: You can cast a Union back to its underlying types.
    • Pattern matching: Use .Match() to execute logic based on the contained type.
    • Type checking: Use .Is<T>() to safely check and extract values.
    • Serialization: They work seamlessly with the SpawnDev.BlazorJS JSON serialization system.
    // Implicit conversion from types to Union
    Union<string, int> u1 = "hello";
    Union<string, int> u2 = 42;
    
    // Explicit conversion from Union to types
    string s = (string)u1;
    int i = (int)u2;
  6. Use AudioWorkletNode for custom audio processing

    main

    The AudioWorkletNode in the SpawnDev.BlazorJS.JSObjects namespace allows you to create user-defined AudioNodes that can be part of an audio routing graph. This is used to run custom audio processing code in a separate thread via an AudioWorkletProcessor.

    Key Concepts

    • Processor Registration: You must first register an AudioWorkletProcessor in a JavaScript module using registerProcessor(name, processorClass).
    • Module Loading: Use the BaseAudioContext.audioWorklet.addModule("path/to/module.js") method to load the processor into the context.
    • Node Creation: Instantiate the AudioWorkletNode by providing the BaseAudioContext and the string name used during registration.
    • Communication: Use the Port property (MessagePort) for bidirectional communication between the C# side (via the node) and the underlying processor.
    // Conceptual C# usage pattern
    await audioContext.AudioWorklet.AddModule("random-noise-processor.js");
    
    using var randomNoiseNode = new AudioWorkletNode(audioContext, "random-noise-processor");
    randomNoiseNode.Connect(audioContext.Destination);
  7. Use MediaKeys in BlazorJS

    main

    The MediaKeys class in the SpawnDev.BlazorJS.JSObjects namespace provides a C# wrapper for the Web API MediaKeys object, used for managing digital rights management (DRM) and content protection.

    When using MediaKeys in BlazorJS, follow these C# conventions instead of the standard JavaScript patterns:

    • PascalCase: Properties and methods use PascalCase (e.g., readyState becomes ReadyState).
    • Events: Use ActionEvent with standard C# event syntax (+= or -=).
    • Async Methods: Methods that return a JavaScript Promise<T> will return a Task<T> in C#.
    • Lifecycle: Always dispose of MediaKeys objects using using statements to prevent memory leaks.
    • Instantiation: Access an existing instance via JS.Get<MediaKeys>(...) or create a new one using the constructor.
    using SpawnDev.BlazorJS.JSObjects;
    
    // Example of typical usage pattern
    using (MediaKeys mediaKeys = await mediaKeySystemAccess.CreateMediaKeysAsync()) 
    {
        // Use mediaKeys here
    }
  8. How ActionEvent handles memory and callbacks

    main

    The ActionEvent system is designed to automate the lifecycle of JavaScript event listeners.

    The Lifecycle

    When you use the += operator on an ActionEvent property:

    1. A Callback is created that wraps your .NET delegate (or its reference count is incremented if it already exists).
    2. The system calls the underlying JavaScript addEventListener with the event name and the Callback.

    When you use the -= operator:

    1. The system looks up the existing Callback associated with your specific delegate.
    2. It calls the underlying JavaScript removeEventListener.
    3. It decrements the Callback's reference count, disposing of it if the count reaches zero.

    Implementation Pattern

    JSObject subclasses typically implement ActionEvent properties like this:

    public ActionEvent<Event> OnChange
    {
        get => new ActionEvent<Event>("change", AddEventListener, RemoveEventListener);
        set { }
    }

    Note: The empty set { } is required to allow the += and -= operators to compile.

  9. Mapping JavaScript Web APIs to SpawnDev.BlazorJS

    main

    When working with Web APIs like ReadableStreamDefaultReader in SpawnDev.BlazorJS, apply these mapping rules:

    • Casing: Use PascalCase for properties and methods (e.g., readyState $\rightarrow$ ReadyState).
    • Async/Await: JavaScript Promise<T> becomes C# Task<T>.
    • Events: Use ActionEvent with standard C# event syntax (+= / -=) instead of addEventListener (e.g., OnClick += handler).
    • Lifecycle: Use using statements for object disposal.
    • Acquisition: Use JS.Get<T>(...) or the deserialization constructor.
  10. Mapping Web API to BlazorJS conventions

    main

    When using Web APIs like MediaList in BlazorJS, follow these C# conventions instead of the standard JavaScript patterns:

    • PascalCase: Properties and methods use PascalCase (e.g., mediaText becomes MediaText).
    • Events: Use ActionEvent with += or -= syntax (e.g., OnClick += handler).
    • Async: Async methods return Task<T> instead of Promise<T>.
    • Lifecycle: Objects should be disposed using using statements.
    • Instantiation: Access objects via JS.Get<T>(...) or the provided deserialization constructors.
  11. Detect the current JavaScript scope

    main

    Because BlazorJSRuntime can run in different environments (Main Window, Dedicated Worker, Shared Worker, or Service Worker), you should check the scope before accessing environment-specific properties.

    Use the following boolean properties:

    • IsWindow: True if running in the main browser window.
    • IsWorker: True if running in any worker context.
    • IsDedicatedWorkerGlobalScope: True if in a dedicated worker.
    • IsServiceWorkerGlobalScope: True if in a service worker.

    Use the corresponding properties to access the scope-specific objects:

    • WindowThis: Access Window properties.
    • DedicateWorkerThis: Access DedicatedWorkerGlobalScope properties.
    • ServiceWorkerThis: Access ServiceWorkerGlobalScope properties.
    if (JS.IsWindow)
    {
        // Running in the main browser window
        var doc = JS.WindowThis!.Document;
    }
    else if (JS.IsDedicatedWorkerGlobalScope)
    {
        // Running in a dedicated web worker
        JS.DedicateWorkerThis!.PostMessage("worker ready");
    }
    else if (JS.IsServiceWorkerGlobalScope)
    {
        // Running in a service worker
    }
  12. Mapping JavaScript ResizeObserver to SpawnDev.BlazorJS

    main

    When porting JavaScript ResizeObserver logic to SpawnDev.BlazorJS, apply the following mapping rules:

    FeatureJavaScriptSpawnDev.BlazorJS
    NamingcamelCase (e.g., readyState)PascalCase (e.g., ReadyState)
    EventsaddEventListener("click", fn)ActionEvent with +=/-= (e.g., OnClick += handler)
    AsyncReturns Promise<T>Returns Task<T>
    LifecycleGarbage CollectionUse using statements for disposal
    Instantiationnew ObserveOptions(...)new ObserveOptions(...) or JS.Get<ObserveOptions>(...)