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.jsonpublicado 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
integritya 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.