WebAssembly and TypeScript: Advanced Integration Techniques

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Advanced WebAssembly and TypeScript Integration



WebAssembly (Wasm) has revolutionized web development by allowing code to run at near-native speed directly in the browser. However, at an advanced level, developers face a challenge: how to effectively combine TypeScript's strict typing with Wasm's low-level capabilities? In this article, we will explore techniques that go beyond basic examples: manual memory management, creating fully typed wrappers, optimizing calls, and building complex projects.



This material is intended for developers already familiar with the basics of Wasm and TypeScript. We will not dwell on compiling "Hello World" — we will focus on real-world tasks: working with buffers, data structures, multithreading, and profiling.



1. Advanced Memory Management: Manual Allocation and Structures



A basic example of exporting a function from Wasm to TypeScript usually works with primitives. At an advanced level, you will need to pass complex data: strings, arrays of structures, images. Let's look at how this is done using WebAssembly.Memory.



1.1. Memory Allocation and Deallocation



Suppose we have a Wasm module written in AssemblyScript or C that exports malloc and free functions. In TypeScript, we create a wrapper:



// types.tsexport interface WasmMemory {  malloc(size: number): number;  free(ptr: number): void;  memory: WebAssembly.Memory;}

// memory-manager.tsexport class MemoryManager { private wasm: WasmMemory;

constructor(wasm: WasmMemory) { this.wasm = wasm; }

allocateString(str: string): number { const encoder = new TextEncoder(); const bytes = encoder.encode(str); const ptr = this.wasm.malloc(bytes.length + 1); // +1 for null terminator const view = new Uint8Array(this.wasm.memory.buffer); view.set(bytes, ptr); view[ptr + bytes.length] = 0; // null terminator return ptr; }

readString(ptr: number): string { const view = new Uint8Array(this.wasm.memory.buffer); let end = ptr; while (view[end] !== 0) end++; const bytes = view.slice(ptr, end); return new TextDecoder().decode(bytes); }

freeString(ptr: number): void { this.wasm.free(ptr); }}


This approach ensures that memory does not leak, and TypeScript fully controls the data lifecycle.



1.2. Working with Data Structures



To pass objects (e.g., a point with coordinates {x: f32, y: f32}), use DataView:



interface Point { x: number; y: number; }

export class PointManager { private wasm: WasmMemory; private readonly SIZE = 8; // 2 float32

constructor(wasm: WasmMemory) { this.wasm = wasm; }

createPoint(p: Point): number { const ptr = this.wasm.malloc(this.SIZE); const view = new DataView(this.wasm.memory.buffer); view.setFloat32(ptr, p.x, true); view.setFloat32(ptr + 4, p.y, true); return ptr; }

readPoint(ptr: number): Point { const view = new DataView(this.wasm.memory.buffer); return { x: view.getFloat32(ptr, true), y: view.getFloat32(ptr + 4, true) }; }}


Important: always check memory alignment. Wasm requires 4-byte alignment for f32.



2. Typing Wasm Imports and Exports in TypeScript



Working with Wasm without typing is dangerous: you could pass a number instead of a pointer and get a segfault. Use strict interfaces.



2.1. Generating Types from Wasm



The wasm-objdump or wasm2wat tool allows you to obtain function signatures. For an AssemblyScript project, use a type generator:



// wasm-types.d.tsdeclare namespace WasmModule {  interface Exports {    add(a: number, b: number): numb                

Blogs

Book Recommendations