Introducción a WebAssembly y C#: de la teoría a la práctica
\nWebAssembly (WASM) se ha convertido en el estándar para aplicaciones web de alto rendimiento. El ecosistema .NET ofrece potentes herramientas para compilar código C# a WASM, abriendo posibilidades para trasladar algoritmos complejos, bibliotecas e incluso frameworks completos al navegador. A nivel avanzado, trabajar con WebAssembly y C# requiere comprender no solo la compilación básica, sino también los detalles de la gestión de memoria, la interacción con JavaScript (JS Interop), la optimización del tamaño del archivo binario y la depuración.
\nEn este artículo, exploraremos los aspectos clave que te permitirán ir más allá de los ejemplos simples y crear aplicaciones WASM eficientes y escalables en C#.
\n\n1. Compilación e implementación de .NET en WebAssembly: métodos avanzados
\nPara compilar C# a WASM se utiliza el entorno de ejecución Mono/WASM, integrado en Blazor WebAssembly o disponible a través de .NET WASM Build Tools. A nivel avanzado, es importante controlar el proceso de compilación.
\n\n1.1. Optimización del tamaño de la compilación y compilación AOT
\nPor defecto, Blazor utiliza compilación JIT dentro del entorno WASM, lo que resulta en la carga completa del entorno Mono (~2-3 MB comprimido). Para reducir el tamaño, se puede utilizar la compilación AOT (Ahead-of-Time). Esta compila tu código C# directamente en instrucciones WASM, eliminando la necesidad de JIT.
\n<!-- En el archivo .csproj -->\n<PropertyGroup>\n <RunAOTCompilation>true</RunAOTCompilation>\n <WasmDebugLevel>0</WasmDebugLevel>\n <WasmNativeStrip>true</WasmNativeStrip>\n</PropertyGroup>\nSin embargo, AOT aumenta el tiempo de compilación y el tamaño del archivo final (pero reduce el tamaño del entorno cargado). Configura WasmDebugLevel y WasmNativeStrip para eliminar la información de depuración.
1.2. Carga diferida y división de ensamblados
\nPara aplicaciones grandes, utiliza la Carga diferida (Lazy Loading). Puedes cargar bibliotecas .NET solo cuando se necesiten. En Blazor, esto se hace a través de LazyAssemblyLoader:
@inject LazyAssemblyLoader Loader\n\n@code {\n private async Task LoadHeavyLibrary()\n {\n var assemblies = await Loader.LoadAssembliesAsync(new[] { "HeavyLibrary.dll" });\n // Ahora la biblioteca está disponible\n }\n}\nEsto es crítico para SEO y UX, ya que la carga inicial sigue siendo rápida.
\n\n2. Interacción con JavaScript (JS Interop) a nivel profundo
\nEl trabajo avanzado con WebAssembly requiere un intercambio eficiente de datos entre C# y JS. El estándar IJSRuntime funciona mediante serialización JSON, lo que es lento para grandes volúmenes de datos.
2.1. Trabajo directo con memoria mediante Span e IntPtr
\nUtiliza System.Runtime.InteropServices.JavaScript (disponible en .NET 7+). Puedes obtener punteros a arrays de JS y trabajar con ellos directamente, evitando copias.
using System.Runtime.InteropServices.JavaScript;\n\n// Exportar función de C# a JS\n[JSExport]\npublic static unsafe int ProcessData(int* dataPtr, int length)\n{\n Span<byte> span = new Span<byte>(dataPtr, length);\n // Procesamiento de alto rendimiento en el lugar\n return span.Length;\n}\n\n// Importar función desde JS\n[JSImport("window.processResult", "MyModule")]\npublic static partial void ProcessResult(int result);\nEste enfoque permite procesar imágenes, audio o grandes arrays de datos (por ejemplo, de WebGL) sin la sobrecarga del marshaling.
\n\n2.2. JS Interop síncrono
\nPor defecto, las llamadas a JS desde WASM son asíncronas. Para escenarios que requieren sincronía (por ejemplo, en bucles de juegos), utiliza [JSImport] con IsBlocking = true (disponible en .NET 8+). Esto permite bloquear el hilo WASM hasta recibir una respuesta de JS.
[JSImport("getTimestamp", "MyModule", IsBlocking = true)]\npublic