TL; DR В этом блоге показано, как импортировать существующий код go в Wasm и запускать его в браузере. В этом блоге я покажу вам, как я создал инструмент для преобразования изображений в символы Ascii в браузере, который был написан на Go. Ссылка на репозиторий Github: wasm-go-image-to-ascii. Вот Демо: Изображение в Ascii

Что такое WebAssembly?

Прежде чем перейти к написанию кода, давайте сначала разберемся, что такое WebAssembly. WebAssembly или WASM - это язык, подобный ассемблеру, который может работать в браузере с почти нативной производительностью. Он не должен быть написан вручную, а должен рассматриваться как цель компиляции для таких языков, как C / C ++, Golang, Rust, .Net и т. Д. Это означает, что сначала мы пишем программу на языке, затем конвертируем ее в WASM, а затем запустить его в браузере. Это позволит программе работать со скоростью, близкой к родной, и даст возможность запускать программу, написанную на любом языке, в браузере. Вы можете создавать веб-приложения на знакомом вам языке. Это не значит, что он удалит javascript, но существует рука об руку с JavaScript. Список языков, поддерживающих компиляцию WASM, находится в awesome-wasm-langs, а дополнительная информация - на WebAssembly Webpage и WebAssembly Concepts.

Запуск иди в браузере

А теперь давайте поработаем руками с некоторыми базовыми WASM и Golang.

Написание кода Go

Напишем нашу первую программу Hello World.

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

Компиляция в WebAssembly

Скомпилируем в Wasm.

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

Это создаст main.wasm файл WebAssembly, который мы сможем импортировать и запустить в браузере.

Интеграция с javascript

После того, как мы напишем наш код Go и скомпилируем его в WASM, мы сможем начать интегрировать его в браузер.

Нам понадобится оболочка времени выполнения Go, написанная на javascript, для взаимодействия с Go through wasm. Код поставляется с Go 1.11+ и может быть скопирован с помощью следующей команды:

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

Теперь давайте интегрировать его в браузер.

<!-- html -->
<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 компилирует и создает экземпляр кода WebAssembly. После создания экземпляра кода мы запустим программу Go с go.run(result.instance). Для получения дополнительной информации посетите документы WebAssembly.instantiateStreaming и Go WebAssembly. Теперь, если мы запустим сервер для обслуживания контента, мы сможем просмотреть вывод в консоли браузера.

Тип MIME для .wasm файла должен быть application/wasm при обслуживании с сервера.

Мы можем использовать goexec для обслуживания файлов:

# sh
# Install go exec
go get -u github.com/shurcooL/goexec
# Start the server at 8080 port
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'

Если мы откроем localhost:8080 в браузере и откроем консоль, мы увидим наше сообщение, отправленное из Go:

Доступ к веб-API и отображение функций Go

Теперь, когда мы знаем, как скомпилировать и запустить код Go в Wasm и запустить его в Интернете, давайте приступим к созданию конвертера изображений в Ascii в браузере, открыв Web APIs. WebAssembly может взаимодействовать с различными веб-API, такими как DOM, CSSOM, WebGL, IndexedDB, Web Audio API и т. Д. В этом руководстве мы будем использовать DOM API в коде Go с помощью пакета syscall/js, предоставленного в Golang.

// go
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!"
}

Приведенный выше код показывает, как мы можем полностью взаимодействовать с API браузера, используя экспериментальный пакет Go syscall/js. Давайте обсудим приведенный выше пример.

Метод js.Global() используется для получения глобального объекта Javascript, который равен window или global. Затем мы можем получить доступ к глобальным объектам или переменным, таким как document, window и другим API-интерфейсам JavaScript. Если мы хотим получить какое-либо свойство из элемента javascript, мы будем использовать obj.Get("property") и установить свойство obj.Set("property", jsDataType). Мы также можем вызвать функцию javascript с помощью метода Call и передать аргументы как obj.Call("functionName", arg1,arg1). В приведенном выше примере мы получили доступ к объекту документа, создали тег h1 и добавили его в тело HTML с помощью DOM API.

Во второй части кода мы раскрыли функцию Go и установили переменную, к которой можно получить доступ с помощью javascript. goVar - это переменная строкового типа, а sayHello - тип функции. Мы можем открыть нашу консоль и взаимодействовать с открытыми переменными. Определение функции для sayHello можно увидеть в последнем разделе кода, который принимает аргумент и возвращает строку.

В конце основного блока ждем канал, который никогда не получит сообщение. Это сделано для того, чтобы код Go продолжал работать, чтобы мы могли получить доступ к открытой функции. На других языках, таких как C ++ и Rust, Wasm рассматривает их как библиотеку, т.е. мы можем напрямую импортировать их и начать использовать открытые функции. Однако в Go импорт рассматривается как приложение, то есть вы можете получить доступ к программе, когда она запущена и запущена, а затем взаимодействие завершается, когда программа выходит. Если мы не добавим канал в конец блока, мы не сможем вызвать функцию, определенную в Go.

Приведенный выше код дает следующий результат:

Импорт изображения в библиотеку Ascii в браузер

Теперь, когда мы знаем, как взаимодействовать между Go и браузером, давайте создадим реальное приложение. Мы будем импортировать существующую библиотеку image2Ascii, которая преобразует изображение в символы ASCII. Это приложение Go CLI, которое берет путь к изображению и преобразует его в символы Ascii. Поскольку мы не можем напрямую получить доступ к файловой системе в браузере, я изменил часть кода в библиотеке, чтобы он принимал байты изображения вместо пути к файлу. Источник репо с изменениями: wasm-go-image-to-ascii. Нам нужно беспокоиться только о доступном API из библиотеки, а не о том, как работает алгоритм. Он раскрывает следующее:

// go
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"`
}

Разобьем весь процесс на следующие задачи:

  1. Создайте прослушиватель событий для ввода файла, который передает выбранное изображение в нашу функцию Go.
  2. Напишите функцию Go для преобразования изображения в ASCII и отображения его в браузере.
  3. Сборка и интеграция в браузер.

Создайте прослушиватель событий для ввода файла

Мы продолжим, предполагая, что функция с именем convert(image, options) будет создана Go.

//js
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
)

Мы добавили change прослушиватель для ввода с идентификатором file. Как только изображение будет выбрано пользователем, мы отправим изображение, преобразовав его в Unit8Array для функции convert.

Функция Go для преобразования изображения в ASCII

//go
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
}

Мы предоставляем convert функцию, которая принимает байты и параметры изображения. Мы используем js.CopyBytesToGo для преобразования javascript Uint8Array в Go []byte. После преобразования изображения функция возвращает строку символов Ascii / Ansi.

Сборка и интеграция в браузер

Наконец, мы можем собрать код в wasm и импортировать его в браузер.

<!-- html -->
<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>

Вот ссылка на репозиторий: https://github.com/subeshb1/wasm-go-image-to-ascii

Заключение

Мы рассмотрели основы Wasm и способы его использования для импорта кода Go в браузер. Мы также рассмотрели, как можно импортировать существующую библиотеку и создать реальное приложение для преобразования изображений в символы ASCII. Делитесь своими мыслями и отзывами в разделе комментариев, а также поделитесь своим проектом в WebAssembly. Хотя Wasm находится на ранней стадии, мы видим, насколько полезным может быть устранение языковой зависимости от браузера и повышение производительности за счет работы с почти нативной скоростью.

Дополнительные ресурсы по WebAssembly: