WebAssembly i TypeScript

WebAssembly i TypeScript

Denne artikkelen forklarer WebAssembly i TypeScript.

Vi vil forklare praktiske og lettfattelige metoder for å integrere TypeScript og WebAssembly.

YouTube Video

WebAssembly i TypeScript

WebAssembly (Wasm) er et binærformat-runtime som kjører med nesten native-hastighet i nettleseren. Ved å kalle Wasm fra TypeScript kan du effektivt utnytte beregningstunge prosesser og eksisterende native-biblioteker skrevet i C/C++ eller Rust.

Grunnleggende utførelsesflyt

Her skal vi forklare den grunnleggende utførelsesflyten til Wasm. TypeScript (eller nettleseren) henter .wasm-filen, instansierer den, og kaller eksporterte funksjoner.

    1. Lag en .wasm-binærfil med AssemblyScript, Rust, C++, eller forbered en eksisterende.
    1. Hent .wasm-filen i TypeScript (eller i nettleseren) og instansier den synkront eller asynkront.
    1. Kall de eksporterte funksjonene og del minne ved bruk av WebAssembly.Memory om nødvendig.

WebAssembly.instantiateStreaming

Neste vil vi vise et grunnleggende eksempel på å laste inn en Wasm-fil og kalle en eksportert funksjon. Nettleseren må støtte instantiateStreaming.

Følgende kode er et eksempel på å hente simple.wasm fra serveren og kalle funksjonen add.

 1// TypeScript: load-and-call.ts
 2// Fetch and instantiate a wasm module and call its exported `add` function.
 3async function run() {
 4  const response = await fetch('http://localhost:3000/simple.wasm');
 5  // Use instantiateStreaming when available for efficiency.
 6  const { instance } = await WebAssembly.instantiateStreaming(response, {});
 7  // @ts-ignore
 8  const result = instance.exports.add(2, 3);
 9  console.log('2 + 3 =', result);
10}
11run().catch(console.error);
  • Funksjoner inne i Wasm lagres i instance.exports.
  • Siden TypeScript ikke får typeinformasjon, må du bruke @ts-ignore eller lage egne typedefinisjoner.

Arbeidsflyt med AssemblyScript

AssemblyScript lar deg skrive Wasm med syntaks lik TypeScript, noe som gjør det til et tilgjengelig valg for TypeScript-utviklere. Her forbereder vi en enkel funksjon, bygger den til .wasm og .d.ts, og kaller den fra TypeScript.

1// assembly/index.ts (AssemblyScript)
2// npm install --save-dev assemblyscript
3
4// Export a simple function to add two integers.
5export function add(a: i32, b: i32): i32 {
6  return a + b;
7}
  • Ved å bruke asc (AssemblyScript-kompilatoren) kan du generere en .wasm-fil og eventuelt en type-definisjonsfil .d.ts. For å prøve det lokalt, installer assemblyscript med npm og bygg det.
1# build commands
2# npm install --save-dev assemblyscript
3npx asc assembly/index.ts   -o build/simple.wasm   -t build/simple.wat   --bindings esm   --exportTable   --sourceMap
4
5# optionally generate d.ts with --exportRuntime or use as-bind / loader tools

Her er et eksempel på å hente og kalle fra TypeScript-siden.

1// ts client that loads AssemblyScript-generated wasm
2async function runAssemblyScript() {
3  const res = await fetch('http://localhost:3000/build/simple.wasm');
4  const { instance } = await WebAssembly.instantiateStreaming(res, {});
5  // @ts-ignore
6  console.log('AssemblyScript add:', instance.exports.add(10, 7));
7}
8runAssemblyScript().catch(console.error);
  • AssemblyScript krever nøye håndtering av minnemodeller og strenger, men er veldig lett å bruke for grunnleggende numeriske beregninger.

Rust + wasm-bindgen (Et kraftig og mye brukt alternativ)

Denne seksjonen forklarer arbeidsflyten for å skrive Wasm i Rust og koble det sammen med JavaScript eller TypeScript ved å bruke wasm-bindgen. Her bruker vi en enkel Fibonacci-funksjon som eksempel for å demonstrere hvordan du importerer den genererte modulen som en ES-modul.

Eksporter funksjoner fra Rust-siden ved bruk av wasm-bindgen.

 1// src/lib.rs (Rust)
 2// install wasm-pack from https://drager.github.io/wasm-pack/installer/
 3use wasm_bindgen::prelude::*;
 4
 5// Export a function to JavaScript using wasm-bindgen.
 6#[wasm_bindgen]
 7pub fn fib(n: u32) -> u32 {
 8    if n <= 1 { return n; }
 9    let mut a = 0;
10    let mut b = 1;
11    for _ in 2..=n {
12        let tmp = a + b;
13        a = b;
14        b = tmp;
15    }
16    b
17}
  • Når du bygger med wasm-pack eller wasm-bindgen CLI, genereres typedefinisjoner for TypeScript og JS-wrappere, slik at du kan importere dem direkte som ESM.
1# build with wasm-pack
2# install wasm-pack from https://drager.github.io/wasm-pack/installer/
3wasm-pack build --target nodejs --out-dir pkg

På TypeScript-siden, importer og bruk ES-modulen fra pkg.

 1// Node.js: import WASM module built with --target web
 2// import init, { fib } from '../pkg/my_wasm_module.js';
 3// Node.js: import WASM module built with --target nodejs
 4import wasm from '../pkg/my_wasm_module.js';
 5
 6async function run() {
 7  //await init();   // --target web
 8  console.log('fib(10)=', wasm.fib(10));
 9}
10
11run().catch(console.error);
  • wasm-pack genererer JavaScript-wrappere og .d.ts typedefinisjoner, noe som gjør det enkelt å bruke fra TypeScript. Vær oppmerksom på at når du spesifiserer web for --target-alternativet i wasm-pack-kommandoen, kreves asynkron initialisering.

Eksempel fra virkeligheten på minnedeling: Overføre og prosessere matriser (lavt nivå)

Når du utveksler store datamengder med Wasm, er det viktig å dele ArrayBuffer for effektiv datautveksling. Her viser vi et eksempel med AssemblyScript, men samme prinsipp gjelder for Rusts wasm-bindgen.

På AssemblyScript-siden, forbered en eksportert funksjon for å skrive til minne. For eksempel vil en funksjon som kvadrerer hvert element i en matrise se slik ut.

 1// assembly/array_ops.ts (AssemblyScript)
 2// Square values in place in the wasm linear memory starting at `ptr` for `len` elements.
 3export function square_in_place(ptr: usize, len: i32): void {
 4  // Treat memory as a pointer to 32-bit integers.
 5  for (let i = 0; i < len; i++) {
 6    let offset = ptr + (i << 2); // i * 4 bytes
 7    let value = load<i32>(offset);
 8    store<i32>(offset, value * value);
 9  }
10}

For å spesifisere minneinnstillingene som brukes av AssemblyScript, forbered følgende asconfig.json.

1{
2  "options": {
3    "memoryBase": 0,
4    "importMemory": false,
5    "initialMemory": 1,
6    "maximumMemory": 10
7  }
8}
1 npx asc assembly/array_ops.ts   -o build/array_ops.wasm   -t build/array_ops.wat   --bindings esm   --exportTable   --sourceMap
  • For å kalle denne funksjonen må du kopiere ArrayBuffer inn i Wasm-minnet og sende pekeren.

Nedenfor er et eksempel på bruk av WebAssembly.Memory i TypeScript for å kopiere data og kalle funksjonen.

 1// TypeScript: use memory to pass array to wasm
 2async function runArrayOps() {
 3  const res = await fetch('http://localhost:3000/build/array_ops.wasm');
 4  const { instance } = await WebAssembly.instantiateStreaming(res, {});
 5  // @ts-ignore
 6  const memory: WebAssembly.Memory = instance.exports.memory;
 7  // Create a view into wasm memory.
 8  const i32View = new Int32Array(memory.buffer);
 9
10  // Example data
11  const input = new Int32Array([1, 2, 3, 4]);
12  // Choose an offset (in i32 elements) to copy data to (simple example: at index 0).
13  const offset = 0;
14  i32View.set(input, offset);
15
16  // Call wasm function: ptr in bytes, len in elements
17  // @ts-ignore
18  instance.exports.square_in_place(offset * 4, input.length);
19
20  // Read back result
21  const result = i32View.slice(offset, offset + input.length);
22  console.log('squared:', result);
23}
24runArrayOps().catch(console.error);
  • memory.buffer er det delte lineære minnet; å minimere kopier forbedrer behandlingshastigheten så mye som mulig. Vær også oppmerksom på at pekere refererer til posisjoner i byte, mens TypedArray styres av elementantall, så vær forsiktig med å ikke blande disse forskjellene.

Typesikker håndtering: Forbered TypeScript-typedefinisjoner

Wasm-eksporter er JavaScript-objekter, så det å lage typedefinisjoner på TypeScript-siden vil lette utviklingen. Her er et enkelt eksempel på en typedefinisjonsfil.

Følgende viser en minimal typedefinisjon du kan lage manuelt som simple.d.ts.

1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;
  • Ved å plassere dette i typeRoots for din tsconfig.json eller ved å bruke declare module får du typesjekking. wasm-pack lager automatisk .d.ts-filer, så det er nyttig å bruke dem.

Initialiseringsmønstre under kjøring: Synkront vs. Asynkront

Siden Wasm-moduler krever I/O (henting) og kompilering, er asynkron initialisering vanlig. Det finnes imidlertid også et mønster hvor du bufret WebAssembly.Module på forhånd og instansierer den synkront.

Nedenfor er den grunnleggende kodestrukturen for å initialisere WebAssembly asynkront. I faktiske prosjekter anbefales dette mønsteret.

1// async init pattern
2async function initWasm(url: string) {
3  const res = await fetch(url);
4  const { instance, module } = await WebAssembly.instantiateStreaming(res, {});
5  return instance;
6}
  • Asynkron initialisering gjør det lett å fleksibelt innføre feilhåndtering og late loading, noe som gjør det mest praktisk i reell utvikling. Videre inneholder koden generert av wasm-pack en init()-API for initialisering, så det å bli vant til denne flyten vil hjelpe arbeidet ditt å gå smidig.

Praktiske ytelseshensyn

Her er noen punkter å ha i bakhodet for betydelige ytelsesforbedringer. Vennligst bruk disse optimaliseringstipsene når du kombinerer TypeScript og WebAssembly.

  • Når funksjonskall er svært hyppige, kan kostnaden ved å kalle mellom JavaScript og Wasm bli en flaskehals. Vi anbefaler å samle data og prosessere det i én omgang så mye som mulig.
  • Minneallokering og kopiering øker behandlingsbelastningen. Bruk delte buffere og pekere for å minimere disse operasjonene.
  • Vær forsiktig når du håndterer flyttall. I TypeScript blir de til typen number, men du kan håndtere dem nøyaktig ved å matche typer på Wasm-siden.

Sammendrag

Ved å kombinere TypeScript og WebAssembly kan du oppnå nesten native-ytelse i nettleseren. Dette er spesielt effektivt for beregningstunge oppgaver eller når du ønsker å utnytte eksisterende native-ressurser. Denne kombinasjonen er et svært kraftig alternativ når du vil forbedre ytelsen til webapplikasjonen din.

Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.

YouTube Video