WebAssembly sa TypeScript

WebAssembly sa TypeScript

Ipinaliliwanag ng artikulong ito ang WebAssembly sa TypeScript.

Ipapaliwanag namin ang praktikal at madaling intindihing mga paraan ng pagsasama ng TypeScript at WebAssembly.

YouTube Video

WebAssembly sa TypeScript

Ang WebAssembly (Wasm) ay isang binary format runtime na tumatakbo nang halos kasing-bilis ng native sa loob ng browser. Sa pagtawag ng Wasm mula sa TypeScript, maaari mong magamit nang mahusay ang mga computation-heavy na proseso at umiiral na mga native libraries na nakasulat sa C/C++ o Rust.

Pangunahing Daloy ng Pagpapatupad

Dito, ipapaliwanag namin ang batayang daloy ng pagpapatakbo ng Wasm. Kumukuha ang TypeScript (o ang browser) ng .wasm file, ini-instantiate ito, at tinatawag ang mga exported functions.

    1. Gumawa ng .wasm binary gamit ang AssemblyScript, Rust, C++, o gumamit ng umiiral na .wasm file.
    1. I-fetch ang .wasm file sa TypeScript (o sa browser) at i-instantiate ito nang synchronous o asynchronous.
    1. Tawagin ang mga exported function at magbahagi ng memory gamit ang WebAssembly.Memory kung kinakailangan.

WebAssembly.instantiateStreaming

Susunod, ipapakita namin ang isang batayang halimbawa ng pag-load ng Wasm file at pagtawag ng exported function. Kailangang suportado ng browser ang instantiateStreaming.

Ang sumusunod na code ay isang halimbawa ng pagkuha ng simple.wasm mula sa server at pagtawag sa function na 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);
  • Ang mga function sa loob ng Wasm ay naka-imbak sa instance.exports.
  • Dahil hindi natatanggap ng TypeScript ang impormasyon ng type, kailangan mong gumamit ng @ts-ignore o gumawa ng sarili mong type definitions.

Daloy ng Trabaho gamit ang AssemblyScript

Pinapayagan ka ng AssemblyScript na magsulat ng Wasm gamit ang syntax na katulad ng TypeScript, kaya madaling lapitan ito para sa mga TypeScript developer. Dito, naghahanda tayo ng isang simpleng function, bina-build ito bilang .wasm at .d.ts, at tinatawag mula sa 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}
  • Sa paggamit ng asc (ang compiler ng AssemblyScript), maaari kang gumawa ng .wasm file at, kung gusto mo, isang type definition na .d.ts file. Para subukan ito sa lokal, mag-install ng assemblyscript gamit ang npm at i-build ito.
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

Narito ang isang halimbawa ng pag-fetch at pagtawag mula sa bahagi ng 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);
  • Kinakailangan ng AssemblyScript ang maingat na paghawak ng memory models at mga string, ngunit napakadali nitong gamitin para sa mga simpleng numerical calculation.

Rust + wasm-bindgen (Isang malakas at karaniwang ginagamit na opsyon)

Ipinaliwanag sa seksyong ito ang daloy ng paggawa ng Wasm gamit ang Rust at ang pagdugtong nito sa JavaScript o TypeScript gamit ang wasm-bindgen. Dito, gagamit tayo ng simpleng Fibonacci function bilang halimbawa kung paano i-import ang nabuo na module bilang ES module.

I-export ang mga function mula sa Rust gamit ang 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}
  • Kapag nag-build ka gamit ang wasm-pack o ang wasm-bindgen CLI, awtomatikong binubuo ang mga type definition para sa TypeScript at JS wrapper, kaya maaari mong i-import nang direkta bilang 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

Sa bahagi ng TypeScript, i-import at gamitin ang ES module mula sa 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);
  • Ang wasm-pack ay gumagawa ng JavaScript wrappers at .d.ts type definitions, kaya madali itong gamitin mula sa TypeScript. Pakitandaan na kapag itinakda mo ang web para sa pagpipiliang --target ng utos na wasm-pack, kinakailangan ang asynchronous na pagsisimula.

Aktwal na Halimbawa ng Pagbabahagi ng Memory: Pagpasa at Pagproseso ng Arrays (Low Level)

