Copilot Studio e .NET 10 no WebAssembly

O .NET 10 no WebAssembly já roda em produção no Microsoft Copilot Studio, que concluiu a migração do seu motor WebAssembly (WASM) do .NET 8 para o .NET 10. A transição foi direta, simplificou o processo de deploy e entregou mais uma rodada de ganhos reais de desempenho para os usuários finais.

Esse caso é interessante porque não se trata de um exemplo de laboratório. Afinal, o Copilot Studio é um produto usado em larga escala, e o time documentou os números da migração no blog oficial do .NET.

Neste artigo, você vai entender por que o Copilot Studio usa WebAssembly, o que mudou no fingerprinting de assets, o impacto do WasmStripILAfterAOT e quais resultados a equipe mediu.

O que é o Copilot Studio e por que ele usa WebAssembly

O Microsoft Copilot Studio é uma plataforma para criar e personalizar agentes de inteligência artificial sem precisar escrever código. Para avaliar regras e lógica de negócio direto no navegador, ele executa C# por meio do .NET WebAssembly.

Segundo o time, essa escolha permite avaliar fórmulas Power Fx em tempo real no cliente. Além disso, o mesmo código de validação e de parsing de expressões roda no servidor e no navegador, o que garante consistência entre os dois lados.

Essa abordagem elimina idas e voltas desnecessárias ao servidor para processamentos que podem acontecer localmente. Com isso, a latência cai e a interface responde mais rápido.

Outro detalhe de arquitetura é que o runtime .NET roda dentro de um Web Worker. Dessa forma, o processamento pesado fica fora da thread principal da interface, e o navegador continua interativo mesmo em bots complexos.

Se você quer entender o papel do WASM no desenvolvimento web, o artigo WASM e o futuro da web traz uma boa introdução.

Histórico da migração até o .NET 10 no WebAssembly

Essa não é a primeira atualização de versão do Copilot Studio. Em um artigo anterior do blog do .NET, o time contou que a migração do .NET 6 para o .NET 8 reduziu em 55% o tamanho do download do motor WASM.

Na mesma ocasião, o carregamento de páginas e bots ficou de 44% a 56% mais rápido. Os comandos responderam de 26% a 35% mais rápido, e o tempo de build e deploy caiu 45%.

Portanto, o salto para o .NET 10 no WebAssembly continua uma trajetória de ganhos a cada versão. Isso mostra que acompanhar as versões do .NET rende benefícios concretos para aplicações WASM.

A migração para o .NET 10 no WebAssembly foi simples

Levar uma aplicação .NET 8 WebAssembly para o .NET 10 é, em grande parte, uma questão de atualizar o <TargetFramework> nos arquivos .csproj. Em seguida, basta garantir que todas as dependências sejam compatíveis com a nova versão.

Para o Copilot Studio, a migração seguiu exatamente esse caminho. Hoje, o build com o .NET 10 no WebAssembly já roda em produção.

Isso indica maturidade da plataforma e baixo risco de adoção para times que mantêm aplicações Blazor WebAssembly. Em outras palavras, a atualização não exigiu reescrever código da aplicação.

Fingerprinting automático no .NET 10 no WebAssembly

Uma das mudanças mais práticas do .NET 10 no WebAssembly é o fingerprinting automático dos assets WASM. Ao publicar a aplicação, o nome de cada arquivo gerado passa a incluir um identificador único.

Com isso, você ganha cache busting e validação de integridade sem nenhuma intervenção manual. O navegador baixa a versão nova de um arquivo assim que o conteúdo muda e reaproveita o cache enquanto nada muda.

Como era antes do .NET 10

Antes do .NET 10, o Copilot Studio, assim como muitas outras aplicações WASM, precisava cumprir três passos manuais:

  • Ler o manifest blazor.boot.json publicado para enumerar os assets.
  • Executar um script PowerShell customizado para renomear cada arquivo com um hash SHA-256 no sufixo.
  • Passar um argumento explícito de integrity a partir do JavaScript ao requisitar cada recurso.

Esse processo funcionava, mas exigia código próprio para manter. Além disso, qualquer mudança na estrutura do publish podia quebrar o script.

Como ficou no .NET 10

No .NET 10, todo esse processo foi eliminado. Os recursos são importados direto a partir do dotnet.js, os fingerprints já fazem parte dos nomes dos arquivos publicados e a validação de integridade ocorre automaticamente.

Assim, o time do Copilot Studio pôde apagar o script de renomeação e remover o argumento integrity do loader no cliente. Toda a lógica de cache e validação que existia sobre esses recursos continuou funcionando sem alterações.

Vale destacar que o manifest blazor.boot.json não existe mais no .NET 10. Segundo a documentação, a configuração de boot agora fica embutida no dotnet.js, e os times que dependiam do arquivo para processamento customizado devem coletar as informações direto do build.

Habilitando o fingerprinting em um projeto standalone

Em um projeto standalone Blazor WebAssembly, você precisa ajustar o index.html e ativar a propriedade <OverrideHtmlAssetPlaceholders> no .csproj:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <!-- Substitui os placeholders do index.html no build e no publish -->
  <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>

