- O WebAssembly teve sucesso ao trazer grandes programas em C++ como o Photoshop para a web, mas sua adoção em apps centrados no DOM foi limitada por causa de um modelo de programação diferente do JavaScript
- O suporte do navegador a Wasm GC e tipos de referência abre oportunidades para linguagens com memória gerenciada, como Python e Scheme, mas na web o tamanho de transferência vira uma barreira direta à adoção
- Um programa Wasm simples em Go tem 2 MB, e com imports pode passar de 10 MB; o REPL do Pyodide baixa cerca de 20 MB, o que pesa para apps web comuns
- O compilador Hoot Scheme tem como alvo Wasm com suporte a GC e reduziu a unidade mínima de compilação “main” para cerca de 70 KB, enquanto unidades auxiliares podem ficar abaixo de 1 KB
- Um tree shaking eficaz vai além de apagar funções não referenciadas: é um problema de compilador em que análise de fluxo e design da biblioteca padrão precisam trabalhar juntos
Áreas em que o WebAssembly teve resultados na web
- O WebAssembly não se espalhou de forma tão ampla quanto se esperava no início para a web, mas obteve sucesso limitado em áreas específicas
- O caso mais representativo é trazer grandes programas em C++, como o Photoshop, para a web
- O Figma também é citado como exemplo de Wasm de 5 anos atrás, mas hoje já não enfatiza tanto o Wasm
- Muitas bibliotecas pequenas de NPM compiladas a partir de C++ ou Rust usam Wasm internamente
- O Blazor pode ser usado em alguns apps corporativos internos, mas talvez seu marketing tenha exagerado um pouco
- A demo 3D FPS da Unreal Engine foi um experimento baseado numa grande versão de lançamento de mais de 5 anos atrás, e hoje a Unreal 5 não oferece suporte a destino WebAssembly
Por que o Wasm travou em apps centrados no DOM
- O WebAssembly tem resultados fora da web e pode se tornar mais importante também na plataforma web, mas na web ele pode ser visto como algo que está apenas saindo do vale da desilusão
- O Wasm é forte em tarefas que o JavaScript não faz bem, ou em trabalhos que exigem implementação compartilhada entre cliente e servidor
- Em apps centrados no DOM, o Wasm não teve sucesso
- Ninguém fala em reescrever o frontend do wordpress.com em Wasm
- O principal modelo de programação da web é o JavaScript, com tipagem dinâmica e memória gerenciada
- O WebAssembly 1.0 foi projetado em torno de tipagem estática e memória linear
- Acessar o DOM a partir de Wasm era trabalhoso a ponto de só entusiastas muito dedicados toparem isso
- Linguagens como C# precisam distribuir também um coletor de lixo, e isso virou obstáculo para adoção de Wasm por linguagens que não são C/Rust
O problema do tamanho de transferência que continua mesmo após o Wasm GC
- Nos próximos meses, os navegadores devem oferecer suporte a tipos de referência e coleta de lixo
- Chrome e Firefox já oferecem Wasm GC
- O Safari também não deve demorar, graças ao trabalho de Asumu Takikawa
- O Wasm GC é uma mudança que leva mais linguagens a atualizar seus toolchains para oferecer suporte a WebAssembly
- Para o Wasm na web dar certo, o compilador precisa gerar código pequeno
- É vantajoso quando o toolchain da linguagem consegue produzir arquivos Wasm úteis na faixa de alguns KB para transferência
- Caso contrário, ele precisa depender de hype ou de um público cativo e fica preso num equilíbrio instável até surgir outra solução
- No ecossistema JavaScript, já existe uma grande indústria de ferramentas para reduzir tamanho de entrega e inchaço
- Bundlers como o esbuild juntam vários módulos JS em um único arquivo
- Eles tentam incluir apenas as funções e tipos de dados realmente usados
- Também aplicam estratégias de redução de tamanho, como minification, encurtando nomes
A armadilha do nome tree shaking
- Tree shaking traz uma metáfora visual: manter só o código necessário numa página e deixar o resto cair
- Nessa metáfora, módulos viram galhos e definições viram folhas, mas numa árvore real balançar o tronco não diz quais galhos são necessários e quais não são
- O próprio nome leva a pensar em remover código desnecessário, mas do ponto de vista algorítmico faz mais sentido enxergar isso como encontrar um ponto fixo que preserve apenas o código necessário
- Mesmo assim, tree shaking é um nome forte e continua sendo usado apesar da sua imprecisão horticultural e algorítmica
A barreira de tamanho causada por runtimes pesados
- Em linguagens com runtimes pesados, tree shaking máximo não era uma prioridade tão alta
- No suporte a WebAssembly do Go, até o programa mais simples tem 2 MB segundo a wiki do golang
- Ao adicionar imports, ele pode passar de 10 MB
- O exemplo de REPL do Pyodide, um port de Python para WebAssembly, baixa cerca de 20 MB de dados
- Esses tamanhos podem servir para demos técnicas ou aplicações muito ricas, mas são difíceis de aceitar como uma opção comum de desenvolvimento web
Toolchains alternativos e implementações ajustadas à plataforma
- O suporte embutido a Wasm do Go e o Pyodide derivam de toolchains upstream, e no servidor o tamanho do binário pode não importar tanto
- Ao mirar dispositivos pequenos, aparecem implementações separadas
- O backend Wasm do TinyGo aparentemente consegue cair para menos de 1 KB
- Esses toolchains alternativos muitas vezes vêm com limitações ou peculiaridades
- Um programa Python com destino Wasm executado em ambiente DOM inevitavelmente será diferente de um programa Python “nativo”
- Os autores do toolchain tentam oferecer a mesma linguagem, mas a implementação da biblioteca padrão pode ser diferente
- Desenvolvedores de ClojureScript provavelmente também gostariam de eliminar, se possível, a documentação de diferenças em relação ao Clojure, e se o Wasm se tornar um destino prático para ClojureScript essa possibilidade pode surgir
Como o Hoot Scheme faz tree shaking
- Após o suporte a GC, o Wasm pode permitir pensar em programação DOM com linguagens como Python, mas para uso popular os módulos precisam ser pequenos
- O compilador Hoot Scheme tem como alvo Wasm com GC
- Hoje a unidade mínima de compilação “main” tem cerca de 70 KB
- O objetivo é reduzir ainda mais
- Unidades auxiliares de compilação que importam recursos de runtime, como tratadores de exceção, a partir do módulo main podem ficar abaixo de 1 KB
- O compilador Hoot anexa um prelude antes do código do usuário
- O tree shaking acontece em várias etapas
- A avaliação parcial pode avaliar apenas os efeitos de bindings não usados e depois removê-los
- fixing letrec faz algo semelhante
- O CPS percorre o programa repetidamente, seguindo apenas funções, valores e arestas de fluxo de controle que são referenciados
- Um passo explícito de eliminação de código morto remove atribuições sem efeito e sem uso que podem surgir após outras otimizações
- Definições da biblioteca padrão escritas em WebAssembly próximo do raw só entram no binário final quando necessário
O que é fácil remover e o que é difícil
- Definições de procedimentos, como funções ou closures, são relativamente fáceis de lidar
- Basta incluir apenas as funções que o código referencia
- Em linguagens como Scheme, só isso já produz um efeito considerável
- Três dificuldades aparecem logo de cara
-
Modelo de avaliação de letrec*
- O escopo das definições do prelude é recursivo, mas tem ordem
- O valor de um binding pode chamar ou referenciar valores definidos antes, e também pode capturar valores que só serão definidos depois
- Se for necessário referenciar, para avaliar um binding, um valor que só é definido mais tarde, isso é erro
- Em procedimentos isso em geral não é um problema, mas em definições que não são procedimentos o compilador pode não conseguir provar a propriedade de que “só referencia bindings anteriores”
- Nesse caso, o algoritmo fixing letrec reloaded pode deixar bindings com
set!, e removê-los exige um passo cuidadoso de DCE
-
vtable de tipos de registro
- Algumas definições que não são procedimentos são tipos de registro
- Tipos de registro têm uma vtable com itens como a forma de imprimir o registro ou de testar instâncias
- Mesmo que callbacks da vtable não sejam realmente usados, eles podem manter muito código vivo
-
Funções de saída polimórficas
- Funções polimórficas como
displayampliam muito o escopo do código necessário - Chamar
displaypara imprimir uma string traz junto toda a infraestrutura de I/O com buffer - Como
displaypode imprimir qualquer coisa, ele também puxa código para vários casos, como bitvector, pair etc. - Chamar
write-string, que serve só para strings, evita o código geral de impressão de dados, mas ainda assim inclui infraestrutura genérica de I/O com buffer, como ports
- Funções polimórficas como
Tree shaking ótimo é um problema de análise de fluxo
- O tree shaking ideal é, no fim, um problema de análise de fluxo
- Se um programa nunca tiver bitvector, então o código dentro de
displayque trata bitvector pode ser código morto - Para saber isso, é preciso saber com que tipos de argumento
displayé chamado, e isso exige análise de fluxo de alto nível - Em Python, o problema fica mais difícil
- Dispatch orientado a objetos é programação de ordem superior, então o significado de
foo.bardepende do que éfoo - A busca em Python é mais dinâmica do que em Scheme, e métodos como
__getattr__podem aparecer por toda parte - Na prática, talvez a análise de fluxo consiga excluir essas buscas dinâmicas
- O alvo do tree shaking em Python não é um conjunto grande de itens com bindings léxicos, e sim um conjunto complexo de módulos
- Isso se parece com JavaScript, mas Python não tem um ecossistema consolidado de bundlers com tree shaking
- Dispatch orientado a objetos é programação de ordem superior, então o significado de
Condições para toolchains de linguagens Wasm na web
- O Wasm GC pode tornar viável programar o DOM com linguagens que não sejam JavaScript
- Para isso chegar ao uso popular, os módulos Wasm gerados precisam ser pequenos
- Cada toolchain de linguagem exige investimento considerável
- Esse investimento muitas vezes aparece na forma de toolchains alternativos, incluindo algoritmos experimentais de tree shaking
- Bibliotecas padrão alternativas precisam ser projetadas para permitir que tree shakers funcionem melhor
1 comentários
Comentários do Hacker News
Mantive o blob Wasm do openEtG (motor do jogo de cartas) com menos de 400 KB, ao mesmo tempo em que transferi muita lógica para Wasm, como a geração de texto das cartas, e tudo foi escrito em Rust
Para reduzir o tamanho, foi preciso gerenciar coisas como usar aritmética de ponto fixo em vez de ponto flutuante, trocar hash maps por vetores, evitar strings, usar um alocador pequeno como
talce reduzir dependênciasEstou usando apenas
randefxhash, mas parece que também daria para removerrand;fxhashé usado só para verificar dessincronização via hash do estado do jogoTambém reduzi os tipos de instâncias genéricas para não acabar trazendo tipos extras como
Box<[i16]>, já queVecjá existe, e remover ponto flutuante e hash maps também ajudou a reduzir a diversidade de tiposOs algoritmos também foram projetados levando em conta o tamanho; por exemplo, uma tabela de consulta com bit packing codifica a mecânica adrenaline, em que criaturas com menor ataque atacam mais vezes
Comparei o custo de armazenar valores não comprimidos com o custo da lógica de decodificação, e a avaliação da IA usa precisão fixa de 6 bits, porque em WebAssembly 64 é codificado com mais eficiência do que 128
O mecanismo de targeting também costumava ser uma AST em que cada predicado era um enum e AND/OR eram fatias de expressão, mas agora a expressão é codificada em um inteiro de 32 bits em notação polonesa, com AND/OR ocupando 2 bits e os predicados 6 bits
Aqui, a notação polonesa foi melhor do que a notação polonesa reversa, porque permitia avaliação com curto-circuito de AND/OR
Estou pensando se ponto fixo poderia ajudar em problemas no trabalho em que conhecemos o requisito máximo de resolução — por exemplo, quando não é necessária precisão de posição abaixo de milímetros —, então gostaria de ouvir mais sobre questões relacionadas
wasm-optdo binaryenParece reduzir de forma consistente o tamanho do Wasm em cerca de 20% a 30%: https://github.com/WebAssembly/binaryen
Ao servir bundles Wasm no navegador, também é bom usar compressão Brotli e configurar o servidor web para usar arquivos comprimidos com Brotli
No nginx isso pode ser feito com uma mudança de uma linha; Brotli reduz o tamanho dos bundles Wasm em cerca de 3 vezes e é muito melhor que gzip
Esse tamanho inclui feedforward/backpropagation de deep learning, algoritmos de aprendizado por reforço e até simulações de dinâmica
Mesmo hoje não chega a ser pesado, mas estou curioso para ver quanto mais dá para reduzir, então quero tentar diminuir isso em breve
Quero entender como faz sentido dizer que evitar ponto flutuante e usar aritmética de ponto fixo economiza espaço
Especialmente algo como o item 6, usar
Veccomo se fosseBox, não parece que levaria a uma grande economiaO nome tree shaking parece um termo bastante inadequado
O compilador Virgil chama isso de “análise de alcançabilidade” e a incorpora ao modelo de compilação
O compilador faz o parsing do programa e do código de bibliotecas, verifica tipos e executa o código de inicialização, mas, depois disso, percorre a partir do ponto de entrada principal e só analisa e inclui no binário final o código que é alcançável
Ele também gera bem programas sem sistema de runtime, com apenas uma única função
main; o sistema de runtime só é necessário para stack traces e coleta de lixo, então pode ser omitido se desejadoO primeiro caso que encontrei foi a ferramenta Treeshaker do Lucid Common Lisp 4.1, de 1992, que era uma implementação comercial de Common Lisp para UNIX
O Lucid CL tinha o conceito de imagem, um dump de memória salvo do heap Lisp em execução, e uma aplicação era composta por uma imagem e um runtime
Como a imagem normalmente incluía quase todo o código e os dados na memória, surgiu o desejo de criar imagens menores para distribuição, e o Treeshaker removia código e dados considerados “não usados” antes de salvar a imagem
Era algo como podar conexões no grafo de dados e código Lisp alcançáveis, e então o GC ou um código especial coletava o lixo para reduzir a memória antes de despejar uma imagem menor
Portanto, o Treeshaker não era uma ferramenta de compilador, mas uma ferramenta para remover código e dados não usados do heap Lisp
Como a imagem Lisp básica incluía até o compilador, o interpretador e a implementação do REPL, se você interrompesse o programa em execução e entrasse no REPL, ainda poderia usar todo o código dentro do heap restaurado a partir da imagem
Por isso fazia sentido remover até o compilador ou o REPL
A análise de alcançabilidade é frequentemente usada para decidir qual código pode ser removido, mas a análise em si não remove código; a remoção é uma etapa posterior
“Tree shaking” normalmente sugere remoção no nível de funções, enquanto a eliminação de código morto também pode ocorrer em níveis muito mais granulares, como remover ramificações de expressões condicionais, e pode se basear em várias análises estáticas
Quando vi pela primeira vez, entendi o significado de imediato, sem pesquisar mais
É sacudir uma árvore para fazer cair o que está preso de forma frouxa; aqui, ficou claro que pacotes não usados são “sacudidos para fora” da árvore
Se você imaginar um diagrama de código-fonte como um objeto físico, ao sacudi-lo, as partes que não são alcançáveis a partir da raiz se soltam e caem
Não é muito diferente de análise de alcançabilidade; uma das expressões apenas evoca mais raciocínio espacial
Parte do código não está conectada ao tronco que é o ponto de entrada
Tree shaking remove as partes desconectadas, ou seja, folhas soltas e galhos mortos
Se a toolchain de compilação de uma linguagem conseguir produzir Wasm útil com apenas alguns KB em termos de transferência pela rede, novas possibilidades se abrem
Binários minúsculos abrirão novos casos de uso para Wasm, e WasmGC certamente ajuda
Java e Kotlin também já conseguem resultados bem bons hoje, na faixa de 2–3 KB: https://developer.chrome.com/blog/wasmgc, https://twitter.com/bashorov/status/1661377260274720770
Mas é preciso cuidado, porque dependendo da API usada, muito código pode vir junto
Ainda assim, graças ao WasmGC, essas linguagens já estão bem melhores que C++ e Rust em tamanho de código, pois não precisam incluir vários KB de código de gerenciamento de memória no bundle
Ao lidar com objetos JavaScript, pode ajudar, e talvez possa ser usado como um alocador de memória alternativo menos eficiente
Mesmo assim, o alocador padrão do Rust em Wasm provavelmente é adequado na maioria dos casos
Se você ativar otimizações de tamanho, usar
wasm-opte comprimir com Brotli, dá para colocar uma quantidade enorme de código em menos de 100 KB de downloadÉ um erro comparar diretamente o custo de 100 KB de Wasm com 100 KB de JavaScript empacotado, porque JavaScript é várias vezes mais lento para fazer parsing e inicializar
O tempo de download é um custo real, mas o tempo até a primeira renderização é muito melhor com 100 KB de Wasm do que com 100 KB de JavaScript
Ainda assim, quanto menor, melhor, e é empolgante ver Java, Kotlin, C#, Python, Go etc. se tornando linguagens práticas para aplicações web
Também fico curioso para ver qual será o tamanho de aplicações reais
A maior diferença provavelmente virá do design dos frameworks, e a comparação de DOM virtual inevitavelmente será sempre mais complexa e mais lenta do que bibliotecas de componentes reativos como Svelte, SolidJS e Leptos, do Rust
Quando o WasmGC for compatível em todos os lugares, a escolha do framework web provavelmente terá um impacto muito maior no desempenho do que a linguagem
Tenho dúvidas se a afirmação de que “Wasm torna concebível programar o DOM em linguagens que não sejam JavaScript” é realmente correta
Pelo que sei, para manipular o DOM em linguagens como Rust, no fim ainda é preciso ter bindings que serializam chamadas a serem executadas do lado do JavaScript
Na forma atual do Wasm, acho que ele ainda está preso ao JavaScript
Em teoria, agora seria possível importar funções do DOM no runtime e chamá-las com referências a objetos DOM, contornando o JavaScript e chamando o runtime diretamente
Não tenho certeza se isso é realmente possível na prática, mas o GC ao menos fornece o mecanismo pré-requisito para chegar a esse ponto
Basicamente, é parecido com o SolidJS
#[component] fn App() -> impl IntoView { let (count, set_count) = create_signal(0); view! { "Click me: "{move || count()} } }Em comparação com JavaScript, há um overhead no tamanho do Wasm, mas não é grave
Depois de
wasm-opte compressão Brotli, o bundle Wasm desse app de contador ficou com 37 KB, numa faixa parecida com React, e depois de executado é muito mais rápidoNão testei manipulação direta do DOM, mas parece bom para componentes comuns
Por exemplo, algo como usar um DOM implementado em Rust a partir de um módulo Wasm escrito em Rust, sem execução de JavaScript
Só que é complicado porque partes da API do DOM foram especificadas de acordo com a semântica do JavaScript; por isso, parecem estar avançando primeiro em coisas com menos legado de JavaScript, como requisições HTTP, sockets TCP e acesso ao sistema de arquivos
O termo “eliminação de código morto” existe há muito tempo; fico curioso por que surgiu a expressão tree shaking
“Tree shaking” se refere a uma análise de programa inteiro que descarta módulos e funções inteiros que não são chamados
Conceitualmente é a mesma coisa, mas autores de compiladores em geral precisam implementá-las separadamente, então é útil haver dois nomes
Tree shaking é fácil de imaginar, mais fácil de dizer que “eliminação de código morto” e mais acessível, então parece ter se tornado um termo mais popular
Ao pesquisar qual termo veio primeiro, encontrei o uso mais antigo de “dead-code elimination” em um artigo de 1973: https://research-repository.st-andrews.ac.uk/bitstream/handle/10023/22636/NicholasAlexandrakisMScThesis1973_original_C.pdf?sequence=1
No Google Scholar, não encontrei usos de “tree shaking” ou “tree shaker” na área de computação; a maioria era sobre árvores, como cítricos
O que parece ser a discussão mais antiga foi um post no comp.lang.lisp: https://groups.google.com/forum/#!topic/comp.lang.lisp/pspFr1XByZk
Acho que a metáfora de tree shaking talvez venha de alguns métodos de colheita de árvores frutíferas
Ao sacudir a árvore, os frutos maduros caem
Só que, na colheita de frutas, o que cai é o desejado; ao armazenar uma imagem serializada, o que cai é descartado, então não é uma metáfora tão boa assim
Frutas são mais delicadas e, se caem de uma altura grande demais, amassam; por isso, em geral são colhidas à mão
O processo de sacudir é bem brusco, mas não prejudica a árvore
Minha filosofia é otimizar bastante tamanho e desempenho em JavaScript por meio de algoritmos e simplificação de design, para abrir espaço no tamanho do bundle e folga de CPU para código que exige computação bruta
No projeto ClubCompy, estou usando Wasm para implementar um sistema de arquivos FAT sobre armazenamento local, e isso acabou sendo muito caro computacionalmente
Também pretendo usar Wasm mais adiante este ano, quando eu recolocar a detecção de colisão de sprites com precisão por pixel
A primeira implementação era JavaScript puro e, quando os 256 sprites na tela colidiam entre si, o framerate caía para menos de 1 fps
Acho que será possível processar isso praticamente de graça em uma worker thread, sem impacto no desempenho
Há um motivo para a maioria dos jogos 2D usar caixas de colisão retangulares: é mais fácil para o jogador prever se haverá colisão
Com colisão por pixel, o mesmo movimento pode ou não colidir dependendo da fase do ciclo de animação
Se um movimento que antes sempre funcionava falha porque o timing da animação coincidiu de um jeito ruim, a sensação é ruim
Além disso, colisão por pixel nem sempre é realista: pequenos detalhes de um sprite podem representar elementos como tecido ou cabelo, que na prática não causariam uma colisão rígida
Também é comum sprites se moverem vários pixels por frame, o que aumenta a chance de colisões entre pixels individuais simplesmente atravessarem uns aos outros
Uma detecção de colisão simples e previsível costuma ser a melhor opção
1 fps é um framerate que deveria aparecer com algo como 20 mil colisões
Se for por pixel, quando há memória suficiente, uma abordagem simples é renderizar um canvas offscreen do campo inteiro, desenhar cada sprite como um stencil de cor diferente e então inspecionar isso
É tempo linear e nem exige particionamento separado
Porém, medidas anti-fingerprinting podem transformar os bits menos significativos dos dados do canvas em ruído, então talvez seja preciso usar os bits mais significativos
Este texto está dizendo algo correto. Wasm tem um problema de tamanho de código
No navegador isso é um problema porque todo o código precisa ser baixado antes de o site iniciar; em arquiteturas serverless também é um problema, porque o código é carregado sob demanda do cold storage para um servidor específico enquanto o cliente espera
Tree shaking pode ajudar, mas provavelmente ficará só como uma otimização incremental
Fundamentalmente, a razão pela qual programas Wasm ficam inchados é que precisam trazer junto o runtime da própria linguagem e a biblioteca padrão inteira
Em contraste, no JavaScript, a implementação e a biblioteca básica são fornecidas pelo navegador
É fácil pensar que, ainda assim, o navegador não poderia carregar previamente todos os runtimes de linguagem, mas outra abordagem deve ser considerada: bibliotecas compartilhadas e linkedição dinâmica
WebAssembly oferece suporte a linkedição dinâmica, e vários módulos Wasm podem ser carregados ao mesmo tempo e chamar uns aos outros
Mas muitas toolchains de Wasm não querem dar suporte a isso, e foram projetadas para linkar estaticamente o programa inteiro e o runtime da linguagem em um único módulo enorme
Pyodide (CPPython em Wasm) é um contraexemplo: hoje ele é projetado com linkedição dinâmica em mente
Foi justamente graças a isso que o Cloudflare Workers pôde adicionar recentemente Python como suporte de primeira classe: https://blog.cloudflare.com/python-workers
Do ponto de vista da liderança técnica de toda a plataforma Workers, todos os Workers executados na mesma máquina compartilham um único runtime Pyodide compilado, então não é preciso carregá-lo separadamente para cada Worker
Se a linkedição dinâmica fosse suportada de forma mais ampla, poderíamos imaginar uma estrutura em que o navegador pré-carregasse runtimes de linguagens populares e até bibliotecas populares, e todas as páginas web que precisassem desse runtime compartilhassem a mesma cópia de código somente leitura
Esses runtimes ainda seriam executados dentro de uma sandbox, então o navegador não precisaria confiar neles; bastaria fornecê-los
Isso permitiria criar navegadores com suporte “embutido” a linguagens além de JavaScript, sem que os mantenedores do navegador precisassem verificar ou se preocupar completamente com a implementação da linguagem
Não conheço bem o assunto, mas entendo que esse modelo não funcionou bem
Havia versões demais das bibliotecas, de modo que versões individuais não eram realmente usadas de forma ampla, e depois, por preocupações de privacidade, os navegadores passaram a separar caches por site ou por origem
Talvez a proposta nem tenha sido pensada em termos de cache, mas este é um problema espinhoso mais próximo de um problema social do que de um problema técnico
Colocar mais runtimes de linguagem como suporte básico do navegador aumenta a barreira de entrada para novos navegadores, e também não é possível oferecer suporte a todas as bibliotecas e runtimes que as pessoas querem
Se cada um trouxer o seu e confiar no cache, fica a questão de como evitar os problemas já vividos com cache de bibliotecas JavaScript
Por exemplo, uma biblioteca compartilhada de Go poderia incluir o runtime e as partes centrais da biblioteca padrão usadas por muitos programas
Mesmo sem suporte a bibliotecas dinâmicas dentro do app, seria possível reduzir o tamanho de todos os programas Go, e o runtime da linguagem não precisaria fazer otimização de espaço tão agressiva
Como ele já está carregado e, se algum programa usar sequer uma função, não é espaço desperdiçado
Isso mudaria o modelo de custo de otimização de tamanho para programas dessa linguagem
Funções da biblioteca padrão incluídas se tornam, na prática, gratuitas assim que se usa a linguagem, então basta usá-las
Porém, o problema se repete em bibliotecas e frameworks comumente usados
Ao executar na Cloudflare, provavelmente você também gostaria de compartilhar a biblioteca padrão da Cloudflare para Go
O problema é que linguagens não evoluem no mesmo ritmo que seus runtimes
O suporte a múltiplas versões da linguagem pode ficar limitado, ou bibliotecas compartilhadas podem se acumular com o tempo, reduzindo o efeito de compartilhamento entre apps
JavaScript tem um modelo de versão baseado em “não há escolha”, exigindo forte compatibilidade retroativa e, às vezes, polyfills
Isso pode ser menos adequado para outras linguagens
Se o runtime realmente quiser reduzir espaço, basta limitar a diversidade de plugins
Apesar das reclamações, o modelo de “é preciso usar JavaScript” funcionou bastante bem nos navegadores
Talvez linguagens baseadas em WebAssembly não precisem ser tão diversas, e a diversidade tem custos, como em uma situação de Torre de Babel
Fazer tree shaking com base em informações reais de produção parece permitir cortar muito código morto ou quase morto sem implementar algoritmos sofisticados de análise estática
Especialmente em contextos como o Hoot, coisas como
appendChildsão funções externas chamadas de dentro de Scheme: https://spritely.institute/news/building-interactive-web-pages-with-guile-hoot.htmlEm teoria, em qualquer ambiente Wasm, muitas partes da biblioteca padrão de JavaScript poderiam ser usadas dessa forma
Zig é perfeito para esse tipo de uso
Pessoalmente, acho que, se o arquivo Wasm tiver menos de 100 KB, isso não é um fator importante; se passar de MB, passa a ser
GC embutido é importante para alguns apps, mas não para todos, e o melhor é criar apps web sem GC
O fator mais importante para o sucesso de apps que usam Wasm continua sendo a vantagem de desempenho
Estou rodando um app Blazor no Cloudflare Pages e, embora o download seja rápido e o desempenho seja bom, o tempo de carregamento é horrível
Acho que não dá para resolver com .NET, e o problema central parece ser que linguagens orientadas a objetos, por design, acabam deixando tudo interligado
Além disso, é difícil competir com o volume de dinheiro investido em JavaScript, e JavaScript tem copiar/colar quase como um recurso da linguagem, então parece um cheat code
Em terceiro lugar, mesmo no Blazor você ainda precisa de JavaScript e de competência nessa área, e vejo isso como o principal problema
O problema é que as linguagens daquela época dependiam de reflexão em muitos casos de uso
Há pessoas trabalhando duro para eliminar esses casos de uso em grande parte do .NET e marcar o código como seguro para remoção quando não utilizado
Se houver a possibilidade de um método ser chamado por reflexão, fica difícil saber o que pode ser removido com segurança
Mesmo que pareça que não há nenhum lugar chamando
Foo.Bar(), e se alguém fizerReflection.getClass(someClass).runMethod(someVar)e essas variáveis estiverem definidas como"Foo"e"Bar"?Por exemplo, Dart não permite reflexão em apps compilados antecipadamente, o que permite remover código não utilizado com segurança: https://docs.flutter.dev/resources/faq#does-flutter-come-with-a-reflection-mirrors-system
Dart é uma linguagem orientada a objetos, mas evita geração de código em tempo de execução e reflexão em tempo de execução, optando por geração de código em tempo de compilação
O .NET também está indo nessa direção, mas isso não acontece da noite para o dia
Porém, como outros disseram, linguagens que não são JavaScript também têm o problema de precisar enviar junto as partes da biblioteca padrão, incluídas no runtime JavaScript do navegador, que elas usam
O modelo de empacotamento atual não aproveita bem a capacidade de trimming do .NET, por causa das limitações de empacotar Wasm sobre Mono
Para ver o quanto ele realmente consegue reduzir, é melhor compilar uma aplicação comum com AOT; aí sai um binário pequeno
O suporte experimental ao alvo NativeAOT-LLVM Wasm em
dotnet/runtimelaboferece bundles muito menores e desempenho muito melhor, mas ainda está emdotnet/runtimelab, não emdotnet/runtime, então não sei quando ficará disponível