WebAssembly (.wasm, .wat) Jak zmieni się tworzenie weba?

WebAssembly to możliwość odpalania kodu napisanego w kompilowalnych językach, takich jak C/C++/Rust (i innych) bezpośrednio w przeglądarce.
WebAssembly (.wasm, .wat) Jak zmieni się tworzenie weba?

WebAssembly to możliwość odpalania skompilowanego kodu bezpośrednio w przeglądarce. Feature, który otwiera nowy rozdział w świecie weba. Czym zatem jest WebAssembly? Jeśli jeszcze nie wiesz, zapraszam do lektury.

WebAssembly

WebAssembly (.wasm, .wat) to standard webowy, który opisuje binarny oraz podobny do assemblera format używany do odpalania kodu w przeglądarce internetowej. Format, który z założenia ma nie być pisany ręcznie, a ma być wynikiem kompilacji. Jest tworzony przez organizację W3C, w której są inżynierowie z Mozilli, Microsoftu, Google i Apple. Dzięki temu WebAssembly jest już supportowany przez większość najpopularniejszych przeglądarek: Edge 16, Chrome już od wersji 57, Firefox od v52, Safari 11, iOs Safari 11, Opera od v44, Chrome i Firefox na Androida oraz w node.js (od v8 LTS). IE11 nie supportuje WebAssembly, ale o IE11 i tak należało by już zapomnieć. Jak widać na początku 2018 wsparcie jest już bardzo szerokie, także najwyższy czas dowiedzieć się więcej czym WebAssembly jest. WASM jest obecnie wersji 1.0. Jednak można powiedzieć, że na razie jest w wersji MVP (ang. minimum viable product), czyli jeszcze nie ma wszystkich funkcjonalności, które twórcy chcieli, żeby WASM wspierał np. bezpośredni dostęp do drzewa DOM. Wziął się, bo ktoś pomyślał „a co by było gdybyśmy mieli inny silnik do odpalania kodu, inny niż JavaScript?”

webassembly browser support

źródło: https://caniuse.com/#feat=wasm

Podstawy WebAssembly

WebAssembly to możliwość odpalania kodu napisanego w kompilowalnych językach, takich jak C/C++/Rust (i innych) bezpośrednio w przeglądarce. Dzięki temu jeśli masz kod, który jest za wolny do odpalenia w JavaScript do tej pory trzeba było przenieść jego wykonanie na serwer, teraz można wykonanć go bezpośrednio u klienta z szybkością zbliżoną do kodu natywnego. Albo jeśli miałeś fajny kod w C++ do wyświetlania bryły 3D, to żeby go wyświetlić w przeglądarce trzeba było przepisać kod do JS. Teraz, ten sam kod, napisany np. 5 lat temu, wystarczy skompilować do WASM – będzie działał prawie tak samo szybko jak kod natywny i bez przepisywania. Silniki gier, Unity (przykład gry) czy Unreal już dodały możliwość eksportowania do WebAssembly, więc możesz napisać grę i odpalać ją bezpośrednio w przeglądarce bez rzadnych wtyczek i instalowania. Dodatkowo bez kompilowania kodu na każdą platformę oddzielnie (Linux, Mac, Windows, 32/64 bity itd.) i generowania przy każdej wersji setek MB paczek, które jeśli udostępniane są w sieci, muszą być hostowane.

Powstaje pytanie, po co kolejny standard? WASM jest inny, gdyż to nie jest fizyczna architektura. Definiuje wirtualną architekturę, która będzie jednakowa dla wszystkich fizycznych architektur (np. x86, x64, arm7, arm8). Jeśli twórcy przeglądarki będą wspierać daną architekturę, to Twój kod też będzie na niej działać.

Jeśli chcesz zobaczyć jak wygląda transpilowanie do Wasm, to przejdź do WasmExplorer tutaj. Możesz użyć poniższego kodu, jako najprostszego przykładu:

int foo(int x) {
  return x / 4;
}

Czemu potrzebujemy WASM?

  • JavaScript nie zapewnia odpowiedniej szybkości/optymalizacji
  • pluginy mają słabe bezpieczeństwo i w ogóle umierają
  • odpalanie istniejącego już kodu, świetnym przykładem jest tu pdf engine na którego pisanie poświęcono już mnóstwo czasu, fajnie było by go wykorzystać, albo jakieś funkcje kryptograficzne zaiplementowane w innych językach niż JS

Czym jeszcze raz jest WebAssembly?

  1. otwarta specyfikacją binarnego formatu, do którego można kompilować programy, które będą mogły byś odpalane w różnych przeglądarkach

  2. wirtualną architektórą ogólnego przeznaczenia

Jak działa WebAssembly

Przeglądarka pobiera stronę, potem JavaScript i plik WASM. Kod JavaScript tworzy instancje WebAssembly i ładuje do niej WASM używając: webassembly.instantiate czy WebAssembly.instantiateStreaming Przeglądarka kompiluje kod do postaci binarnej, a kiedy kod jest gotowy &ndash uruchamia.

Przykład WebAssembly z TypeScript

Tak, można przekompilować TypeScript do WASM. Wszystko dzięki projektowi AssemblyScript (github).

AssemblyScript compiles strictly typed TypeScript to WebAssembly using Binaryen. It generates minimal WebAssembly modules while being just an npm install away.

Najprostsza strona HTML będzie zawierać link do JavaScriptu i kontener na wynik modułu WASM.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body style="background: #fff">
  <span id="container"></span>
  <script src="./main.js"></script>
</body>
</html>

Kod modułu WASM napisany w TypeScript:

declare function sayHello(): void;

sayHello();

export function add(x: i32, y: i32): i32 {
  return x + y;
}

export const aa = 22;

Powyższy kod TypeScript wykonuje funkcję sayHello(); i eksportuje funkcję add oraz stałą aa. A zamieniony przy użyciu AssemblyScript/Binaryen do WASM wygląda następująco:

(module
  (type $t0 (func))
  (type $t1 (func (param i32 i32) (result i32)))
  (import "env" "sayHello" (func $main/sayHello (type $t0)))
  (func $main/add (export "add") (type $t1) (param $p0 i32) (param $p1 i32) (result i32)
    get_local $p0
    get_local $p1
    i32.add
    return)
  (func $start (type $t0)
    call $main/sayHello)
  (memory $memory (export "memory") 1)
  (global $aa (export "aa") i32 (i32.const 22))
  (global $g1 i32 (i32.const 4))
  (start 2))

I teraz najciekawsze, kod JavaScript inicjalizujący moduł:

WebAssembly.instantiateStreaming(fetch("../out/main.wasm"), {
  env: {
    sayHello: function() {
      console.log("Hello from module!");
    },
    abort: function(msg, file, line, column) {
      console.error("abort called at main.ts:" + line + ":" + column);
    }
  }
}).then(result => {
  const exports = result.instance.exports;
  document.getElementById("container").innerText = "Result: " + exports.add(19, 23);
  console.log(exports.aa);
});

Do instantiateStreaming przekazywany jest source przez fetch pliku .wasm oraz obiekt z funkcjami JavaScriptowymi, które mają być przekazane do modułu. instantiateStreaming zwraca promise stąd po kropce słówko then. Funkcja przekazana do then, pod parametrem result odbiera instancje modułu. W polu exports znajdują się wszystkie wyeksportowane zmienne z WASM dostępne w JS. Do kontenera od id „container” dostanie wpisane: Result: 42 Natomiast do konsoli wypisane zostanie:

Hello from module!
22

webassembly.studio

Jeśli chcesz pobawić się online WebAssembly możesz przejść na stronę https://webassembly.studio/ link.