Kapag nagpapalitan ng maraming data sa Wasm, mahalagang magbahagi ng ArrayBuffer para sa mas episyenteng palitan ng datos. Narito ang isang halimbawa gamit ang AssemblyScript, ngunit pareho ang prinsipyo kahit sa paggamit ng wasm-bindgen ng Rust.

Sa bahagi ng AssemblyScript, maghanda ng exported function para magsulat sa memory. Halimbawa, ganito ang hitsura ng function para i-square ang bawat elemento sa isang array.

 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}

Upang tukuyin ang memory settings na gagamitin ng AssemblyScript, ihanda ang sumusunod na 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
  • Para tawagin ang function na ito, kailangang kopyahin ang ArrayBuffer sa memory space ng Wasm at ipasa ang pointer.

Narito ang halimbawa ng paggamit ng WebAssembly.Memory sa TypeScript para kopyahin ang data at tawagin ang function.

 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);
  • Ang memory.buffer ay ang shared na linear memory; ang pag-minimize ng mga pagkopya ay nagpapabilis ng proseso. Tandaan din na ang pointer ay tumutukoy sa posisyon ng bytes, habang ang TypedArray ay naka-base sa element count, kaya siguraduhing hindi ka magkamali dito.

Type-safe na Pamamaraan: Maghanda ng TypeScript Type Definitions

Ang mga Wasm export ay mga JavaScript object, kaya mas madali ang development kung magbibigay ng type definition sa bahagi ng TypeScript. Narito ang isang simpleng halimbawa ng type definition file.

Narito ang minimal na type definition na maaari mong manu-manong gawin bilang simple.d.ts.

1// simple.d.ts
2export function add(a: number, b: number): number;
3export const memory: WebAssembly.Memory;
  • Kapag inilagay ito sa typeRoots ng iyong tsconfig.json o gumamit ng declare module, magiging active ang type checking. Ang wasm-pack ay awtomatikong gumagawa ng .d.ts files, kaya mainam kung gagamitin ito.

Mga Pattern ng Initialization sa Runtime: Synchronous vs Asynchronous

Dahil kailangan ng Wasm modules ang I/O (fetching) at compilation, kadalasan ay asynchronous ang initialization. Gayunpaman, may pattern ding kino-cache muna ang WebAssembly.Module at saka ini-instantiate nang synchronous.

Narito ang batayang code structure para sa pag-initialize ng WebAssembly nang asynchronous. Para sa aktwal na mga proyekto, irerekomenda ang pattern na ito.

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}
  • Mas madali at mas flexible ang asynchronous initialization sa pag-incorporate ng error handling at lazy loading, kaya ito ang pinakamadaling gamitin sa aktwal na development. Bukod dito, ang code na binegenerate ng wasm-pack ay may kasamang init() API para sa initialization, kaya kung masasanay ka sa flow na ito magiging magaan ang iyong development.

Praktikal na mga Dapat Isaalang-alang sa Pagganap (Performance)

Narito ang ilang mga punto na dapat tandaan para sa mas mataas na pagganap. Pakitingnan ang mga tip na ito kapag kinokombina ang TypeScript at WebAssembly para sa optimal na pagganap.

  • Kapag sobrang madalas ang function calls, maaaring maging bottleneck ang overhead ng tawag sa pagitan ng JavaScript at Wasm. Inirerekomenda naming pagsamahin ang data at iproseso ito ng sabay-sabay hangga't maaari.
  • Ang memory allocation at pagkopya ay nagpapataaas ng processing load. Gamitin ang mga shared buffer at pointer upang mabawasan ang mga operasyong ito.
  • Mag-ingat sa paghawak ng floating-point numbers. Sa TypeScript, nagiging number sila, ngunit mas magiging tama ang handling kung itatapat mo ang type sa panig ng Wasm.

Buod

Sa pagsasama ng TypeScript at WebAssembly, makakamit mo ang halos native na pagganap mismo sa browser. Ito ay partikular na epektibo para sa mga computation-intensive na gawain o kapag gusto mong gamitin ang mga umiiral nang native assets. Ang kombinasyong ito ay napakalakas na opsyon kapag nais mong pataasin ang pagganap ng iyong web application.

Maaari mong sundan ang artikulo sa itaas gamit ang Visual Studio Code sa aming YouTube channel. Paki-check din ang aming YouTube channel.

YouTube Video