Začínáme s WebAssembly and Go vytvořením převodníku obrázku na ASCII

Co je WebAssembly?

Než přejdeme k psaní kódu, pojďme nejprve pochopit, co je WebAssembly. WebAssembly nebo WASM je jazyk podobný assembleru, který lze v prohlížeči spustit s téměř nativním výkonem. Nemá být napsán ručně, ale má být považován za cíl kompilace pro jazyky jako C/C++, Golang, Rust, .Net atd. To znamená, že nejprve napíšeme program v jazyce, poté jej převedeme na WASM a poté spustit v prohlížeči. To umožní programu běžet téměř nativní rychlostí a umožní spustit program napsaný v libovolném jazyce, který lze spustit v prohlížeči. Webové aplikace můžete vytvářet v jazyce, který znáte. Neznamená to, že odstraní javascript, ale existuje ruku v ruce s JavaScriptem. Seznam jazyků, které podporují kompilaci WASM, je v úžasné-wasm-langs a další informace na webové stránce WebAssembly a WebAssembly Concepts.

Spusťte v prohlížeči

Teď si ušpiníme ruce nějakým základním WASM a Golangem.

Psaní kódu Go

Pojďme napsat náš první program hello world.

package main

import "fmt"

func main() {
    fmt.Println("Hi from the browser console!!")
}

Kompilace do WebAssembly

Pojďme to zkompilovat do Wasm.

 GOOS=js GOARCH=wasm go build -o main.wasm main.go

Tím se vytvoří main.wasm Soubor WebAssembly, který můžeme importovat a spustit v prohlížeči.

Integrace s javascriptem

Poté, co napíšeme náš Go kód a zkompilujeme jej do WASM, můžeme jej začít integrovat do prohlížeče.

K interakci s Go through wasm budeme potřebovat runtime wrapper Go napsaný v javascriptu. Kód je dodáván s Go 1.11+ a lze jej zkopírovat pomocí následujícího příkazu:

    cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

Nyní jej integrujeme do prohlížeče.

<html>
  <head>
    <meta charset="utf-8" />
    <script src="wasm_exec.js"></script>
    <script>
      const go = new Go()
      WebAssembly.instantiateStreaming(
        fetch('main.wasm'),
        go.importObject
      ).then(result => {
        go.run(result.instance)
      })
    </script>
  </head>
  <body></body>
</html>

WebAssembly.instantiateStreaming zkompiluje a vytvoří instanci kódu WebAssembly. Po vytvoření instance kódu spustíme program Go s go.run(result.instance) . Další informace najdete v dokumentech WebAssembly.instantiateStreaming a Go WebAssembly.
Nyní, když spustíme server pro poskytování obsahu, můžeme zobrazit výstup v konzole prohlížeče.

Můžeme použít goexec pro poskytování souborů:

# Install go exec
go get -u github.com/shurcooL/goexec

# Start the server at 8080 port
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'

Pokud otevřeme localhost:8080 v prohlížeči a otevřete konzoli, uvidíme naši zprávu odeslanou z Go:

Přístup k webovým rozhraním API a zpřístupnění funkcí Go

Nyní, když víme, jak zkompilovat a spustit kód Go to Wasm a spustit jej na webu, začněme s vytvářením převodníku Image to Ascii v prohlížeči pomocí Web APIs . WebAssembly může spolupracovat s různými webovými rozhraními API, jako je DOM , CSSOM , WebGL , IndexedDB , Web Audio API atd. V tomto tutoriálu budeme používat DOM Rozhraní API v kódu Go s pomocí syscall/js balíček poskytovaný v Golangu.

package main

import (
  "syscall/js"
)

func main() {
  c := make(chan bool)
  //1. Adding an <h1> element in the HTML document
  document := js.Global().Get("document")
  p := document.Call("createElement", "h1")
  p.Set("innerHTML", "Hello from Golang!")
  document.Get("body").Call("appendChild", p)

  //2. Exposing go functions/values in javascript variables.
  js.Global().Set("goVar", "I am a variable set from Go")
  js.Global().Set("sayHello", js.FuncOf(sayHello))

  //3. This channel will prevent the go program to exit
  <-c
}

