WebAssembly dalam TypeScript

WebAssembly dalam TypeScript

Artikel ini menjelaskan tentang WebAssembly dalam TypeScript.

Kami akan menjelaskan metode praktis dan mudah dipahami untuk mengintegrasikan TypeScript dengan WebAssembly.

YouTube Video

WebAssembly dalam TypeScript

WebAssembly (Wasm) adalah runtime berbentuk biner yang berjalan dengan kecepatan hampir setara native di dalam browser. Dengan memanggil Wasm dari TypeScript, Anda dapat memanfaatkan proses yang memerlukan komputasi tinggi dan pustaka native yang sudah ada, yang ditulis dalam C/C++ atau Rust, secara efisien.

Alur Eksekusi Dasar

Di sini, kami akan menjelaskan alur eksekusi dasar dari Wasm. TypeScript (atau browser) mengambil file .wasm, menginstansiasinya, dan memanggil fungsi yang diekspor.

    1. Buat biner .wasm menggunakan AssemblyScript, Rust, C++, atau siapkan yang sudah ada.
    1. Ambil file .wasm di TypeScript (atau browser) dan instansiasikan secara sinkron atau asinkron.
    1. Panggil fungsi yang diekspor dan bagikan memori dengan menggunakan WebAssembly.Memory jika diperlukan.

WebAssembly.instantiateStreaming

Selanjutnya, kami akan menunjukkan contoh dasar memuat file Wasm dan memanggil fungsi yang diekspor. Browser harus mendukung instantiateStreaming.

Kode berikut adalah contoh mengambil simple.wasm dari server dan memanggil fungsi 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);
  • Fungsi-fungsi di dalam Wasm disimpan di instance.exports.
  • Karena TypeScript tidak menerima informasi tipe, Anda perlu menggunakan @ts-ignore atau membuat definisi tipe sendiri.

Alur Kerja Menggunakan AssemblyScript

AssemblyScript memungkinkan Anda menulis Wasm dengan sintaks yang mirip dengan TypeScript, sehingga mudah diakses untuk pengembang TypeScript. Di sini, kita menyiapkan fungsi sederhana, membangunnya menjadi .wasm dan .d.ts, lalu memanggilnya dari 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}
  • Dengan menggunakan asc (kompiler AssemblyScript), Anda dapat menghasilkan file .wasm dan, jika diinginkan, file definisi tipe .d.ts. Untuk mencobanya secara lokal, instal assemblyscript dengan npm dan bangunlah.
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

Berikut adalah contoh pengambilan dan pemanggilan dari sisi TypeScript.

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 membutuhkan penanganan hati-hati untuk model memori dan string, tetapi sangat mudah digunakan untuk perhitungan numerik dasar.

Rust + wasm-bindgen (Pilihan yang kuat dan sering digunakan)

Bagian ini menjelaskan alur kerja penulisan Wasm dalam Rust dan menghubungkannya dengan JavaScript atau TypeScript menggunakan wasm-bindgen. Di sini, kami menggunakan fungsi Fibonacci sederhana sebagai contoh untuk menunjukkan cara mengimpor modul yang dihasilkan sebagai modul ES.

Ekspor fungsi dari sisi Rust menggunakan 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}
  • Saat Anda membangun dengan wasm-pack atau CLI wasm-bindgen, definisi tipe untuk TypeScript dan pelapis JS akan dibuat, sehingga Anda dapat langsung mengimpornya sebagai 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

Dari sisi TypeScript, impor dan gunakan modul ES dari 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 menghasilkan pelapis JavaScript dan definisi tipe .d.ts, sehingga mudah digunakan dari TypeScript. Harap diperhatikan bahwa ketika Anda menentukan web untuk opsi --target pada perintah wasm-pack, inisialisasi secara asinkron diperlukan.

Contoh Dunia Nyata tentang Berbagi Memori: Mengoper dan Memproses Array (Tingkat Rendah)

Ketika mentransfer data dalam jumlah besar dengan Wasm, membagi ArrayBuffer untuk pertukaran data yang efisien sangatlah penting. Di sini kami menunjukkan contoh menggunakan AssemblyScript, tetapi prinsip yang sama berlaku untuk wasm-bindgen di Rust.

