Aprenda a usar os recursos mais recentes do WebAssembly e ofereça suporte aos usuários em todos os navegadores.
O WebAssembly 1.0 foi lançado há quatro anos, mas o desenvolvimento não parou por aí. Novos recursos são adicionados pelo processo de padronização de propostas. Como acontece com novos recursos na Web, a ordem de implementação e os cronogramas podem variar significativamente entre os mecanismos. Se você quiser usar esses novos recursos, é necessário garantir que nenhum dos seus usuários seja deixado de fora. Neste artigo, você vai aprender uma abordagem para fazer isso.
Alguns recursos novos melhoram o tamanho do código adicionando novas instruções para operações comuns, outros adicionam primitivas de desempenho poderosas e outros melhoram a experiência do desenvolvedor e a integração com o restante da Web.
A lista completa de propostas e os respectivos estágios estão disponíveis no repositório oficial ou na página oficial do mapa de ruta de recursos.
Para garantir que os usuários de todos os navegadores possam usar seu aplicativo, você precisa descobrir quais recursos quer usar. Em seguida, divida-os em grupos com base no suporte do navegador. Em seguida, compile sua base de código separadamente para cada um desses grupos. Por fim, no navegador, você precisa detectar os recursos com suporte e carregar o pacote JavaScript e Wasm correspondente.
Como selecionar e agrupar atributos
Vamos seguir essas etapas escolhendo um conjunto de recursos arbitrário como exemplo. Digamos que eu identifiquei que quero usar SIMD, threads e tratamento de exceções na minha biblioteca por motivos de tamanho e desempenho. O suporte a navegadores é o seguinte:
Você pode dividir os navegadores nas seguintes coortes para garantir que cada usuário tenha a melhor experiência possível:
- Navegadores baseados no Chrome: há suporte para threads, SIMD e processamento de exceções.
- Firefox: a linha de execução e o SIMD têm suporte, mas o tratamento de exceções não tem.
- Safari: as linhas de execução têm suporte, mas o SIMD e o tratamento de exceções não têm.
- Outros navegadores: assumem apenas o suporte básico ao WebAssembly.
Essa divisão é feita por suporte a recursos na versão mais recente de cada navegador. Os navegadores modernos são atualizados automaticamente e sempre estão em dia. Portanto, na maioria dos casos, você só precisa se preocupar com a versão mais recente. No entanto, se você incluir a WebAssembly de referência como uma coorte de fallback, ainda será possível fornecer um aplicativo funcional, mesmo para usuários com navegadores desatualizados.
Compilação para diferentes conjuntos de recursos
O WebAssembly não tem uma maneira integrada de detectar recursos com suporte no momento da execução. Portanto, todas as instruções no módulo precisam ter suporte no destino. Por isso, é necessário compilar o código-fonte no Wasm separadamente para cada um desses conjuntos de recursos.
Cada conjunto de ferramentas e sistema de build é diferente, e você vai precisar consultar a documentação do seu compilador para saber como ajustar esses recursos. Para simplificar, vou usar uma biblioteca C++ de arquivo único no exemplo a seguir e mostrar como fazer a compilação com o Emscripten.
Vou usar SIMD com a emulação SSE2, threads com suporte à biblioteca Pthreads e escolher entre o processamento de exceções do Wasm e a implementação de JavaScript padrão:
# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions
O código C++ pode usar #ifdef __EMSCRIPTEN_PTHREADS__
e #ifdef __SSE2__
para escolher condicionalmente entre implementações paralelas (threads e SIMD) das mesmas funções e as implementações seriais no momento da compilação. Ele vai ficar assim:
void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
// …implementation using threads and SIMD for max speed
#else
// …implementation using threads but not SIMD
#endif
#else
// …fallback implementation for browsers without those features
#endif
}
O processamento de exceções não precisa de diretivas #ifdef
, porque pode ser usado da mesma forma em C++, independentemente da implementação escolhida pelas flags de compilação.
Como carregar o pacote correto
Depois de criar pacotes para todas as coortes de recursos, é necessário carregar o correto do aplicativo JavaScript principal. Para isso, primeiro detecte quais recursos são compatíveis com o navegador atual. Para isso, use a biblioteca wasm-feature-detect. Ao combinar com a importação dinâmica, você pode carregar o pacote mais otimizado em qualquer navegador:
import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';
let initModule;
if (await threads()) {
if (await simd()) {
if (await exceptions()) {
initModule = import('./main.threads-simd-exceptions.mjs');
} else {
initModule = import('./main.threads-simd.mjs');
}
} else {
initModule = import('./main.threads.mjs');
}
} else {
initModule = import('./main.basic.mjs');
}
const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would
Palavras finais
Neste post, mostramos como escolher, criar e alternar entre pacotes para diferentes conjuntos de recursos.
À medida que o número de recursos aumenta,o número de coortes de recursos pode se tornar insustentável. Para aliviar esse problema, escolha coortes de recursos com base nos dados reais dos usuários, ignore os navegadores menos populares e deixe que eles voltem para coortes um pouco menos ideais. Desde que o aplicativo ainda funcione para todos os usuários, essa abordagem pode oferecer um equilíbrio razoável entre o aprimoramento progressivo e a performance de execução.
No futuro, o WebAssembly poderá ter uma maneira integrada de detectar recursos com suporte e alternar entre diferentes implementações da mesma função no módulo. No entanto, esse mecanismo seria um recurso pós-MVP que você precisaria detectar e carregar condicionalmente usando a abordagem acima. Até lá, essa abordagem continua sendo a única maneira de criar e carregar código usando novos recursos do WebAssembly em todos os navegadores.