Blazor WASM e o Futuro da Web
WASM é o futuro da web, e neste artigo vamos entender por que esse tema ficou tão em alta no fim de 2022 e quais tópicos você deve acompanhar sobre o assunto. Além disso, vamos ver como o ecossistema .NET começou a levar o WebAssembly para além do navegador, com o WASI, o Wasmtime e os primeiros SDKs experimentais.
O que é WASM e por que ele chamou atenção
WebAssembly, ou simplesmente WASM, é uma linguagem de baixo nível, parecida com assembly, que usa um formato binário e roda nos navegadores modernos. Por isso, programas compilados para esse formato executam em velocidade próxima à nativa.
Em 5 de dezembro de 2019, o W3C publicou o WebAssembly como Recomendação oficial. Segundo o comunicado, depois de HTML, CSS e JavaScript, ele se tornou a quarta linguagem da web capaz de rodar código no navegador.
O próprio W3C descreve o formato como seguro, portátil, de baixo nível e projetado para execução eficiente e representação compacta. Em outras palavras, ele funciona como alvo de compilação para bases de código que já existem.
Na prática, isso significa que você não escreve WASM à mão. Você escreve em C#, Rust, Go, C++ ou outra linguagem, e o compilador gera o módulo .wasm.
Para quem trabalha com .NET, o primeiro contato costuma acontecer pelo Blazor WebAssembly. Se você ainda não conhece o framework, vale ler uma visão geral do Blazor antes de seguir.
No vídeo abaixo, conversamos sobre por que o tema ganhou tanta força e quais pontos merecem a sua atenção.
WASM no .NET 7: onde a plataforma estava em 2022
O .NET 7 chegou em novembro de 2022 como uma versão de suporte padrão (STS), com forte foco em desempenho. Entre as melhorias, a Microsoft destacou ganhos no runtime Mono, que move o Blazor WebAssembly, o Android e o iOS.
Ao mesmo tempo, o WASM aparecia em duas frentes no .NET. A primeira era o navegador, com Blazor e a nova interop com JavaScript, enquanto a segunda era o servidor, com experimentos baseados em WASI.
Blazor WebAssembly e as melhorias do .NET 7
No .NET 7, o workload wasm-tools ganhou suporte a WebAssembly SIMD, que funcionava apenas com compilação AOT e não era suportado pelo Safari. Também passou a oferecer suporte ao tratamento de exceções do WebAssembly.
Além disso, o .NET 7 ampliou os algoritmos de criptografia disponíveis no WebAssembly. Quando possível, o runtime usa a API SubtleCrypto do navegador e recorre a uma implementação .NET quando ela não está disponível.
Se você quer entender a relação entre o framework e a visão de futuro da plataforma, o artigo Blazor e o futuro da web complementa bem este conteúdo.
JSImport e JSExport fora do Blazor
Outra novidade do .NET 7 foi a interop de baixo nível com os atributos [JSImport] e [JSExport]. Com ela, você chama JavaScript a partir do .NET e expõe métodos .NET para o JavaScript, sem depender do modelo de componentes do Blazor.
Por exemplo, a documentação do Microsoft Learn mostra como importar o console.log do escopo global:
using System.Runtime.InteropServices.JavaScript;
internal static partial class Interop
{
// Importa a função global console.log do JavaScript
[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string message);
// Expõe um método .NET para ser chamado pelo JavaScript
[JSExport]
internal static string GetGreeting() => "Hello from .NET";
}
Esse recurso exige a propriedade AllowUnsafeBlocks no projeto, porque o gerador de código do Roslyn usa ponteiros na interop. Então, o WASM do .NET deixava de ser exclusivo do Blazor e podia entrar em qualquer aplicação JavaScript.
Compilando um console .NET para WASM com Wasi.Sdk
Em 2022, Steve Sanderson publicou um SDK experimental para compilar aplicações .NET 7 e ASP.NET Core em apps WASI independentes. Depois, o projeto passou a viver no repositório dotnet-wasi-sdk, uma das referências deste artigo.
O pacote principal se chama Wasi.Sdk e produz um arquivo .wasm como saída do build. Ele exigia o .NET 7, a partir do 7.0.100-preview.4, e um runtime WASI como Wasmtime ou Wasmer no PATH.
O fluxo para um console é curto:
# Cria um console comum
dotnet new console -o MyFirstWasiApp
cd MyFirstWasiApp
# Adiciona o SDK experimental de WASI
dotnet add package Wasi.Sdk --prerelease
# Gera o arquivo .wasm junto com o build
dotnet build
# Executa o módulo com o Wasmtime
wasmtime bin/Debug/net7.0/MyFirstWasiApp.wasm
Repare que o código C# continua igual. O Program.cs com um Console.WriteLine("Hello, Wasmtime!"); vira um módulo WASM que roda em qualquer host WASI.
No exemplo da JetBrains, o projeto HelloWasm gerava o arquivo bin/Debug/net7.0/HelloWasm.wasm. Em seguida, o autor o executava pelo Wasmtime a partir do Rider.
Se você costuma criar projetos pelo terminal, o artigo sobre criar projetos .NET via console ajuda a fixar os comandos usados aqui.
ASP.NET Core rodando em WASI
O SDK também trazia o pacote Wasi.AspNetCore.Server.Native, que oferece rede TCP nativa para o ASP.NET Core sobre WASI. Assim, uma aplicação web completa podia virar um único arquivo WebAssembly.
A mudança no Program.cs era uma chamada encadeada no builder:
// Program.cs
var builder = WebApplication.CreateBuilder(args).UseWasiConnectionListener();
var app = builder.Build();
app.MapGet("/", () => "Hello from ASP.NET Core on WASI");
app.Run();
Depois, o launchSettings.json precisava declarar um único endpoint HTTP, como http://localhost:8080. Por fim, o arquivo de projeto informava ao Wasmtime qual porta liberar:
<PropertyGroup>
<!-- Argumentos repassados ao runtime WASI na execução -->
<WasiRunnerArgs>--tcplisten localhost:8080 --env ASPNETCORE_URLS=http://localhost:8080</WasiRunnerArgs>
</PropertyGroup>
Para servir arquivos estáticos embutidos no .wasm, havia ainda o pacote Wasi.AspNetCore.BundledFiles. Nesse caso, você trocava o middleware padrão por app.UseBundledStaticFiles() e incluía os arquivos com o item WasmBundleFiles.
<ItemGroup>
<!-- Embute a pasta wwwroot dentro do arquivo .wasm -->
<WasmBundleFiles Include="wwwroot\**" />
</ItemGroup>
Repare que o host continua no controle. Sem o argumento --tcplisten, o módulo WASM simplesmente não consegue escutar a porta.
Hospedando módulos WASM dentro de uma aplicação .NET
O caminho inverso também existe. Em vez de compilar .NET para WebAssembly, você pode usar uma aplicação .NET como host de módulos WASM.
O artigo da JetBrains mostra isso com a biblioteca do Wasmtime para .NET. Nele, um módulo escrito em WAT, o formato textual do WebAssembly, importa uma função implementada em C#:
using Wasmtime;
using var engine = new Engine();
// Módulo em WAT que importa "hello" e exporta "run"
using var module = Module.FromText(
engine,
"hello",
"(module (func $hello (import \"\" \"hello\")) (func (export \"run\") (call $hello)))"
);
using var linker = new Linker(engine);
using var store = new Store(engine);
// Liga a importação "hello" a um callback em C#
linker.Define(
"",
"hello",
Function.FromCallback(store, () => Console.WriteLine("Hello from C#!"))
);
var instance = linker.Instantiate(store, module);
var run = instance.GetAction("run")!;
run();
Ao executar, a saída é Hello from C#!. Ou seja, o módulo WebAssembly chama de volta o código .NET por meio do linker.
Esse exemplo usa o formato WAT apenas por didática. Na vida real, o módulo costuma vir de um arquivo .wasm compilado a partir de outra linguagem, como Rust ou C.
Esse padrão abre espaço para sistemas de plugins isolados. Afinal, cada módulo só enxerga as funções que o host decide expor.
Spin SDK e o WASM como modelo de microsserviço
Plataformas de hospedagem também começaram a apostar no formato. Segundo a JetBrains, a Fermyon lançou um Spin SDK para .NET voltado para a sua plataforma de hospedagem baseada em WASM.
O modelo lembra uma função serverless, com um handler HTTP decorado por atributo:
using System.Net;
using Fermyon.Spin.Sdk;
namespace Microservice;
public static class Handler
{
// Cada requisição HTTP chega neste método
[HttpHandler]
public static HttpResponse HandleHttpRequest(HttpRequest request)
{
return new HttpResponse
{
StatusCode = HttpStatusCode.OK,
BodyAsString = "Hello from .NET",
};
}
}
Note que não há Program.cs, builder nem pipeline de middlewares nesse exemplo. Toda a lógica fica concentrada no método marcado com [HttpHandler].
Nesse cenário, o módulo WASM é a unidade de deploy, e não um contêiner. Por isso, o artigo da JetBrains insiste que a infraestrutura terá papel essencial na forma como você constrói soluções com WASM.
Limitações do WASM no servidor em 2022
Apesar do entusiasmo, o cenário de 2022 ainda era experimental. O próprio artigo da JetBrains lista as principais barreiras, e vale conhecê-las antes de qualquer aposta.
Primeiro, a execução era de thread única. Nos testes do autor, o Wasmtime só tinha acesso a uma thread, e APIs que usam Thread ou Task.Delay podiam travar ou encerrar o processo.
Além disso, o runtime .NET para WASM era o mesmo do Blazor WebAssembly, com as mesmas limitações. Algumas APIs simplesmente não funcionavam por falta de suporte do host WASM.
Outro ponto crítico era a ausência de sockets de saída. Sem isso, uma aplicação WASM não conseguia conversar com dependências como um banco de dados ou um serviço web, embora já houvesse discussões sobre uma especificação de sockets.
O ferramental também era uma preocupação. Segundo o artigo, os fornecedores precisavam alcançar a experiência a que os desenvolvedores .NET estavam acostumados, embora já existisse suporte a depuração com GDB e LLDB.
Por fim, havia a questão arquitetural. Adotar WASM no servidor exige repensar a orquestração e as unidades de trabalho das aplicações existentes.
Mesmo no navegador havia detalhes de compatibilidade. Por exemplo, o SIMD do .NET 7 funcionava apenas com AOT e não tinha suporte no Safari.
Por que WASM é tratado como o futuro da web
Se as limitações eram tantas, por que tanta gente dizia que WASM era o futuro? A resposta está nas vantagens que o modelo promete quando amadurecer.
O artigo da JetBrains cita redução no uso de CPU e memória, menor tempo de cold boot, contas de hospedagem menores e maior economia de escala. Além disso, os pacotes tendem a ser menores que imagens de contêiner.
Outro argumento é a distribuição global. Segundo o autor, WASM pode ser implantado ao mesmo tempo em muitas regiões, além das regiões limitadas e congestionadas dos provedores de nuvem.
Também pesa a portabilidade. Um mesmo binário roda no navegador, no servidor, na borda ou dentro de outra aplicação, desde que o host seja compatível.
Para o desenvolvedor .NET, isso significa reaproveitar C# em lugares onde antes só cabia JavaScript, Rust ou Go. Por isso, mesmo sendo uma tecnologia relativamente nova, o WebAssembly despertou um otimismo cauteloso.
Ainda assim, nenhuma dessas vantagens dispensa testes. Antes de levar algo para produção, meça consumo de memória e tempo de inicialização no seu próprio cenário.
O que aconteceu com o SDK experimental de WASI
Vale registrar o destino da referência principal deste artigo. O repositório dotnet/dotnet-wasi-sdk foi arquivado em 29 de outubro de 2024 e hoje está em modo somente leitura.
Segundo o próprio README, muitos dos experimentos iniciados ali chegaram ao runtime do .NET durante o .NET 8. Em outras palavras, o protótipo de 2022 cumpriu o papel de abrir caminho para o suporte oficial.
Portanto, os comandos com Wasi.Sdk mostrados aqui retratam o momento do .NET 7. Eles servem para entender a evolução do WASM no ecossistema, e não como receita para projetos novos.
Conclusão
No fim de 2022, WASM já era padrão do W3C, rodava o Blazor no navegador e começava a sair dele com o WASI. Enquanto isso, o .NET 7 trazia a interop com [JSImport] e [JSExport], e o SDK experimental compilava consoles e até ASP.NET Core para um único .wasm.
Ao mesmo tempo, as limitações eram reais: thread única, ausência de sockets de saída, APIs faltando e ferramental imaturo. Por isso, o melhor caminho era experimentar, entender o modelo e acompanhar a evolução das especificações.
Para começar, um console simples rodando no Wasmtime já mostra como o WASM muda a forma de empacotar uma aplicação .NET.
Se a promessa de menos recursos, cold start menor e portabilidade total se confirmar, o WebAssembly muda a forma como empacotamos e distribuímos software. É por esse motivo que tanta gente aposta no WASM como o futuro da web.
FAQ
WASM substitui o JavaScript?
Não. O WebAssembly roda ao lado do JavaScript no navegador e, nesse ambiente, depende dele para acessar o DOM e as APIs da página.
Qual a diferença entre WebAssembly e WASI?
O WebAssembly é o formato binário e a máquina virtual que executa o código. Já o WASI é a interface padrão que permite a esse código acessar recursos do sistema, como arquivos e rede, fora do navegador.
Preciso do Blazor para usar WebAssembly no .NET?
Não. A partir do .NET 7, a interop com [JSImport] e [JSExport] permite usar o runtime .NET em WebAssembly dentro de qualquer aplicação JavaScript.
O Wasi.Sdk era um produto oficial e estável?
Não. Era um pacote experimental, e o repositório foi arquivado em 2024 depois que parte dos experimentos chegou ao runtime do .NET 8.
Dava para acessar um banco de dados a partir de um módulo WASI em 2022?
Segundo o artigo da JetBrains, não havia suporte a sockets de saída, o que impedia a comunicação com bancos de dados e serviços web naquele momento.