func sayHello(this js.Value, inputs []js.Value) interface{} {
  firstArg := inputs[0].String()
  return "Hi " + firstArg + " from Go!"
}

Výše uvedený kód ukazuje, jak můžeme plně interagovat s rozhraním API prohlížeče pomocí experimentálního balíčku Go syscall/js . Proberme výše uvedený příklad.

js.Global() metoda se používá k získání globálního objektu Javascript, který je window nebo global . Poté můžeme přistupovat ke globálním objektům nebo proměnným jako document , window a další javascriptová API. Pokud chceme získat nějakou vlastnost z javascriptového prvku, použijeme obj.Get("property") a nastavit vlastnost obj.Set("property", jsDataType) . Můžeme také volat funkci javascriptu pomocí Call metodu a předejte argumenty jako obj.Call("functionName", arg1,arg1) . Ve výše uvedeném příkladu jsme přistoupili k objektu dokumentu, vytvořili značku h1 a přidali ji do těla HTML pomocí DOM API.

Ve druhé části kódu jsme odhalili funkci Go a nastavili proměnnou, ke které lze přistupovat pomocí javascriptu. goVar je proměnná typu string a sayHello je typ funkce. Můžeme otevřít naši konzoli a pracovat s exponovanými proměnnými. Definice funkce pro sayHello lze vidět v poslední části kódu, která přebírá argument a vrací řetězec.

Na konci hlavního bloku čekáme na kanál, který nikdy nepřijme zprávu. To se provádí, abychom udrželi kód Go spuštěný, abychom měli přístup k exponované funkci. V jiných jazycích, jako je C++ a Rust Wasm s nimi zachází jako s knihovnou, tj. můžeme je přímo importovat a začít používat exponované funkce. V Go se však s importem zachází jako s aplikací, tj. můžete přistupovat k programu, když se spustí a běží, a poté je interakce ukončena, když je program ukončen. Pokud nepřidáme kanál na konec bloku, nebudeme schopni zavolat funkci, která byla definována v Go.

Výše uvedený kód vytváří následující výstup:

Import obrázku do knihovny Ascii do prohlížeče

Nyní, když víme, jak komunikovat tam a zpět mezi Go a prohlížečem, pojďme vytvořit aplikaci v reálném světě. Budeme importovat existující knihovnu image2Ascii, která převádí obrázek na znaky ASCII. Jedná se o aplikaci Go CLI, která vezme cestu obrázku a převede jej na znaky Ascii. Protože nemůžeme přistupovat k systému souborů v prohlížeči přímo, upravil jsem některé kódy v knihovně tak, aby místo cesty k souboru vzaly bajty obrázku. Zdroj do repozitáře se změnami:wasm-go-image-to-ascii. Musíme se starat pouze o odhalené API z knihovny, spíše než o to, jak algoritmus zatím funguje. Odhaluje následující:

func ImageFile2ASCIIString(imgByte []byte, option *Options) string
type Options struct {
  Colored         bool    `json:"colored"`
  FixedWidth      int     `json:"fixedWidth"`
  FixedHeight     int     `json:"fixedHeight"`
  Reversed        bool    `json:"reversed"`
}

Rozdělme celý proces na následující úkoly:

  1. Vytvořte posluchač událostí pro vstup souboru, který předá vybraný obrázek naší funkci Go.
  2. Napište funkci Go pro převod obrázku do ASCII a jeho vystavení prohlížeči.
  3. Vytváření a integrace do prohlížeče.

Vytvořte posluchač událostí pro vstup souboru

Budeme pokračovat za předpokladu funkce s názvem convert(image, options) bude vytvořen Go.

document.querySelector('#file').addEventListener(
  'change',
  function() {
    const reader = new FileReader()
    reader.onload = function() {
      // Converting the image to Unit8Array
      const arrayBuffer = this.result,
        array = new Uint8Array(arrayBuffer)
      // Call wasm exported function
      const txt = convert(
        array,
        JSON.stringify({
          fixedWidth: 100,
          colored: true,
          fixedHeight: 40,
        })
      )
      // To convert Ansi characters to html
      const ansi_up = new AnsiUp()
      const html = ansi_up.ansi_to_html(txt)
      // Showing the ascii image in the browser
      const cdiv = document.getElementById('console')
      cdiv.innerHTML = html
    }
    reader.readAsArrayBuffer(this.files[0])
  },
  false
)