E no wwwroot/index.html:

<head>
    <script type="importmap"></script>
</head>

<body>
    <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>

Durante o build e o publish, o framework substitui esses placeholders por valores calculados. Dessa forma, o fingerprint entra no nome do blazor.webassembly.js, e o import map aponta para os demais assets do .NET.

A mesma propriedade também ativa o preload dos assets do framework, desde que o <head> tenha a tag <link rel="preload" id="webassembly" />. Por outro lado, se a aplicação não usa um HTML gerado pelo build, como em custom elements consumidos por uma SPA, a documentação recomenda desligar essa propriedade.

WasmStripILAfterAOT no .NET 10 no WebAssembly

A outra grande mudança do .NET 10 no WebAssembly é que a propriedade WasmStripILAfterAOT passou a vir habilitada por padrão em builds AOT. Ela remove o código Intermediate Language (IL) original dos métodos após a compilação ahead of time (AOT) para WebAssembly.

Como o IL não é mais necessário em tempo de execução depois da compilação AOT, o .NET 10 o remove automaticamente do output publicado. No .NET 8, a configuração existia, mas o valor padrão era false, então era preciso ativá-la no .csproj:

<PropertyGroup>
  <!-- Compila os assemblies para WebAssembly antes da publicação -->
  <RunAOTCompilation>true</RunAOTCompilation>
  <!-- Necessário apenas no .NET 8; no .NET 10 já é o padrão -->
  <WasmStripILAfterAOT>true</WasmStripILAfterAOT>
</PropertyGroup>

A remoção do IL reduz o tamanho da pasta _framework no output publicado. Portanto, a aplicação economiza banda e acelera o carregamento inicial.

Lembre que a compilação AOT para WebAssembly depende do workload wasm-tools. Você o instala com dotnet workload install wasm-tools, e o build com AOT costuma demorar bem mais do que o build comum.

JIT e AOT no .NET 10 no WebAssembly

Para entender a estratégia do Copilot Studio, vale separar os dois modos de execução. No modo padrão, que o time chama de JIT, o runtime interpreta o IL no navegador.

Esse modo compila rápido e baixa menos arquivos, o que favorece o startup. Em contrapartida, a execução de código pesado fica mais lenta.

No modo AOT, o build traduz o código .NET para WebAssembly antes da publicação. Assim, a execução fica muito mais rápida, embora os arquivos fiquem maiores e o build demore mais.

Por isso, cada modo tem um ponto forte. No caso do .NET 10 no WebAssembly, o desafio do Copilot Studio era combinar os dois sem que o usuário pagasse o custo de nenhum deles.

A estratégia de empacotamento do Copilot Studio

O Copilot Studio usa uma estratégia de empacotamento mais sofisticada do que a maioria das aplicações Blazor. Para equilibrar o tempo de startup e o desempenho contínuo, ele publica um único pacote NPM com dois motores: um JIT, para startup rápido, e um AOT, para máxima velocidade de execução.

Em tempo de execução, os dois motores carregam em paralelo. O motor JIT trata as interações iniciais, e o controle passa para o AOT assim que ele fica pronto.

Os arquivos idênticos bit a bit entre os dois modos são deduplicados para manter o pacote menor. Esse detalhe pesa bastante na conta do .NET 10 no WebAssembly. Contudo, o WasmStripILAfterAOT gera assemblies AOT que não correspondem mais aos seus pares JIT, então menos arquivos podem ser compartilhados:

  • No .NET 8, 59 arquivos eram compartilhados entre os motores JIT e AOT.
  • No .NET 10, apenas 22 arquivos são compartilhados.

O efeito líquido no pacote do Copilot Studio foi um aumento de tamanho de aproximadamente 15%. Na prática, porém, o impacto para o usuário é pequeno, porque o motor JIT continua sendo o que o usuário baixa e executa primeiro.

O download do AOT ficou cerca de 6% mais lento, aproximadamente 200 ms, em uma rede LAN rápida. Em uma conexão 4G, ficou cerca de 17% mais lento, aproximadamente 5 segundos, mas tudo isso acontece em segundo plano, depois que o app já responde.

Os ganhos de performance superam o custo de empacotamento

Nos workloads do Copilot Studio, os benefícios em tempo de execução compensam com folga o custo no tamanho do pacote. O time mediu os seguintes resultados:

  • Aproximadamente 20% mais rápido na primeira execução (cold path).
  • Aproximadamente 5% mais rápido nas execuções seguintes (warm path).

Com o .NET 10 no WebAssembly, esses ganhos aparecem mais em agentes grandes e complexos, os "big bots". Nesses cenários, o código compilado via AOT faz o trabalho mais pesado.

A tabela abaixo resume o comparativo entre .NET 8 e .NET 10 no Copilot Studio:

Cenário .NET 8 .NET 10 Ganho
Cold path (primeira execução) baseline ~20% mais rápido significativo
Warm path (execuções seguintes) baseline ~5% mais rápido incremental
Arquivos compartilhados JIT/AOT 59 22 redução
Tamanho do pacote baseline +15% custo aceitável

