Integrar WebAssembly com Claude Code: Rust, wasm-pack e Vite
Integre Rust WebAssembly ao Vite com Claude Code, wrappers tipados, benchmarks, casos de uso e armadilhas comuns.
O papel certo de Claude Code com WebAssembly
WebAssembly, ou Wasm, é um formato binário portátil que permite executar código escrito em Rust, C, C++ e outras linguagens no navegador ou no Node.js. Ele não deve ser tratado como substituto geral de JavaScript. Em projetos reais, Wasm funciona melhor como acelerador de um trecho específico: processamento de imagem, compressão, manipulação de bytes para criptografia, cálculo numérico, agregação de CSV ou reaproveitamento de uma biblioteca Rust/C++ existente.
Claude Code ajuda porque a integração correta não está em um único arquivo. Você precisa da função Rust, do build com wasm-pack, do código de ligação gerado por wasm-bindgen, do carregamento assíncrono no Vite, de um wrapper TypeScript, de um benchmark e de uma revisão que olhe o custo da fronteira JS-Wasm. Essa fronteira é o ponto em que dados passam entre JavaScript e WebAssembly. Se ela for cruzada muitas vezes com chamadas pequenas, a melhoria pode desaparecer.
Neste artigo vamos criar uma base pequena e copiável: inverter um buffer RGBA, somar uma coluna numérica de CSV e calcular um checksum leve sobre bytes. Esses exemplos cobrem imagem, texto e dados binários. O mesmo padrão pode ser estendido para processamento rápido no navegador, migração de código Rust/C++, compressão, codecs próprios ou cálculos locais com dados que não devem ir ao servidor. Para o contexto maior de desempenho, leia também Claude Code performance optimization.
Use a documentação oficial como referência: MDN WebAssembly para a plataforma, wasm-bindgen Guide para a ponte entre Rust e JavaScript e wasm-pack repository para o fluxo de build. Peça a Claude Code para seguir essas fronteiras antes de inventar um carregador especial.
Escolha o caso de uso antes de codar
Wasm não é automaticamente mais rápido. Ele vale a pena quando recebe um bloco grande de trabalho e processa tudo em loops apertados. Ele costuma falhar quando JavaScript chama uma função Wasm minúscula milhares de vezes. Portanto, o primeiro passo é definir qual operação merece atravessar a fronteira e como ela será medida.
| Caso de uso | Por que combina com Wasm | O que Claude Code deve verificar |
|---|---|---|
| Processamento de imagem | Buffers RGBA são bons para loops lineares | Cópias de memória, leitura de Canvas e benchmark justo |
| Criptografia, compressão, codecs | Trabalham com bytes e podem usar bibliotecas Rust | Se uma biblioteca auditada é obrigatória e o que não deve ser feito à mão |
| CSV e cálculo numérico | Parsing e agregação repetem muitas operações | Linhas vazias, NaN, arquivos grandes e estratégia de erro |
| Portar Rust ou C++ | Reaproveita lógica validada no navegador | APIs do sistema, I/O, threads e dependências incompatíveis |
| Processamento local no navegador | Mantém dados sensíveis no dispositivo | Tamanho inicial, fallback e navegadores alvo |
Nos testes de Masa, o caminho mais seguro foi portar uma função e medir antes de reescrever um recurso inteiro. Em imagem, a função Rust era rápida, mas o tempo de ler e escrever ImageData podia dominar. Em CSV, passar o texto inteiro uma vez foi melhor do que chamar Wasm por linha. Essas restrições devem aparecer no prompt inicial de Claude Code.
Módulo Rust mínimo com wasm-pack
wasm-pack compila o crate Rust, executa wasm-bindgen e gera uma pasta pkg com o binário Wasm, o loader JavaScript, metadados de pacote e declarações TypeScript. wasm-bindgen é a biblioteca que expõe funções Rust selecionadas para JavaScript.
# Cargo.toml
[package]
name = "wasm-lab"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn invert_rgba(pixels: &mut [u8]) {
for chunk in pixels.chunks_exact_mut(4) {
chunk[0] = 255 - chunk[0];
chunk[1] = 255 - chunk[1];
chunk[2] = 255 - chunk[2];
}
}
#[wasm_bindgen]
pub fn sum_csv_column(csv: &str, column: usize) -> f64 {
csv.lines()
.filter(|line| !line.trim().is_empty())
.filter_map(|line| line.split(',').nth(column))
.filter_map(|cell| cell.trim().parse::<f64>().ok())
.sum()
}
#[wasm_bindgen]
pub fn fnv1a32(bytes: &[u8]) -> u32 {
let mut hash = 0x811c9dc5u32;
for byte in bytes {
hash ^= u32::from(*byte);
hash = hash.wrapping_mul(0x01000193);
}
hash
}
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
wasm-pack build --target web --out-dir pkg
A função fnv1a32 não é um hash criptográfico seguro. Para senhas, assinaturas, pagamentos ou tokens, use Web Crypto API ou uma biblioteca auditada. Aqui ela serve apenas como exemplo pequeno de passagem de bytes pela fronteira JS-Wasm.
Carregamento no Vite com wrapper tipado
Depois do build, você terá pkg/wasm_lab.js e pkg/wasm_lab.d.ts. No Vite, importe o módulo gerado, espere init() e exponha uma API pequena em TypeScript. Assim a UI não chama Wasm antes da inicialização nem repete o carregamento a cada clique.
// src/wasm-client.ts
import init, {
fnv1a32,
invert_rgba,
sum_csv_column,
} from "../pkg/wasm_lab";
export type WasmClient = {
invertImage(imageData: ImageData): Promise<ImageData>;
sumCsvColumn(csv: string, columnIndex: number): Promise<number>;
checksum(bytes: Uint8Array): Promise<number>;
};
let initPromise: Promise<void> | undefined;
async function ensureWasm(): Promise<void> {
initPromise ??= init().then(() => undefined);
return initPromise;
}
export const wasmClient: WasmClient = {
async invertImage(imageData) {
await ensureWasm();
const pixels = new Uint8Array(
imageData.data.buffer,
imageData.data.byteOffset,
imageData.data.byteLength,
);
invert_rgba(pixels);
return imageData;
},
async sumCsvColumn(csv, columnIndex) {
await ensureWasm();
return sum_csv_column(csv, columnIndex);
},
async checksum(bytes) {
await ensureWasm();
return fnv1a32(bytes);
},
};
// src/main.ts
import { wasmClient } from "./wasm-client";
const fileInput = document.querySelector<HTMLInputElement>("#csv-file");
const output = document.querySelector<HTMLPreElement>("#output");
fileInput?.addEventListener("change", async () => {
const file = fileInput.files?.[0];
if (!file || !output) return;
const csv = await file.text();
const total = await wasmClient.sumCsvColumn(csv, 2);
output.textContent = `column 2 total: ${total.toFixed(2)}`;
});
Com wasm-pack --target web, comece pela configuração padrão do Vite. Plugins só entram quando você importa .wasm bruto ou muda o formato de bundling. No começo, os erros mais comuns são caminho errado e chamada antes de init().
Prompt de revisão para Claude Code
Claude Code deve implementar e depois revisar de forma crítica. O prompt de revisão precisa ser estreito: inicialização assíncrona, cópias, fronteira JS-Wasm, DOM, tipos e comandos de verificação.
Review only these files:
- src/lib.rs
- pkg/wasm_lab.d.ts
- src/wasm-client.ts
- src/main.ts
- src/bench.ts
Goal:
Integrate the Rust WebAssembly module into the Vite app without changing UI behavior.
Check:
1. init() is awaited before any exported Wasm function is called.
2. init() is cached and not repeated for every click or file upload.
3. Large arrays cross the JS-Wasm boundary at most once per user action.
4. DOM updates stay in TypeScript, not inside Rust.
5. The wrapper exposes typed methods and keeps generated pkg files out of hand edits.
6. Benchmarks compare the same input data for JavaScript and Wasm.
Run:
wasm-pack build --target web --out-dir pkg
npm run typecheck
npm run build
Em times, coloque essas regras no CLAUDE.md. Cada alteração de Wasm passa pela mesma revisão, sem depender da memória de uma pessoa.
Benchmark e verificação
Não aprove uma migração para Wasm por sensação. Meça o mesmo input e o mesmo output. O benchmark abaixo compara inversão RGBA em JavaScript e Wasm.
// src/bench.ts
import { wasmClient } from "./wasm-client";
function invertJs(pixels: Uint8Array): void {
for (let index = 0; index < pixels.length; index += 4) {
pixels[index] = 255 - pixels[index];
pixels[index + 1] = 255 - pixels[index + 1];
pixels[index + 2] = 255 - pixels[index + 2];
}
}
function cloneImageData(source: Uint8Array, width: number, height: number): ImageData {
return new ImageData(new Uint8ClampedArray(source), width, height);
}
export async function runBench(): Promise<void> {
const width = 1920;
const height = 1080;
const source = new Uint8Array(width * height * 4);
crypto.getRandomValues(source);
const jsPixels = new Uint8Array(source);
const wasmImage = cloneImageData(source, width, height);
const jsStart = performance.now();
invertJs(jsPixels);
const jsMs = performance.now() - jsStart;
const wasmStart = performance.now();
await wasmClient.invertImage(wasmImage);
const wasmMs = performance.now() - wasmStart;
console.table({
javascriptMs: Number(jsMs.toFixed(2)),
wasmMs: Number(wasmMs.toFixed(2)),
ratio: Number((jsMs / wasmMs).toFixed(2)),
});
}
wasm-pack build --target web --out-dir pkg
npm run typecheck
npm run build
npm run dev
Se Wasm não vencer, investigue conversões de dados antes de culpar Rust. Canvas, ImageData, strings e build de desenvolvimento podem esconder o ganho real. Envie o resultado para Claude Code e peça uma recomendação: manter Wasm, mover para Web Worker ou otimizar JavaScript.
Armadilhas comuns
A primeira armadilha é a inicialização assíncrona. init() precisa terminar antes de qualquer export ser chamado. Cacheie a promise no wrapper.
A segunda é o tamanho do bundle. Cada crate Rust pode aumentar o .wasm. Comece com uma função e confira o build de produção.
A terceira é o custo da fronteira JS-Wasm. Evite chamadas pequenas em loop; passe arrays, strings ou buffers maiores.
A quarta é tentar manipular DOM dentro de Wasm. Eventos, renderização, acessibilidade e mensagens de erro devem ficar em TypeScript.
A quinta é ignorar cópias de memória. Typed arrays, strings e ImageData podem ser copiados pelos bindings. O benchmark deve incluir essa conversão.
A sexta é compatibilidade de navegador e headers. Wasm básico é amplamente suportado, mas Wasm threads e SharedArrayBuffer exigem COOP e COEP. Sites com anúncios, iframes ou CDN precisam testar cedo.
Operação em equipe e CTA
Para uma prova individual, este código basta. Em equipe, defina que lógica vai para Rust, o que fica em TypeScript, como tratar arquivos gerados em pkg, quais navegadores são alvo e qual benchmark bloqueia o merge. Essas regras devem estar no CLAUDE.md e nos prompts de revisão.
ClaudeCodeLab pode transformar isso em um fluxo real no seu repositório: escolher o caso de uso certo, revisar ativos Rust/C++, desenhar benchmarks e treinar o time para usar Claude Code com segurança. Se WebAssembly afeta performance de produção, privacidade no navegador ou arquitetura frontend compartilhada, comece por Claude Code training and consultation.
Nota de verificação
Ao testar esse fluxo, o problema inicial não foi a função Rust, mas o momento de chamar init(). Quando a inicialização ficou dentro de wasm-client.ts, imagem, CSV e checksum seguiram o mesmo caminho. Inputs pequenos foram rápidos o bastante em JavaScript; buffers Full HD e CSVs maiores mostraram melhor o trade-off. A lição é medir a fronteira inteira, não só o corpo da função.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Permission receipt no Claude Code: escopo, prova e rollback
Padrão de permission receipt para Claude Code: ações permitidas, limites de aprovação, comandos de prova, rollback e CTA de receita.
Agent Harness seguro para Claude Code e Codex: permissoes, verificacao e rollback
Monte uma base segura para agentes com Claude Code e Codex usando politicas, plano, verificacao e recuperacao.
Subagentes no Claude Code: guia prático para delegar trabalho com segurança
Guia prático de subagentes no Claude Code para dividir artigos e código: regras, prompts, riscos e checklist.