Přidali jsme change posluchače na vstup s id file . Jakmile uživatel obrázek vybere, odešleme jej převedením na Unit8Array na convert funkce.

Funkce Go pro převod obrázku do ASCII


package main

import (
  "encoding/json"
  _ "image/jpeg"
  _ "image/png"
  "syscall/js"

  "github.com/subeshb1/wasm-go-image-to-ascii/convert"
)

func converter(this js.Value, inputs []js.Value) interface{} {
  imageArr := inputs[0]
  options := inputs[1].String()
  inBuf := make([]uint8, imageArr.Get("byteLength").Int())
  js.CopyBytesToGo(inBuf, imageArr)
  convertOptions := convert.Options{}
  err := json.Unmarshal([]byte(options), &convertOptions)
  if err != nil {
    convertOptions = convert.DefaultOptions
  }

  converter := convert.NewImageConverter()
  return converter.ImageFile2ASCIIString(inBuf, &convertOptions)
}

func main() {
  c := make(chan bool)
  js.Global().Set("convert", js.FuncOf(converter))
  <-c
}

Vystavujeme convert funkce, která bere bajty obrazu a možnosti. Používáme js.CopyBytesToGo převést javascript Uint8Array přejít na []byte . Po převedení obrázku funkce vrátí řetězec znaků Ascii/Ansi.

Sestavení a integrace do prohlížeče

Nakonec můžeme sestavit kód wasm a importovat jej do prohlížeče.

<html>
  <head>
    <meta charset="utf-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/drudru/ansi_up/ansi_up.js"></script>
    <script src="wasm_exec.js"></script>
  </head>
  <body>
    <!-- ASCII Image container  -->
    <pre
      id="console"
      style="background: black; color: white; overflow: scroll;"
    ></pre>
    <!-- Input to select file -->
    <input type="file" name="file" id="file" />
    <script>
      // Integrating WebAssembly
      const go = new Go()
      WebAssembly.instantiateStreaming(
        fetch('main.wasm'),
        go.importObject
      ).then(result => {
        go.run(result.instance)
      })
      // Adding image change listener
      document.querySelector('#file').addEventListener(
        'change',
        function() {
          const reader = new FileReader()
          reader.onload = function() {
            // Converting the image to Unit8Array
            const arrayBuffer = this.result,
              array = new Uint8Array(arrayBuffer)
            // Call wasm exported function
            const txt = convert(
              array,
              JSON.stringify({
                fixedWidth: 100,
                colored: true,
                fixedHeight: 40,
              })
            )
            // To convert Ansi characters to html
            const ansi_up = new AnsiUp()
            const html = ansi_up.ansi_to_html(txt)
            // Showing the ascii image in the browser
            const cdiv = document.getElementById('console')
            cdiv.innerHTML = html
          }
          reader.readAsArrayBuffer(this.files[0])
        },
        false
      )
    </script>
  </body>
</html>


Zde je odkaz na úložiště:https://github.com/subeshb1/wasm-go-image-to-ascii

Závěr

Podívali jsme se na základy Wasm a na to, jak jej používat k importu kódu Go do prohlížeče. Podívali jsme se také na to, jak můžeme importovat existující knihovnu a vytvořit aplikaci v reálném světě pro převod obrázků na znaky ASCII. Podělte se o své myšlenky a zpětnou vazbu v sekci komentářů a sdílejte svůj projekt také ve WebAssembly. Přestože je Wasm v rané fázi, vidíme, jak užitečné může být odstranění jazykové závislosti na prohlížeči a zlepšení výkonu spuštěním téměř nativní rychlosti.

  • Základní příklad uvedený v blogu:https://github.com/subeshb1/Webassembly/tree/master/go
  • Wasm image do ASCII:https://github.com/subeshb1/wasm-go-image-to-ascii
  • Ukázka:https://subeshbhandari.com/app/wasm/image-to-ascii

Další zdroje na WebAssembly:

  • Awesome Wasm:https://github.com/mbasso/awesome-wasm
  • WebAssembly z MDN:https://developer.mozilla.org/en-US/docs/WebAssembly