Ой, все чудесится и чудесится!
— Л. Кэррол, Алиса в Стране Чудес
- Вас задрало, что node_modules на простом сайте соревнуются по количеству используемого места с вашей коллекцией музыки?
- Вы перечитали инструкцию к Redux в шестидесятый раз и поняли две вещи: "До меня кажется доходит..." и "Думаю, мне стоит перечитать это ещё раз!"
- Вы в очередной раз узнали, что 1 + "1" == "11", а [] - {} == NaN?
- Билд скрипт в webpack занимает больше места чем ваша библиотека на javascript?
Встречаем, vugu. Молодая (сразу предупреждаю, не релизнутая) и очень интересная библиотека, которая позволяет вам использовать golang напрямую в html. Естественно, так как пока не существует браузеров со встроенной поддержкой golang, то реализовывать всё пришлось через WASM.
Vugu это очень молодая библиотека и упоминаний о ней на хабре я не нашёл, за исключением пары дайджестов.
Давайте посмотрим и потрогаем эту библиотеку изнутри. И так, что же такое vugu?
Для начала, давайте представим, что вы пишите html компонент, только скрипты имеют тип application/x-go вместо javascript:
<div>
<p vg-if='c.ShowText'>
Conditional text here.
</p>
</div>
<script type="application/x-go">
type Root struct { // component for "root"
ShowText bool `vugu:"data"`
}
</script>
Вы сохраняете вышеописанное безобразие в файл с расширением *.vugu и
go generate
go build
./file-name
В комплекте идёт утилита, для облегчения разработки, под названием vgrun, но это просто обёртка над стандартными командами. Для простоты иллюстраций я буду использовать именно эту библиотеку.
vgrun devserver.go
запустит простой сервер, и начнёт следить за файлами на предмет изменений. Если оные находятся, то программа автоматически перезапускает сервер и обновляет приложение. Просто и без наворотов.
Что же, пришло время проследить путь vugu файла до конечного пользователя.
- Файл парсится html парсером. Да, файл должен содержать полностью рабочий HTML.
- После этого файл ещё раз парсится vugu парсером. Тут файл разбирается на части и пересобирается в go. Сгенерированный код это полностью рабочий код на golang.
- Полученный файл компилируется в WASM и упаковывается в web assembly для запуска на клиенте.
- PROFIT!
Что? Надо больше? Ладно, так уж и быть. Давайте закапываться глубже и делать больше. Давайте для начала посмотрим на сгенерированный golang файл. Файл называется 0_components_vgen.go. В него и пойдём.
Код из <script type="application/x-go"> из vugu переносится в golang без каких либо вопросов и изменений. Приятно. В дополнение к этому, в файле создаётся функция Build, которая генерирует HTML интерфейс.
func (c *Root) Build(vgin *vugu.BuildIn) (vgout *vugu.BuildOut) {
vgout = &vugu.BuildOut{}
var vgiterkey interface{}
_ = vgiterkey
var vgn *vugu.VGNode
vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "div", Attr: []vugu.VGAttribute{vugu.VGAttribute{Namespace: "", Key: "class", Val: "demo"}}}
vgout.Out = append(vgout.Out, vgn) // root for output
{
vgparent := vgn
_ = vgparent
vgn = &vugu.VGNode{Type: vugu.VGNodeType(1), Data: "\n "}
vgparent.AppendChild(vgn)
vgn = &vugu.VGNode{Type: vugu.VGNodeType(3), Namespace: "", Data: "button", Attr: []vugu.VGAttribute(nil)}
vgparent.AppendChild(vgn)
vgn.DOMEventHandlerSpecList = append(vgn.DOMEventHandlerSpecList, vugu.DOMEventHandlerSpec{
EventType: "click",
Func: func(event vugu.DOMEvent) { c.HandleCat(event) },
// TODO: implement capture, etc. mostly need to decide syntax
})
Выглядит запутанно, как и любой сгенерированный код, но если присмотреться, то можно запросто увидеть что это просто наш HTML, созданный программно.
После всего, vugu собирает все компоненты вашего сайта в единый программный блок (а можно и не в единый) и отправляет всё это на клиент. Библиотека в состоянии отслеживать DOM и DOMEvents для того, чтобы передавать события от HTML компонентов в ваш код на golang.
Ну вот. Всё достаточно просто. На самом деле, всё очень просто. vugu построена на этом подходе. Vugu - это не фреймворк. Это библиотека, которую вы можете использовать в некоторых частях вашего проекта. Вы можете программно вызывать рендер определённых компонентов там, где вам это нужно. Вам не придётся зависеть от create-react-app или чего-то ещё. Всё очень легковесно.
Ну что же, хватит болтовни, давайте напишем простенькое приложение, чтобы показать, что можно и чего нельзя делать с помощью vugu.
Для начала сделаем:
go get -u github.com/vugu/vgrun
vgrun -install-tools
После этого можно создать проект из темплейта:
vgrun -new-from-example=simple .
Ну и запустить всё это дело на локальном девсервере:
vgrun devserver.go
Если в этот момент у вас вылетит пара ошибок о том, что у вас не достаёт каких-либо модулей, следуйте инструкциям и запустите go get. Последняя версия golang не признаёт зависимостей в go.mod и вам придётся загрузить их руками.
vscode имеет функцию подсветки синтаксиса для vugu. Приятный плюс. Устанавливаем эту подсветку и начинаем писать наш root.vugu.
Для простоты душевной напишем программу, которая будет показывать фотографию котиков. Интернет ведь создавался для котиков, так ведь?
В начале каждого vugu файла находится HTML разметка компонента.
<div class="demo">
<button @click="c.HandleCat(event)">Get a cat!</button>
<div vg-if='c.IsLoading'>Loading...</div>
<div vg-if='len(c.Cats) > 0'>
<div vg-for='c.Cats'>
<img :src='value.URL' alt="cat"></img>
</div>
</div>
</div>
Тут всё достаточно просто. И в принципе, понятно для любого человека, который работал с vue.js. Для тех, кто не работал с vue, разобраться не составит большого труда.
События определяются с помощью @. @click, например, это ваш обработчик события, который запустится по нажатию на кнопку. Самое приятное, сюда можно запихнуть функцию или напрямую писать golang код.
Вы можете показывать определённый контент используя аттрибут vg-if.
<div vg-if='c.IsLoading'>Loading...</div>
Соответственно Loading... будет показан только когда переменная IsLoading равняется true (Откуда взялся этот "с" я объясню попозжее). Сюда тоже можно запихивать любой golang код, как видно на следующей строке.
После этого мы будем использовать vg-for для того, чтобы сгенерировать вывод для каждого элемента в коллекции.
Ну и на закуску, если вы добавляете двоеточие в начале HTML атрибута, то значение этого атрибута будет взято из golang кода.
Дальше у нас начинаются чудеса и самая интересная часть программы.
<script type="application/x-go">
import (
"encoding/json"
"net/http"
"log"
)
type Root struct {
IsLoading bool `vugu:"data"`
Cats []Cat `vugu:"data"`
}
type Cat struct {
ID string `json:"id"`
URL string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
}
Здесь я определяю две структуры, Cat, это для поддержки котиков в API и Root, основной структуры в программе. Название этой структуры должно совпадать с названием файла, и она должна быть экспортирована (начинаться с заглавной буквы). Поля этой структуры должны быть отмечены тэгом `vugu:"data"`. Всё отмеченное этим тегом будет доступно в нашем HTML коде через переменную с.
Root это название нашего компонента. Root это специальный компонент в vugu. Маунт-поинт вашего приложения начинается с Root.
Количество vugu файлов не ограничено. Создавайте столько компонентов, сколько душе угодно. Если вам приспичило назвать что-то двумя словами, то файл должен называться: koshki-sobachki.vugu а основной тип в этом файле KoshkiSobachki.
Раутинг будет создан автоматически, основываясь на называниях компонентов. Соответственно вышеописанный компонент будет создан и смонтирован по адресу /KoshkiSobachki. Хотя, опять же, vugu не очень любит всю эту магию и синтаксический сахар. Весь раутинг можно переопределить вручную.
Ладно, давайте закончим писать наш простой сайт.
func (c *Root) HandleCat(event vugu.DOMEvent) {
ee := event.EventEnv()
go func() {
ee.Lock()
c.IsLoading = true
ee.UnlockRender()
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.thecatapi.com/v1/images/search?limit=3&size=full", nil)
req.Header.Set("x-api-key", "710c211b")
res, err := client.Do(req)
if err != nil {
log.Printf("Error fetching: %v", err)
return
}
defer res.Body.Close()
var newcat []Cat
err = json.NewDecoder(res.Body).Decode(&newcat)
if err != nil {
log.Printf("Can't unmarshal the json: %v", err)
return
}
ee.Lock()
defer ee.UnlockRender()
c.Cats = newcat
c.IsLoading = false
}()
}
Код достаточно прост. Идём на https://thecatapi.com, регистрируемся, получаем бесплатный API-Key и грузим его в код. (Не бойтесь, ключ в этом примере не валидный, так что вы не сможете угнать у меня мой любимый сервис генерации котиков). Две вещи, которые надо здесь упомянуть это:
- Код обработчика события напрямую запускает goroutine и не блокирует сам обработчик событий.
- В коде goroutine мы будем использовать EventEnvironment для того, чтобы синхронизировать доступ к данным. Перед тем, как вы обновляете поля структуры с вам необходимо вызвать Lock() а сразу после UnlockRender().
Проверяем:
Замечательно, всё работает.
Давайте погрузимся в некоторые детали vugu.
Главная деталь — ничего не происходит автоматически и без вашего участия. Такова позиция главного разработчика vugu. Всё что вы видите на экране происходит по вашему велению.
Посему, например, CSS, объявленный в vugu файлах будет просто вставлен в ваш HTML. Никаких примочек. Ничего не будет переименовано, и если вы напишите .header в двух разных модулях, у вас произойдёт коллизия стилей. Так что аккуратно.
Компонент, написанный единожды можно использовать внутри других компонентов (так же как и в React и Blazor). Компоненты могут находиться в четырёх состояниях:
- Init(ctx vugu.InitCtx) компонент создан, но ещё не успел повидать жизнь и выпить пивка.
- Compute(ctx vugu.ComputeCtx) компонент скоро увидит свет. Пересчитываем переменные, обновляем значения.
- Rendered(ctx vugu.RenderedCtx) по компоненту как следует прошлись и отрендерили по самые помидоры.
- Destroy(ctx vugu.DestroyCtx) компонент никому не сдался, и ему пора на свалку. Выгорание, что ещё сказать.
Приведу ещё примеров с сайта создателя vugu:
<!-- root.vugu -->
<div class="root">
<ul>
<main:MyLine FileName="example.txt" :LineNumber="rand.Int63n(100)" ></main:MyLine>
</ul>
</div>
<script type="application/x-go">
import "math/rand"
</script>
<!-- my-line.vugu -->
<li class="my-line">
<strong vg-content='c.FileName'></strong>:<span vg-content='c.LineNumber'></span>
</li>
<script type="application/x-go">
type MyLine struct {
FileName string vugu:"data"
LineNumber int vugu:"data"
}
</script>
В этом примере вы можете видеть, как просто создавать и использовать компоненты в vugu.
Ну и напоследок, wasm файл, который получается на выходе, весит 7 метров. Это на порядок лучше, чем то, что выдаёт Blazor, но мы можем пойти глубже.
Что если я скажу, что вы можете запустить ваш проект написанный на vugu в tinygo? Именно это я вам и говорю.
Идём на https://www.vugu.org/doc/tinygo и запускаем билд либо через докер, либо с помощью синей изоленты и кузькиной матери. На выходе мы получаем замечательный wasm файл, который весит 500килобайт. Ура! Всем по кошаку! Получайте, сколько хотите!
Ладно, хватит разглагольствовать, идём и читаем официальную документацию на https://www.vugu.org/doc.
После этого можно идти и читать намного более объёмную документацию на https://pkg.go.dev/github.com/vugu/vugu/.
Я связался с автором проекта и проверил, проект не запущен, хотя документация устарела. Я лично предложил свою помощь в обновлении документации и развитии проекта, так что vugu в массы. Но, несмотря на это, вот вам официальное заявление:
Проект, всё же, находится в режиме тестирования. Например, последняя версия tinigo сломала компиляцию wasm кода. Так что пользуйтесь на свой страх и риск. Но, пользуйтесь. Это весело.
Если у кого-то есть вопросы — создавайте issue, делайте PR или пишите мне в личку, я могу написать создателям в Slack.
Удачного всем погружения в vugu!
Источник статьи: https://habr.com/ru/post/567440/