Como migrar sua aplicação para o .NET 10 no WebAssembly

O processo que o time do Copilot Studio recomenda é direto. Primeiro, atualize o <TargetFramework> para net10.0 e as referências dos pacotes Microsoft.AspNetCore.*, Microsoft.Extensions.* e System.*.

Em segundo lugar, remova qualquer lógica customizada de renomeação de assets ou injeção manual do argumento integrity. Afinal, o fingerprinting agora é nativo da plataforma.

Em terceiro lugar, se a aplicação usa compilação AOT, você recebe os benefícios do WasmStripILAfterAOT automaticamente, sem configuração adicional. Nesse caso, também pode remover a propriedade do .csproj, já que ela virou o padrão.

Para quem precisa de ajuda na modernização, o GitHub Copilot app modernization for .NET analisa a solução, planeja o upgrade e aplica as mudanças necessárias. O artigo sobre upgrade de aplicações .NET legadas com GitHub Copilot mostra esse fluxo na prática.

Antes de publicar, rode a aplicação e teste os fluxos principais. Em especial, confira se nenhum código seu lia o blazor.boot.json, porque esse arquivo deixou de existir.

Considerações sobre Web Workers

Se a aplicação carrega o runtime do .NET 10 no WebAssembly dentro de um Web Worker, defina dotnetSidecar = true durante a inicialização. Essa configuração garante a inicialização correta no contexto de um worker.

Assim, você evita problemas sutis de bootstrap que só aparecem em ambientes de worker. Como o Copilot Studio roda o runtime em um worker, essa dica vem direto da experiência do time.

Para entender melhor esse modelo, o artigo sobre Web Workers no Blazor mostra como mover processamento para fora da thread principal.

Por que essa migração importa para o ecossistema .NET

A migração do Copilot Studio é mais um exemplo de que cada versão do .NET torna o WebAssembly mais rápido, menor e mais simples de entregar. O caso real documentado pelo time mostra que a mudança tem baixo risco, com benefícios imediatos de desempenho e de simplificação operacional.

Para times que mantêm aplicações Blazor WebAssembly em produção, o .NET 10 no WebAssembly representa um upgrade que vale avaliar. Além disso, o .NET 10 é uma versão LTS, com suporte de três anos.

A eliminação dos scripts de fingerprinting, somada aos ganhos do AOT, reduz a complexidade operacional. Com isso, a experiência do usuário final melhora de forma mensurável.

Conclusão

O Microsoft Copilot Studio mostrou na prática que migrar do .NET 8 para o .NET 10 no WebAssembly é direto e entrega resultados concretos. O fingerprinting automático de assets eliminou scripts auxiliares e reduziu a complexidade do deploy.

Já a ativação padrão do WasmStripILAfterAOT melhorou o desempenho de execução, com ganhos de até 20% no cold path em cenários reais. O custo foi um pacote cerca de 15% maior, absorvido pelo carregamento em paralelo dos motores JIT e AOT.

Para desenvolvedores .NET que trabalham com Blazor WebAssembly, o .NET 10 é o próximo passo natural. Por fim, a experiência do Copilot Studio serve como referência sólida para justificar essa atualização, e o artigo original no blog do .NET traz todos os detalhes.

FAQ

O que é WebAssembly no contexto do .NET?

WebAssembly (WASM) é um formato de instrução binária que executa código compilado no navegador com desempenho próximo ao nativo. O .NET roda código C# em WASM, o que permite que aplicações Blazor rodem inteiramente no cliente.

O que é compilação AOT no Blazor WebAssembly?

AOT (Ahead of Time) é um modo em que o build traduz o código .NET para WebAssembly antes da publicação. Isso dispensa a interpretação em tempo de execução e aumenta a velocidade.

O que é o WasmStripILAfterAOT?

É uma propriedade MSBuild que remove o IL dos métodos após a compilação AOT. No .NET 10, ela vem habilitada por padrão em builds AOT, o que reduz o tamanho do output publicado.

O fingerprinting automático funciona com qualquer arquivo estático?

O fingerprinting nativo do .NET 10 cobre os assets do framework WASM. Para arquivos JavaScript próprios, use o item <StaticWebAssetFingerprintPattern> no .csproj para incluir outros tipos de arquivo.

Qual é a diferença entre JIT e AOT no contexto WASM?

O modo JIT executa o código .NET no navegador sem compilação prévia, o que favorece o startup. O modo AOT compila tudo antes, o que acelera a execução, e o Copilot Studio usa os dois em paralelo.

Preciso alterar o código da aplicação para migrar para o .NET 10?

Em geral, não. A migração se resume a atualizar o <TargetFramework> e os pacotes NuGet, exceto se a aplicação dependia do blazor.boot.json ou de scripts próprios de fingerprinting.

O .NET 10 é uma versão LTS?

Sim. O .NET 10 é uma versão LTS (Long Term Support), com suporte de três anos a partir do lançamento.