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"`
}
Разобьем весь процесс на следующие задачи:
- Создайте прослушиватель событий для ввода файла, который передает выбранное изображение в нашу функцию Go.
- Напишите функцию Go для преобразования изображения в ASCII и отображения его в браузере.
- Сборка и интеграция в браузер.
Создайте прослушиватель событий для ввода файла
Мы продолжим, предполагая, что функция с именем 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 находится на ранней стадии, мы видим, насколько полезным может быть устранение языковой зависимости от браузера и повышение производительности за счет работы с почти нативной скоростью.
- Базовый пример, описанный в блоге: https://github.com/subeshb1/Webassembly/tree/master/go
- Изображение Wasm в ASCII: https://github.com/subeshb1/wasm-go-image-to-ascii
- Демо: https://subeshbhandari.com/app/wasm/image-to-ascii
Дополнительные ресурсы по WebAssembly:
- Великолепный Wasm: https://github.com/mbasso/awesome-wasm
- WebAssembly от MDN: https://developer.mozilla.org/en-US/docs/WebAssembly