Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

WebAssembly no Node.js: módulos e memória

Atualizado em: 9 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

WebAssembly no Node.js permite executar módulos binários compilados de linguagens como Rust, C e C++ dentro do runtime JavaScript. O código Wasm pode realizar cálculos intensivos, reutilizar bibliotecas existentes e oferecer uma fronteira explícita entre JavaScript e memória linear.

No Node.js, a API global WebAssembly fornece compilação, instanciação, memória, tabelas, módulos e tratamento de erros. A integração funciona sem navegador e pode ser usada em servidores, CLIs, workers, processamento de arquivos, criptografia, codecs e bibliotecas de alto desempenho.

Neste guia, você aprenderá a carregar um arquivo .wasm, fornecer imports, acessar exports, compartilhar memória, usar Worker Threads, medir desempenho, distinguir WebAssembly de WASI e aplicar limites de segurança.

O que é WebAssembly?

WebAssembly é um formato binário portátil e uma máquina virtual com instruções de baixo nível. Ele foi criado para executar código com desempenho previsível em diferentes ambientes, mantendo validação estrutural antes da execução.

Um módulo Wasm normalmente contém:

  • funções compiladas;
  • imports esperados do host;
  • exports disponíveis ao JavaScript;
  • memória linear opcional;
  • tabelas de referências;
  • globais;
  • seções personalizadas.

WebAssembly não é uma linguagem-fonte. O desenvolvedor escreve Rust, C, AssemblyScript ou outra linguagem e gera o binário.

WebAssembly e WASI não são iguais

A API WebAssembly executa um módulo e permite que JavaScript forneça funções e memória. WASI define interfaces semelhantes a sistema operacional para arquivos, argumentos, ambiente e streams.

Um módulo puramente computacional pode usar apenas WebAssembly.instantiate(). Um programa que espera acesso a arquivos pelo modelo WASI precisa de configuração adicional. Consulte WASI no Node.js.

Validando bytes

import { readFile } from 'node:fs/promises';

const bytes = await readFile(new URL('./math.wasm', import.meta.url));

if (!WebAssembly.validate(bytes)) {
  throw new Error('Módulo WebAssembly inválido');
}

Buffer é uma subclasse de Uint8Array e pode ser passado às APIs. Veja Buffer no Node.js.

Instanciando um módulo

const result = await WebAssembly.instantiate(bytes, {});
const { instance, module } = result;

console.log(Object.keys(instance.exports));

Quando a entrada são bytes, o resultado contém o módulo compilado e a primeira instância. Uma instância possui estado; o módulo compilado pode ser instanciado novamente.

Chamando uma função exportada

Imagine um módulo que exporta add:

const { add } = instance.exports;
const total = add(20, 22);
console.log(total); // 42

Tipos numéricos básicos atravessam a fronteira com baixo atrito. Valores i64 são representados como BigInt no JavaScript.

Erros comuns da API

  • WebAssembly.CompileError: bytes inválidos ou recurso não suportado;
  • WebAssembly.LinkError: import ausente ou tipo incompatível;
  • WebAssembly.RuntimeError: trap durante execução, como acesso inválido à memória.
try {
  await WebAssembly.instantiate(bytes, imports);
} catch (error) {
  if (error instanceof WebAssembly.LinkError) {
    console.error('Imports incompatíveis');
  }
  throw error;
}

Fornecendo funções importadas

Um módulo pode chamar funções do host:

const imports = {
  env: {
    logNumber(value) {
      console.log({ wasmValue: value });
    }
  }
};

const { instance } = await WebAssembly.instantiate(bytes, imports);

O namespace e o nome precisam coincidir exatamente com o binário. Inspecione imports:

const compiled = await WebAssembly.compile(bytes);
console.log(WebAssembly.Module.imports(compiled));

Inspecionando exports

console.log(WebAssembly.Module.exports(compiled));

Essa informação ajuda a validar contratos antes de instanciar, mas não substitui uma API tipada e documentada.

Memória linear

WebAssembly.Memory representa um buffer de páginas de 64 KiB:

const memory = new WebAssembly.Memory({
  initial: 16,
  maximum: 64
});

const imports = { env: { memory } };

Definir maximum evita crescimento indefinido. O módulo só poderá crescer até o limite permitido.

Lendo a memória

const view = new Uint8Array(memory.buffer);
console.log(view.slice(0, 32));

Quando a memória cresce, o ArrayBuffer anterior pode ser substituído. Recrie a view depois de uma chamada que possa executar memory.grow().

Strings entre JavaScript e Wasm

Strings não atravessam diretamente a ABI básica. O padrão comum é copiar UTF-8 para a memória:

const encoder = new TextEncoder();
const input = encoder.encode('Olá, WebAssembly');

const pointer = instance.exports.allocate(input.byteLength);
new Uint8Array(memory.buffer, pointer, input.byteLength).set(input);

const result = instance.exports.process(pointer, input.byteLength);
instance.exports.deallocate(pointer, input.byteLength);

O módulo precisa expor uma convenção de alocação. Falhas de tamanho, ponteiro e ciclo de vida podem causar corrupção lógica. Veja TextEncoder e TextDecoder no Node.js.

Compilar uma vez, instanciar várias

const module = await WebAssembly.compile(bytes);

const instanceA = await WebAssembly.instantiate(module, importsA);
const instanceB = await WebAssembly.instantiate(module, importsB);

Isso é útil para criar instâncias isoladas sem recompilar o binário. Cada instância pode ter memória própria e imports diferentes.

Cache do módulo compilado

Mantenha o WebAssembly.Module em memória durante a vida do processo. Não serialize assumindo compatibilidade entre versões do runtime. Para inicializações repetidas, meça o benefício de bundling e cache do processo.

instantiateStreaming no Node.js

Quando o módulo vem de HTTP:

const response = await fetch('https://example.com/module.wasm');

const result = await WebAssembly.instantiateStreaming(
  response,
  imports
);

O servidor precisa enviar MIME type adequado, normalmente application/wasm. Para arquivos locais, readFile() e instantiate() são simples e previsíveis.

Não carregue Wasm de URL arbitrária

Uma API que aceita qualquer URL para baixar um módulo cria risco de SSRF, consumo de memória e execução de código não aprovado. Use allowlist, HTTPS, digest esperado e limite de bytes.

const expectedSha256 = process.env.WASM_SHA256;
const actual = createHash('sha256').update(bytes).digest('hex');

if (actual !== expectedSha256) {
  throw new Error('Integridade do módulo inválida');
}

Assinatura e cadeia de suprimentos

Versione o binário, registre origem, gere SBOM e valide digest no deploy. O fato de Wasm ser validado estruturalmente não significa que a lógica seja confiável.

Consulte SBOM com Syft no Node.js e Cosign no Node.js.

CPU e bloqueio do event loop

Uma função Wasm longa continua executando na thread que a chamou. Ela pode bloquear o event loop assim como uma função JavaScript CPU-bound.

Para trabalho pesado, use Worker Threads:

import { Worker } from 'node:worker_threads';

const worker = new Worker(
  new URL('./wasm-worker.js', import.meta.url),
  { workerData: { file: 'codec.wasm' } }
);

Veja Worker Threads no Node.js.

Compartilhamento com workers

Um WebAssembly.Module pode ser clonado entre workers em ambientes suportados. Isso permite compilar no processo principal e instanciar em cada worker. Meça a estratégia, pois transferência, inicialização e memória ainda possuem custo.

Memória compartilhada

WebAssembly pode trabalhar com memória compartilhada quando os recursos necessários são suportados:

const memory = new WebAssembly.Memory({
  initial: 32,
  maximum: 128,
  shared: true
});

Concorrência exige Atomics e desenho cuidadoso. Não compartilhe estruturas mutáveis sem protocolo claro.

Desempenho real

WebAssembly não é automaticamente mais rápido. Custos importantes:

  • copiar strings e buffers;
  • atravessar a fronteira JS/Wasm muitas vezes;
  • alocar memória;
  • instanciar módulos;
  • converter estruturas complexas;
  • executar algoritmo inadequado.

Prefira chamadas maiores que processam lotes, em vez de milhões de chamadas pequenas.

Benchmark correto

const start = performance.now();
for (let i = 0; i < 1000; i++) {
  instance.exports.transform(pointer, length);
}
const duration = performance.now() - start;

Aqueça o código, repita, compare com implementação JavaScript e meça memória. Use perf_hooks no Node.js e profiling para identificar o custo dominante.

Rust para WebAssembly

Projetos Rust podem usar targets como wasm32-unknown-unknown e ferramentas de binding. Defina uma ABI pequena, evite expor detalhes internos e fixe versões do toolchain.

C e C++

Clang e Emscripten podem gerar Wasm. Bibliotecas que dependem de sistema operacional talvez precisem de WASI ou adaptações. Nem toda biblioteca nativa pode ser recompilada sem mudanças.

WebAssembly versus addon nativo

Wasm oferece portabilidade e isolamento de memória linear. Addons Node-API podem acessar recursos nativos com integração mais direta, mas exigem builds por plataforma e possuem maior impacto em caso de bug de memória.

Veja Addons C++ no Node.js.

WebAssembly versus subprocesso

Um subprocesso oferece isolamento de processo, limites do sistema operacional e comunicação por IPC, com overhead maior. Para código não confiável, Wasm dentro do mesmo processo não substitui sandbox operacional completa.

Limites operacionais

Aplique:

  • tamanho máximo do módulo;
  • maximum na memória;
  • timeout externo por Worker;
  • limite de concorrência;
  • digest e versão permitidos;
  • inputs validados;
  • observabilidade de CPU e memória.

Cancelamento

JavaScript não consegue interromper uma função Wasm síncrona já em execução. Para deadline real, execute em Worker e termine o worker:

const timer = setTimeout(() => {
  worker.terminate();
}, 5_000);

Use pool para evitar custo de criar um worker por requisição.

Testes

Teste:

  • módulo válido e inválido;
  • import ausente;
  • limites de memória;
  • strings vazias e Unicode;
  • buffers grandes;
  • traps;
  • resultado equivalente ao JavaScript;
  • timeout do worker;
  • digest incorreto.

Mocks e módulos pequenos

Mantenha uma versão Wasm mínima para testes de integração. Testes unitários da lógica JavaScript podem injetar um objeto com os mesmos exports, evitando compilar o binário em cada caso.

Observabilidade

Registre:

  • versão e digest do módulo;
  • tempo de compilação;
  • tempo de instanciação;
  • duração por operação;
  • bytes copiados;
  • crescimento de memória;
  • traps e timeouts.

Não registre dados processados quando forem sensíveis.

Erros comuns

  • assumir que Wasm nunca bloqueia;
  • não definir maximum de memória;
  • copiar buffers repetidamente;
  • chamar função pequena em loop pela fronteira;
  • executar binário sem validar origem;
  • confundir Wasm com WASI;
  • não liberar memória conforme a ABI;
  • reutilizar views após memory.grow();
  • usar Wasm sem benchmark comparativo.

Fluxo recomendado

  1. defina a operação de alto custo;
  2. crie ABI pequena;
  3. compile com toolchain fixo;
  4. valide e assine o binário;
  5. compile uma vez por processo;
  6. instancie com memória limitada;
  7. processe lotes;
  8. isole CPU pesada em workers;
  9. compare com JavaScript;
  10. monitore versão, CPU e memória.

Conclusão

WebAssembly no Node.js permite reutilizar código compilado e acelerar workloads específicos com uma interface binária portátil. A API JavaScript oferece módulos, instâncias, memória e imports suficientes para integrar cálculos, codecs e bibliotecas.

O sucesso depende da fronteira: minimize cópias, limite memória, valide o binário e use workers para CPU longa. Com benchmarks e uma ABI clara, Wasm complementa JavaScript sem introduzir uma caixa-preta insegura.

Consulte a referência WebAssembly da MDN e a documentação do projeto WebAssembly.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita