WebAssembly i TypeScript
Den här artikeln förklarar WebAssembly i TypeScript.
Vi förklarar praktiska och lättförståeliga metoder för att integrera TypeScript och WebAssembly.
YouTube Video
WebAssembly i TypeScript
WebAssembly (Wasm) är en binärformat-runtime som körs med nästan inbyggd hastighet i webbläsaren. Genom att anropa Wasm från TypeScript kan du effektivt utnyttja beräkningsintensiva processer och befintliga egna bibliotek skrivna i C/C++ eller Rust.
Grundläggande körflöde
Här förklarar vi det grundläggande körflödet för Wasm. TypeScript (eller webbläsaren) hämtar .wasm-filen, instansierar den och anropar exporterade funktioner.
-
- Skapa en .wasm-binär med AssemblyScript, Rust, C++ eller förbered en befintlig.
-
- Hämta .wasm-filen i TypeScript (eller i webbläsaren) och instansiera den synkront eller asynkront.
-
- Anropa de exporterade funktionerna och dela minne med hjälp av
WebAssembly.Memoryom det behövs.
- Anropa de exporterade funktionerna och dela minne med hjälp av
WebAssembly.instantiateStreaming
Nästa visar vi ett grundläggande exempel på att läsa in en Wasm-fil och anropa en exporterad funktion. Webbläsaren behöver stödja instantiateStreaming.
Följande kod är ett exempel på hur man hämtar simple.wasm från servern och anropar funktionen 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);- Funktionerna i Wasm lagras i
instance.exports. - Eftersom TypeScript inte får någon typinformation behöver du använda
@ts-ignoreeller skapa egna typdefinitioner.
Arbetsflöde med AssemblyScript
AssemblyScript låter dig skriva Wasm med en syntax som liknar TypeScript, vilket gör det till ett lättillgängligt val för TypeScript-utvecklare. Här förbereder vi en enkel funktion, kompilerar den till .wasm och .d.ts, och anropar den från 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}- Genom att använda
asc(kompilatorn förAssemblyScript), kan du generera en.wasm-fil och, om du vill, en typdefinitionsfil.d.ts. För att prova det lokalt, installeraassemblyscriptmed npm och 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 toolsHär är ett exempel på att hämta och anropa från TypeScript-sidan.
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 kräver noggrann hantering av minnesmodeller och strängar, men är mycket lätt att använda för grundläggande numeriska beräkningar.
Rust + wasm-bindgen (Ett kraftfullt och ofta använt alternativ)
Detta avsnitt förklarar arbetsflödet för att skriva Wasm i Rust och koppla det till JavaScript eller TypeScript med hjälp av wasm-bindgen. Här använder vi en enkel Fibonacci-funktion som exempel för att visa hur det genererade modulen importeras som ett ES-modul.
Exportera funktioner från Rust-sidan med hjälp 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-packellerwasm-bindgenCLI genereras typdefinitioner för TypeScript och JS-omslag, vilket gör att du kan importera dem direkt 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 pkgPå TypeScript-sidan importerar och använder du ES-modulen från 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-packgenererar JavaScript-omslag och.d.ts-typdefinitioner, vilket gör det enkelt att använda från TypeScript. Observera att när du angerwebför--target-alternativet i kommandotwasm-pack, krävs asynkron initialisering.
Praktiskt exempel på minnesdelning: Skicka och bearbeta arrayer (låg nivå)
Vid utbyte av stora datamängder med Wasm är det viktigt att dela ArrayBuffer för effektiv dataöverföring. Här visar vi ett exempel med AssemblyScript, men samma princip gäller med Rusts wasm-bindgen.
På AssemblyScript-sidan förbered en exporterad funktion för att skriva till minnet. Till exempel skulle en funktion för att kvadrera varje element i en array se ut så här.
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}För att specificera minnesinställningarna som används av AssemblyScript ska du förbereda följande 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- För att anropa denna funktion behöver du kopiera
ArrayBuffertill Wasm-minnesutrymmet och skicka pekaren.
Nedan är ett exempel på att använda WebAssembly.Memory i TypeScript för att kopiera data och anropa funktionen.
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är det delade linjära minnet; om du minimerar kopior förbättras bearbetningshastigheten så mycket som möjligt. Notera också att pekare refererar till positioner i byte, medanTypedArrayhanteras med elementantal, så var försiktig så att du inte blandar ihop dessa skillnader.
Typsäker hantering: Förbered typdefinitioner för TypeScript
Wasm-exporter är JavaScript-objekt, så om du tillhandahåller typdefinitioner på TypeScript-sidan blir utvecklingen enklare. Här är ett enkelt exempel på en typdefinitionsfil.
Följande visar en minimal typdefinition som du kan skapa manuellt som simple.d.ts.
1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;- Placera detta i
typeRootsi dintsconfig.jsoneller använddeclare moduleför att aktivera typkontroll.wasm-packgenererar automatiskt.d.ts-filer, så det är användbart att använda dem.
Initialiseringsmönster vid körning: Synkront vs asynkront
Eftersom Wasm-moduler kräver I/O (hämtning) och kompilering är asynkron initialisering vanligt. Det finns dock även ett mönster där du cachar WebAssembly.Module i förväg och instansierar den synkront.
Nedan är en grundläggande kodstruktur för att initiera WebAssembly asynkront. I faktiska projekt rekommenderas detta mönster.
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 gör det enkelt att flexibelt inkludera felhantering och lat inläsning, vilket gör det mest praktiskt i verklig utveckling. Dessutom innehåller koden som genereras av
wasm-packettinit()-API för initialisering, så det är bra att vänja sig vid detta flöde för att underlätta arbetet.
Praktiska prestandafrågor
Här är några punkter att tänka på för att förbättra prestandan avsevärt. Vänligen använd dessa optimeringstips när du kombinerar TypeScript och WebAssembly.
- När funktionsanrop sker mycket ofta kan övergången mellan JavaScript och Wasm bli en flaskhals. Vi rekommenderar att du samlar data och bearbetar dem på en gång så mycket som möjligt.
- Minnesallokering och kopiering ökar bearbetningsbelastningen. Använd delade buffertar och pekare för att minimera dessa operationer.
- Var försiktig när du hanterar flyttal. I TypeScript blir de av typen
number, men du kan hantera dem korrekt genom att matcha typerna på Wasm-sidan.
Sammanfattning
Genom att kombinera TypeScript och WebAssembly kan du uppnå nästan inbyggd prestanda i webbläsaren. Detta är särskilt effektivt för beräkningsintensiva uppgifter eller när du vill dra nytta av befintliga inbyggda resurser. Denna kombination är ett mycket kraftfullt alternativ när du vill förbättra prestandan för din webbapplikation.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.