Dari sisi AssemblyScript, siapkan fungsi yang diekspor untuk menulis ke memori. Sebagai contoh, fungsi untuk mengkuadratkan setiap elemen array akan terlihat seperti ini.

 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}

Untuk menentukan pengaturan memori yang digunakan oleh AssemblyScript, siapkan asconfig.json berikut.

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
  • Untuk memanggil fungsi ini, Anda perlu menyalin ArrayBuffer ke ruang memori Wasm dan mengoper pointer-nya.

Berikut adalah contoh menggunakan WebAssembly.Memory di TypeScript untuk menyalin data dan memanggil fungsi.

 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 adalah memori linier bersama; meminimalkan penyalinan dapat meningkatkan kecepatan pemrosesan semaksimal mungkin. Perhatikan juga bahwa pointer mengacu pada posisi dalam byte, sementara TypedArray dikelola berdasarkan jumlah elemen, jadi hati-hati untuk tidak tertukar.

Penanganan Aman Tipe: Siapkan Definisi Tipe TypeScript

Ekspor Wasm adalah objek JavaScript, sehingga menyediakan definisi tipe di sisi TypeScript akan mempermudah pengembangan. Berikut adalah contoh sederhana file definisi tipe.

Berikut menunjukkan definisi tipe minimal yang dapat Anda buat secara manual sebagai simple.d.ts.

1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;
  • Menempatkan ini di typeRoots pada tsconfig.json Anda atau menggunakan declare module akan mengaktifkan pemeriksaan tipe. wasm-pack secara otomatis menghasilkan file .d.ts, jadi sangat berguna untuk menggunakannya.

Pola Inisialisasi pada Waktu Runtime: Sinkron vs Asinkron

Karena modul Wasm membutuhkan I/O (pengambilan) dan kompilasi, inisialisasi asinkron adalah hal yang umum. Namun, ada juga pola di mana Anda menyimpan cache WebAssembly.Module terlebih dahulu dan menginstansiasikannya secara sinkron.

Berikut adalah struktur kode dasar untuk inisialisasi WebAssembly secara asinkron. Dalam proyek nyata, pola ini sangat direkomendasikan.

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}
  • Inisialisasi asinkron memudahkan untuk menggabungkan penanganan kesalahan dan pemuatan malas secara fleksibel, sehingga paling nyaman dalam pengembangan nyata. Selain itu, kode yang dihasilkan oleh wasm-pack mencakup API init() untuk inisialisasi, sehingga terbiasa dengan alur ini akan membantu kelancaran pekerjaan Anda.

Pertimbangan Kinerja Praktis

Berikut adalah beberapa poin penting yang perlu diperhatikan untuk meningkatkan kinerja secara signifikan. Silakan merujuk ke tips optimasi ini saat mengombinasikan TypeScript dan WebAssembly.

  • Ketika pemanggilan fungsi sangat sering, overhead panggilan antara JavaScript dan Wasm dapat menjadi hambatan. Kami menyarankan untuk mengelompokkan data dan memprosesnya sekaligus sebanyak mungkin.
  • Alokasi dan penyalinan memori meningkatkan beban pemrosesan. Manfaatkan buffer dan pointer bersama untuk meminimalkan operasi tersebut.
  • Berhati-hatilah saat menangani angka floating-point. Di TypeScript, mereka menjadi tipe number, tetapi Anda dapat menangani secara akurat dengan mencocokkan tipe di sisi Wasm.

Ringkasan

Dengan mengombinasikan TypeScript dan WebAssembly, Anda dapat mencapai performa hampir setara native di browser. Ini sangat efektif untuk tugas yang memerlukan komputasi tinggi atau ketika Anda ingin memanfaatkan aset native yang sudah ada. Kombinasi ini adalah pilihan yang sangat kuat ketika Anda ingin meningkatkan kinerja aplikasi web Anda.

Anda dapat mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Silakan periksa juga saluran YouTube kami.

YouTube Video