GopherJS: transpřekladač z jazyka Go do JavaScriptu

9. 1. 2025
Doba čtení: 27 minut

Sdílet

Autor: Depositphotos
GopherJS kompiluje kód Go do čistého kódu JavaScriptu. Jeho hlavním účelem je nabídnout vývojářům možnost psát front-endový kód v jazyce Go, který bude stále fungovat ve všech prohlížečích.

Obsah

1. GopherJS: transpřekladač z jazyka Go do JavaScriptu

2. Svět transpřekladačů do JavaScriptu

3. Transpřekladač GopherJS

4. Instalace Go 1.19.13 před samotnou instalací GopherJS

5. Jak vlastně vypadá instalace Go 1.19.13?

6. Instalace transpřekladače GopherJS

7. Program typu „Hello, world!“, který se bude spouštět ve webovém prohlížeči

8. Pomocný HTTP server pro poskytování HTML stránek i JavaScriptových souborů

9. Spuštění programu typu „Hello, world!“ ve webovém prohlížeči

10. Proč je výsledný soubor hello_world.go tak obrovský?

11. Manipulace s DOMem webové stránky

12. Demonstrační příklad: změna obsahu vybraného elementu na webové stránce

13. Přidání nových značek do HTML stránky přes DOM

14. Kreslení do 2D canvasu z jazyka Go

15. Demonstrační příklad: kreslení do 2D canvasu

16. Komunikace mezi kódem psaným v jazyce Go a JavaScriptem

17. Zavolání funkce naprogramované v Go z JavaScriptu

18. Modifikace obsahu HTML stránky po stisku tlačítka

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. GopherJS: transpřekladač z jazyka Go do JavaScriptu

Pravděpodobně nejjednodušší a nejpřímější cestou podpory nového programovacího jazyka ve webových prohlížečích je integrace jeho interpretru přímo do prohlížeče, popř. použití pluginu s tímto interpretrem. Ovšem i přes snahy některých vývojářů a softwarových společností o začlenění dalších skriptovacích jazyků do webových prohlížečů (z historického pohledu se jednalo minimálně o Tcl, VBScript, Dart v Dartiu apod.) je patrné, že v současnosti je jediným široce akceptovaným skriptovacím jazykem na straně webového prohlížeče pouze JavaScript se všemi přednostmi a zápory, které tato monokultura přináší. To však v žádném případě neznamená, že by se ty části aplikace, které mají být spouštěny na straně klienta, musely psát pouze v JavaScriptu, jenž nemusí zdaleka všem vývojářům vyhovovat, ať již z objektivních (hoisting) či ze subjektivních příčin (například kvůli dosti zvláštně navrženému typovému systému, který ovšem umožnil realizovat například JSF*ck).

V relativně nedávné minulosti proto vzniklo a pořád ještě vzniká mnoho projektů, jejichž cílem je umožnit tvorbu webových aplikací pro prohlížeč v jiných programovacích jazycích. Zdrojové kódy je pak nutné nějakým způsobem zpracovat (transpřeložit, přeložit, …) takovým způsobem, aby je bylo možné ve webovém prohlížeči spustit stejně, jako kód psaný přímo v JavaScriptu. Možností je hned několik – lze použít plugin (velmi problematické a dnes značně nepopulární řešení, pravděpodobně již mrtvá cesta vývoje), transpřekladač do JavaScriptu či virtuální stroj, popř. interpret daného jazyka implementovaný opět v JavaScriptu..

Jednu z dnes velmi populárních technik umožňujících použití prakticky libovolného programovacího jazyka pro tvorbu aplikací běžících na straně webového prohlížeče, představuje použití takzvaných transcompilerů (source-to-source compiler) zajišťujících překlad programu napsaného ve zdrojovém programovacím jazyce do funkčně identického programu napsaného v JavaScriptu (někdy se setkáme i s označením transpiler). Transpřekladač se většinou spouští jen jednou na vývojářském počítači, samotní klienti již mají k dispozici JavaScriptový kód.

Poznámka: ve skutečnosti není technologie transpřekladačů žádným způsobem svázána právě s JavaScriptem, protože se používala (a používá) i pro další manipulace se zdrojovými kódy.

Existuje však i druhá možnost, kdy je samotný transpřekladač naprogramován v JavaScriptu a spouštěn přímo ve webovém prohlížeči klientů. Oba přístupy mají své přednosti, ale pochopitelně i nějaké zápory (například tvůrci uzavřených aplikací pravděpodobně budou upřednostňovat první možnost, protože výstupy transcompilerů jsou většinou dosti nečitelné; dokonce by mohla snaha o prozkoumání kódu spadat pod reverse engineering). Druhá možnost je relativně elegantní v tom ohledu, že se z pohledu programátora webové aplikace skutečně jedná o nový programovací jazyk, který je jakoby přímo zpracováván prohlížečem na stejné úrovni jako JavaScript. Příkladem může být kombinace JavaScriptu a jazyka WISP.

Zapomenout nesmíme ani na technologii WebAssembly, která je určitou alternativou k JavaScriptu. Jazyk Go překlad do WebAssembly podporuje (není přitom nutné instalovat žádné další nástroje ani knihovny) a dokonce většina demonstračních příkladů, které si dnes ukážeme, lze po malých úpravách přeložit do WebAssembly a teprve poté spustit v prohlížeči.

2. Svět transpřekladačů do JavaScriptu

Z praxe můžeme uvést například následující projekty založené na technologii transpřekladače. Některé z těchto projektů je možné použít přímo v prohlížeči, jiné provádí překlad do JavaScriptu na příkazovém řádku. A pochopitelně existují i kombinace obou způsobů (opět viz například projekt WISP podporující oba režimy):

:
# Jazyk Poznámka
1 CoffeeScript přidání syntaktického cukru do JavaScriptu
2 JSweet překlad programů z Javy do JavaScriptu, popř. do TypeScriptu
3 Transcrypt překlad Pythonu do JavaScriptu (tomuto nástroji se budeme věnovat v dalším článku)
4 ClojureScript překlad aplikací psaných v Clojure do JavaScriptu
5 Kaffeine rozšíření JavaScriptu o nové vlastnosti
6 RedScript jazyk inspirovaný Ruby
7 GorillaScript další rozšíření JavaScriptu
8 ghcjs transpřekladač pro fanoušky programovacího jazyka Haskell
9 wisp zjednodušená a dnes již nevyvíjená varianta ClojureScriptu
10 Babel překlad novějších variant JavaScript (ES2015) a TypeScriptu do zvolené (starší) verze JavaScriptu, stále populární, i přesto, že nové prohlížeče ES2015 podporují
11 GopherJS překladač programů naprogramovaných v jazyce Go do JavaScriptu – tímto projektem se zabýváme dnes
Poznámka: seznam všech (či alespoň většiny) známých transpřekladačů do JavaScriptu naleznete například na stránce https://github.com/jashke­nas/coffeescript/wiki/List-of-languages-that-compile-to-JS, i když je nutné varovat, že některé projekty (kromě výše zmíněných) jsou v dosti špatném stavu nebo již nejsou dále vyvíjeny. To se do jisté míry týká i druhého transpřekladače z jazyka Go do JavaScriptu – projektu go2js.

3. Transpřekladač GopherJS

V dnešním článku si ukážeme práci s transpřekladačem pojmenovaným GopherJS. Jak již název tohoto projektu naznačuje, jedná se o transpřekladač z programovacího jazyka Go do JavaScriptu. Přitom je však nutné dodat, že do výsledného kódu v JavaScriptu jsou přidány i použité knihovny (balíčky), pochopitelně v „transcriptované“ podobě a taktéž základní funkce z runtime, které umožňují například práci s řezy, mapami atd. Výsledný kód v JavaScriptu tedy může být poměrně velký (začíná se na přibližně 100kB, jak ostatně uvidíme dále), na druhou stranu však obsahuje všechny funkce potřebné pro spuštění výsledku, například ve webovém prohlížeči (samozřejmě je ale možné výsledek použít například i v node.js atd.). Navíc je pochopitelně možné výsledný kód minifikovat a ještě více tak zmenšit jeho velikost.

Velmi důležité je taktéž to, že je plně podporována standardní knihovna syscall/js, která umožňuje z Go kódu například manipulovat s DOMem HTML stránky, kreslit do canvasu, volat JavaScriptové funkce atd. Jazyk Go, resp. programy v něm psané, tak mohou být téměř dokonale integrovány do frontendu (tedy až na fázi překladu, která u původního JavaScriptu může odpadnout).

4. Instalace Go 1.19.13 před samotnou instalací GopherJS

GopherJS je sice nabízen stejným způsobem, jako jakýkoli jiný balíček pro Go, ovšem jeho instalace má jeden háček – je totiž nutné použít Go 1.19.13 a nikoli novější verze Go (nejnovější stabilní verze přitom nese číslo 1.23). Musíme tedy k již existující nainstalované verzi Go paralelně nainstalovat i verzi Go 1.19.13. Dále popsaný postup se může hodit i v jiných situacích, protože vám umožňuje mít na jednom počítači větší množství verzí Go.

Nejprve nainstalujeme balíček nazvaný go1.19.13@latest, který obsahuje samotný instalátor Go 1.19.13 (takže tento balíček je velmi malý – několik kilobajtů):

$ go install golang.org/dl/
 
go: downloading golang.org/dl v0.0.0-20241213165225-4b9e3f9eb6e1

Po instalaci si ověříme, že je sice možné spustit program go1.19.13, ovšem nejedná se o plnohodnotný překladač jazyka Go, což nám napoví vypsaná zpráva:

$ go1.19.13
 
go1.19.13: not downloaded. Run 'go1.19.13 download' to install to /home/ptisnovs/sdk/go1.19.13

Instalace celého Go 1.19.13 (překladače, dalších nástrojů, knihoven, zdrojových kódů) se realizuje následujícím příkazem:

$ go1.19.13 download
 
Downloaded   0.0% (     3119 / 149141790 bytes) ...
Downloaded   0.4% (   524288 / 149141790 bytes) ...
 
Downloaded  98.3% (146618736 / 149141790 bytes) ...
Downloaded 100.0% (149141790 / 149141790 bytes)
Unpacking /home/ptisnovs/sdk/go1.19.13/go1.19.13.linux-amd64.tar.gz ...
Success. You may now run 'go1.19.13'

Nyní je již možné použít příkaz go1.19.13 namísto pouhého go pro spuštění jakéhokoli standardního nástroje jazyka Go:

$ go1.19.13 version
 
go version go1.19.13 linux/amd64

5. Jak vlastně vypadá instalace Go 1.19.13?

Nástroje i knihovny starší verze jazyka Go, v našem případě konkrétně Go 1.19.13, jsou nainstalovány do adresáře sdk umístěného v domovském adresáři uživatele. Interní struktura odpovídá běžné instalaci Go (navíc zde zůstal i tarball, který je samozřejmě možné smazat):

$ tree ~/sdk -L 2
 
/home/ptisnovs/sdk
└── go1.19.13
    ├── api
    ├── bin
    ├── codereview.cfg
    ├── CONTRIBUTING.md
    ├── doc
    ├── go1.19.13.linux-amd64.tar.gz
    ├── lib
    ├── LICENSE
    ├── misc
    ├── PATENTS
    ├── pkg
    ├── README.md
    ├── SECURITY.md
    ├── src
    ├── test
    └── VERSION

Samotný příkaz go1.19.13 je uložen na standardním místě, tj. v ~/go/bin:

$ whereis go1.19.13
 
go1.19.13: /home/ptisnovs/go/bin/go1.19.13

6. Instalace transpřekladače GopherJS

Ve chvíli, kdy je nainstalován Go verze 1.19.13, si můžeme nainstalovat i transpřekladač GopherJS. Použijeme přitom příkaz go install. Je přitom vlastně jedno, jaká konkrétní verze Go bude pro spuštění tohoto příkazu použita:

$ go install github.com/gopherjs/gopherjs@v1.19.0-beta1

GopherJS závisí na několika dalších balíčcích, které jsou pochopitelně taktéž doinstalovány:

go: downloading github.com/gopherjs/gopherjs v1.19.0-beta1
go: downloading github.com/sirupsen/logrus v1.8.1
go: downloading golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
go: downloading github.com/spf13/cobra v1.2.1
go: downloading github.com/spf13/pflag v1.0.5
go: downloading golang.org/x/sync v0.3.0
go: downloading github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c
go: downloading golang.org/x/sys v0.10.0
go: downloading github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86
go: downloading golang.org/x/tools v0.11.0
go: downloading github.com/fsnotify/fsnotify v1.5.1
go: downloading github.com/visualfc/goembed v0.3.3
go: downloading github.com/evanw/esbuild v0.18.0

Dále nastavíme proměnnou prostředí nazvanou GOPHERJS_GOROOT tak, aby obsahovala cestu k adresáři, ve kterém je nainstalován Go verze 1.19.13. Tento adresář získáme příkazem go1.19.13 env GOROOT, takže zmíněnou proměnnou prostředí nastavíme například takto:

$ export GOPHERJS_GOROOT="$(go1.19.13 env GOROOT)"
Poznámka: tento příkaz je možné přidat do .bash_profile atd.

Nyní již můžeme transpřekladač GopherJS spustit:

$ gopherjs

Měly by se vypsat následující informace:

GopherJS is a tool for compiling Go source code to JavaScript.
 
Usage:
  gopherjs [command]
 
Available Commands:
  build       compile packages and dependencies
  clean       clean GopherJS build cache
  completion  generate the autocompletion script for the specified shell
  doc         display documentation for the requested, package, method or symbol
  help        Help about any command
  install     compile and install packages and dependencies
  run         compile and run Go program
  serve       compile on-the-fly and serve
  test        test packages
  version     print GopherJS compiler version
 
Flags:
      --alloc_profile string   Save GopherJS compiler allocation profile at the given path. If unset, profiling is disabled.
      --cpu_profile string     Save GopherJS compiler CPU profile at the given path. If unset, profiling is disabled.
  -h, --help                   help for gopherjs
      --log_level string       Compiler log level (debug, info, warn, error, fatal, panic). (default "error")
 
Additional help topics:
  gopherjs get        download and install packages and dependencies
 
Use "gopherjs [command] --help" for more information about a command.

7. Program typu „Hello, world!“, který se bude spouštět ve webovém prohlížeči

Nyní si ukažme, jakým způsobem se vlastně překládá projekt naprogramovaný v jazyce Go do JavaScriptu a jak se výsledný JavaScriptový zdrojový kód stane součástí webové aplikace. Samotný projekt je tak triviální, že si možná ani nezaslouží označení „projekt“. Jeho část napsaná v jazyce Go vypadá následovně:

// Technologie GopherJS
//
// Program typu "Hello, world!"
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
func main() {
        fmt.Println("Hello, world!")
        js.Global().Call("alert", "Hello, world!")
}

Běžný překlad „projektu“ by se provedl známým způsobem:

$ go build

Přičemž výsledkem by byl nativní spustitelný soubor pro použitou architekturu mikroprocesoru a operační systém.

My ovšem potřebujeme provést transpřeklad do JavaScriptu. Ten zajistíme následujícím příkazem:

$ gopherjs build hello_world.go

Výsledkem překladu tímto způsobem budou dva soubory:

-rw-r--r--. 1 ptisnovs ptisnovs 1297978 Jan  6 21:12 hello_world.js
-rw-r--r--. 1 ptisnovs ptisnovs   60380 Jan  6 21:12 hello_world.js.map

První z těchto souborů obsahuje celý projekt i se všemi potřebnými knihovnami (fmt), druhý soubor slouží například pro ladění – obsahuje mapování mezi řádky původního zdrojového kódu a kódu vzniklého transpřekladem.

Webová stránka, která po svém otevření v prohlížeči načte i soubor hello_world.js, je prozatím velmi jednoduchá:

<!doctype html>
<html>
    <head>
        <title>Hello, world! from GopherJS</title>
        <script src="hello_world.js" type="text/javascript"></script>
    </head>
    <body>
        Hello, world! from GopherJS
    </body>
</html>

8. Pomocný HTTP server pro poskytování HTML stránek i JavaScriptových souborů

Zbývá nám vyřešit ještě jeden problém – jak vlastně otevřít HTML stránku, v níž je umístěn odkaz na zdrojový kód s JavaScriptem?. Mohlo by se zdát, že se jedná o triviální úkol – prostě stránku otevřeme ve webovém prohlížeči přímo ze souboru a prohlížeč si po jejím zparsování ostatní potřebné soubory načte automaticky sám přímo z disku. Toto řešení je sice funkční v případě obrázků či dalšího multimediálního obsahu, ovšem už v případě JavaScriptu nemusí vždy fungovat (tato funkcionalita může být zakázána) a nebude funkční například ani v případě WebAssembly. Prohlížeč totiž v tomto případě může striktně vyžadovat, aby mu byl předán obsah s korektním MIME typem a taktéž může být načítání a spouštění skriptů přímo z disku zakázáno ve webovém prohlížeči.

Bude tedy praktičtější nasimulovat chování naší „aplikace“ (jediného HTML souboru a jediného JavaScriptového zdrojového kódu) tak, aby to odpovídalo jejímu reálnému použití – tj. spuštění na vzdáleném klientovi. To zajistíme spuštěním vlastního HTTP serveru, který je v případě použití programovacího jazyka Go implementován téměř triviálním způsobem:

package main
 
import (
        "fmt"
        "net/http"
)
 
func main() {
        const address = ":8080"
 
        const directory = http.Dir(".")
 
        fmt.Println("Starting HTTP server on address", address)
        err := http.ListenAndServe(address, http.FileServer(directory))
 
        if err != nil {
                fmt.Println("Failed to start server", err)
                return
        }
}

HTTP server spustíme ve vlastním terminálu, a to z adresáře, kde se nachází i naše aplikace „Hello, world!“:

$ go run http_server.go
 
Starting HTTP server on address :8080

Nyní si můžeme ověřit, zda jsou všechny soubory naší aplikace dostupné i z klienta:

$ curl localhost:8080/

Náš HTTP server zobrazí (resp. vrátí) seznam souborů:

<pre>
<a href="hello_world.go">hello_world.go</a>
<a href="hello_world.html">hello_world.html</a>
<a href="hello_world.js">hello_world.js</a>
<a href="hello_world.js.map">hello_world.js.map</a>
<a href="http_server.go">http_server.go</a>
</pre>

Pro jistotu se podíváme i na kód HTML stránky:

$ curl localhost:8080/hello_world.html
 
<!doctype html>
<html>
    <head>
        <title>Hello, world! from GopherJS</title>
        <script src="hello_world.js" type="text/javascript"></script>
    </head>
    <body>
        Hello, world! from GopherJS
    </body>
</html>

A ověříme si, že i JavaScriptový kód je dostupný a navíc i to, že se vrací korektní Content-Type (podtrženo):

$ curl -v localhost:8080/hello_world.js > /dev/null
 
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> GET /hello_world.js HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.6.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Length: 1297978
< Content-Type: text/javascript; charset=utf-8
< Last-Modified: Fri, 03 Jan 2025 20:17:02 GMT
< Date: Fri, 03 Jan 2025 20:18:19 GMT
<
{ [33280 bytes data]
100 1267k  100 1267k    0     0   125M      0 --:--:-- --:--:-- --:--:--  137M
* Connection #0 to host localhost left intact

9. Spuštění programu typu „Hello, world!“ ve webovém prohlížeči

Ve chvíli, kdy výše popsaný HTTP server běží, si můžeme ve webovém prohlížeči otevřít stránku z adresy localhost:8080. Měl by se zobrazit seznam dostupných souborů, který je generován přímo HTTP serverem:

Obrázek 1: Seznam dostupných souborů generovaný přímo HTTP serverem naprogramovaným v Go.

Vybereme si stránku hello_world.html. Ta by se měla zobrazit na ploše prohlížeče a navíc by se měl zobrazit dialog s hlášením „Hello, world“:

Obrázek 2: Stránka s dialogem s hlášením „Hello, world“.

Nakonec se podíváme do konzole webové stránky (Ctrl+Shift+K ve Firefoxu), v níž by taktéž mělo být viditelné hlášení „Hello, world“, které bylo vypsáno funkcí fmt.Println:

Obrázek 3: Zpráva „Hello, world“ v konzoli webové stránky.

10. Proč je výsledný soubor hello_world.go tak obrovský?

Připomeňme si, že výsledkem transpřekladu prvního demonstračního příkladu je soubor (v JavaScriptu), jehož velikost přesahuje jeden megabajt:

-rw-r--r--. 1 ptisnovs ptisnovs 1297978 Jan  6 21:12 hello_world.js
-rw-r--r--. 1 ptisnovs ptisnovs   60380 Jan  6 21:12 hello_world.js.map

Je to je poměrně hodně (a to i na poměry, které ve světě JavaScriptu vládnou), ovšem musíme si uvědomit, že ve výsledném souboru není obsažen pouze výsledek transpřekladu našeho zdrojového kódu, ale i všech potřebných knihoven. V tomto případě konkrétně standardní knihovny fmt, která je poměrně rozsáhlá.

Pokusme se tedy náš demonstrační příklad upravit takovým způsobem, aby se balíček fmt nepoužíval. To je snadné – zavoláme standardní funkci println, kterou není zapotřebí importovat:

// Technologie GopherJS
//
// Program typu "Hello, world!"
// Použití funkce println
 
package main
 
import (
        "syscall/js"
)
 
func main() {
        println("Hello, world!")
        js.Global().Call("alert", "Hello, world!")
}

Nyní bude výsledek transpřekladu již mnohem menší – jen cca 100 kB:

-rw-r--r--. 1 ptisnovs ptisnovs 108971 Jan  6 21:15 hello_world_2.js
-rw-r--r--. 1 ptisnovs ptisnovs   4018 Jan  6 21:15 hello_world_2.js.map

Pokud vynecháme i poslední importovaný balíček syscall/js:

// Technologie GopherJS
//
// Program typu "Hello, world!"
// Použití funkce println
 
package main
 
func main() {
        println("Hello, world!")
}

nepřesáhne výsledek transpřekladu hranici 90kB.

Poznámka: v oněch necelých 90kB je vlastně naprogramována funkcionalita runtime jazyka Go, dále podpora pro jeho typový systém, základní datové typy, strukturované datové typy, ale například i dekódování znaků atd.

11. Manipulace s DOMem webové stránky

V praxi, tj. při programování webového front-endu, se prakticky vždy setkáme s nutností manipulace s DOMem celé HTML stránky, popř. s DOMem souboru typu SVG (méně často). I tato možnost je pochopitelně na straně jazyka Go podporována, a to díky tomu, že balíček syscall/js zpřístupňuje programátorům objekt Global, který v JavaScriptu odpovídá objektu window (minimálně pokud se bavíme o HTML stránkách, nikoli o node.js):

// získání objektu typu "window" (z pohledu JavaScriptu)
window := js.Global()

Přes tento objekt můžeme přistoupit k dalšímu známému objektu document s obsahem HTML stránky:

// přečtení instance objektu "document"
document := window.Get("document")

Následně již můžeme volat metody objektu document, ovšem nepřímo přes:

proměnná = document.Call("jméno_JS_metody", parametry)

Například pro získání elementu  identifikátorem header zavoláme:

// získání reference na element s ID="header" umístěného
// na HTML stránce
element := document.Call("getElementById", "header")

Popř. můžeme měnit atributy s využitím:

document.Set("jméno_atributu", hodnota)

12. Demonstrační příklad: změna obsahu vybraného elementu na webové stránce

Podívejme se nyní, jakým způsobem můžeme změnit obsah značky s identifikátorem „header“. V plain JavaScriptu by se jednalo o tento kód:

element = document.getElementById("header");
element.innerHTML = "foobar";

V programovacím jazyce Go by se s využitím balíčku syscall/js dosáhlo podobné funkcionality tímto způsobem:

// Technologie GopherJS
//
// - manipulace s DOMem přímo z jazyka Go
// - změna atributu vybraného elementu
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
func main() {
        fmt.Println("started")
 
        // získání objektu typu "window" (z pohledu JavaScriptu)
        window := js.Global()
 
        // přečtení instance objektu "document"
        document := window.Get("document")
 
        // získání reference na element s ID="header" umístěného
        // na HTML stránce
        element := document.Call("getElementById", "header")
 
        // změna atributu elementu
        // (text uvnitř značky)
        element.Set("innerHTML", "foobar")
 
        fmt.Println("finished")
}

Ve zdrojovém kódu HTML stránky si povšimněte přítomnosti elementu (značky) h1 s ID nastaveným na „header“. Právě tento nadpis bude modifikován. Ovšem v tomto případě musíme zajistit, že se JavaScriptový kód vzniklý transpilací zavolá až poté, co se element na stránku umístí. Toho lze dosáhnout mnoha více či méně sofistikovanými způsoby. Ten nejjednodušší způsob je naznačen v HTML kódu:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <h1 id="header">nothing</h2>
        <script src="dom_manipulation.js" type="text/javascript"></script>
    </body>
</html>

Výsledná HTML stránka by měla (po modifikaci našim kódem) vypadat následovně:

Obrázek 4: Změna elementu na HTML stránce.

13. Přidání nových značek do HTML stránky přes DOM

Podobným způsobem můžeme do HTML stránky přidat další značky, což je ukázáno na dalším demonstračním příkladu, po jehož inicializaci by se do stránky měla přidat tabulka s hodnotami faktoriálů čísel od nuly do deseti. Povšimněte si, že nyní voláme metodu document.createElement() a taktéž document.body.appendChild():

// Technologie GopherJS
//
// - manipulace s DOMem přímo z jazyka Go
// - přidání nových elementů do HTML stránky
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// rekurzivní výpočet faktoriálu
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func main() {
        fmt.Println("started")
 
        // získání objektu typu "window" (z pohledu JavaScriptu)
        window := js.Global()
 
        // přečtení instance objektu "document"
        document := window.Get("document")
 
        // získání reference na element s ID="header" umístěného
        // na HTML stránce
        element := document.Call("getElementById", "header")
 
        // změna atributu elementu
        // (text uvnitř značky)
        element.Set("innerHTML", "Factorial table")
 
        // konstrukce tabulky faktoriálů
        for n := int64(0); n <= 10; n++ {
                // výpočet faktoriálu
                f := Factorial(n)
                message := fmt.Sprintf("%2d! = %d", n, f)
 
                // vytvoření nového elementu
                pre := document.Call("createElement", "pre")
                pre.Set("innerHTML", message)
 
                // přidání elementu do HTML stránky
                document.Get("body").Call("appendChild", pre)
        }
 
        fmt.Println("finished")
}

Příslušná HTML stránka, do které se bude přidávat nový element:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <h1 id="header">nothing</h2>
        <script src="dom_add_element.js" type="text/javascript"></script>
    </body>
</html>
Poznámka: opět si povšimněte, že JavaScriptový kód importujeme až po přidání příslušného elementu na HTML stránku.

Ukázka výsledku zobrazeného ve webovém prohlížeči:

Obrázek 5: Tabulka s vypočtenými hodnotami faktoriálu vypsaná do DOMu HTML stránky.

14. Kreslení do 2D canvasu z jazyka Go

Poměrně často se aplikace překládané do JavaScriptu používají pro kreslení 2D či 3D grafiky, typicky s přímým či nepřímým využitím WebGL. Tomuto zajisté zajímavému tématu se však dnes věnovat nebudeme. Namísto toho si ukážeme, jak může aplikace naprogramovaná v jazyce Go vytvořit 2D canvas (HTML 5) a kreslit do něj. Nejdříve si zpřístupníme objekt document, což již známe:

window := js.Global()
document := window.Get("document")

Následně do dokumentu (HTML stránky) vložíme nový canvas se zadanou velikostí:

canvas := document.Call("createElement", "canvas")
canvas.Set("height", CanvasWidth)
canvas.Set("width", CanvasHeight)
document.Get("body").Call("appendChild", canvas)

Získáme kontext pro kreslení:

context2d := canvas.Call("getContext", "2d")

A následně například vybarvíme celou plochu canvasu světle šedou barvou a navíc nakreslíme žlutý obdélník:

// obdélník
context2d.Set("fillStyle", "#c0c0c0")
context2d.Call("fillRect", 0, 0, CanvasWidth, CanvasHeight)
 
// obdélník zobrazený uvnitř prvního obdélníku
context2d.Set("fillStyle", "yellow")
context2d.Call("fillRect", 10, 10, CanvasWidth-20, CanvasHeight-20)

15. Demonstrační příklad: kreslení do 2D canvasu

Celý demonstrační příklad, který postupně vytvoří 2D canvas a poté do něj vykreslí dvojici obdélníků, může být v jazyce Go naprogramován následujícím způsobem:

// Technologie GopherJS
package main
 
import (
        "fmt"
        "syscall/js"
)
 
func main() {
        // rozměry canvasu
        const CanvasWidth = 256
        const CanvasHeight = 256
 
        fmt.Println("started")
 
        // získání objektu typu "window" (z pohledu JavaScriptu)
        window := js.Global()
        fmt.Println("window", window)
 
        // přečtení instance objektu "document"
        document := window.Get("document")
        fmt.Println("document", document)
 
        // změna nadpisu
        element := document.Call("getElementById", "header")
        fmt.Println("element", element)
        element.Set("innerHTML", "2D canvas")
 
        // vytvoření elementu typu "canvas"
        canvas := document.Call("createElement", "canvas")
        canvas.Set("height", CanvasWidth)
        canvas.Set("width", CanvasHeight)
 
        // vložení canvasu na HTML stránku
        document.Get("body").Call("appendChild", canvas)
 
        // vykreslení grafických objektů na canvas
        context2d := canvas.Call("getContext", "2d")
 
        // obdélník
        context2d.Set("fillStyle", "#c0c0c0")
        context2d.Call("fillRect", 0, 0, CanvasWidth, CanvasHeight)
 
        // obdélník zobrazený uvnitř prvního obdélníku
        context2d.Set("fillStyle", "yellow")
        context2d.Call("fillRect", 10, 10, CanvasWidth-20, CanvasHeight-20)
 
        fmt.Println("finished")
}

HTML stránka, v níž se 2D canvas vytvoří:

<!doctype html>
<html>
    <head>
        <title>Canvas manipulation from Go</title>
    </head>
    <body>
        Hello, world! from GopherJS
        <h1 id="header">canvas should be displayed here...</h1>
        <script src="draw_into_canvas.js" type="text/javascript"></script>
    </body>
</html>

A konečně se podívejme na výsledek, tedy jak bude HTML stránka vypadat po spuštění skriptu vzniklého transpilací našeho Go kódu do JavaScriptu:

Obrázek 6: HTML stránka, v níž byl vytvořen canvas, do něhož se vykreslila dvojice obdélníků.

16. Komunikace mezi kódem psaným v jazyce Go a JavaScriptem

V závěrečné části dnešního článku si ukážeme dvojici nejsložitějších příkladů, v nichž bude ukázána komunikace mezi kódem psaným v jazyce Go na straně jedné a JavaScriptem na straně druhé. To je důležitá a vlastně i téměř nezbytná vlastnost, protože je například nutné reagovat na činnost uživatele na webové stránce (stisk tlačítka atd.) a volat příslušné callback funkce. V obou dále uvedených příkladech nejprve vytvoříme pomocnou funkci naprogramovanou v Go, která bude volatelná z JavaScriptu. Povšimněte si, že se této funkci předává řez libovolných JavaScriptových hodnot a funkce může taktéž vracet libovolnou hodnotu:

// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintHello(this js.Value, args []js.Value) any {
        // získání objektu typu "window" (z pohledu JavaScriptu)
        window := js.Global()
 
        // přečtení instance objektu "document"
        document := window.Get("document")
 
        // získání reference na element s ID="header" umístěného
        // na HTML stránce
        element := document.Call("getElementById", "header")
 
        // změna atributu elementu
        // (text uvnitř značky)
        element.Set("innerHTML", "Hello from Go")
 
        // je nutné vrátit nějakou hodnotu
        return nil
}

Tuto funkci je ovšem nutné zaregistrovat, aby ji viděl i JavaScriptový engine, a to pod jménem „printHello“:

// export funkce PrintHello tak, aby byla volatelná
// z JavaScriptu
js.Global().Set("printHello", js.FuncOf(PrintHello))
Poznámka: pozor na to, že dříve se používal nepatrně odlišný postup:
// export funkce PrintHello tak, aby byla volatelná
// z JavaScriptu
js.Global().Set("printHello", js.NewCallback(PrintHello))

To však není vše – dále totiž musíme zajistit, aby se hlavní funkce Go (main) automaticky neukončila, protože by JavaScriptová část nemohla přistupovat k objektu Go (byl by odstraněn GC). To se provede s využitím kanálu s nulovou kapacitou, z něhož se pokusíme přečíst hodnotu. Tato operace je blokující a tudíž nikdy neskončí:

func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintHello tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printHello", js.FuncOf(PrintHello))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Volání zaregistrované funkce printHello() z JavaScriptu bude vypadat následovně:

<script type="text/javascript">
    printHello();
</script>

17. Zavolání funkce naprogramované v Go z JavaScriptu

Podívejme se nyní na konkrétní implementaci postupu popsaného v předchozí kapitole. Výsledkem by přitom měla být tato stránka, jejíž statický obsah je sice odlišný od obsahu vykresleného, ovšem po inicializaci se spustí kód původně psaný v jazyce Go, který obsah stránky příslušným způsobem modifikuje:

Obrázek 7: Stránka, která byla modifikována kódem naprogramovaným v Go, který byl zavolán z JavaScriptu.

Úplný zdrojový kód tohoto demonstračního příkladu (přesněji řečeno jeho část psaná v jazyce Go) vypadá následovně:

// Technologie WebAssembly
//
// - rozhraní mezi jazyky Go a JavaScript
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintHello(this js.Value, args []js.Value) any {
        // získání objektu typu "window" (z pohledu JavaScriptu)
        window := js.Global()
 
        // přečtení instance objektu "document"
        document := window.Get("document")
 
        // získání reference na element s ID="header" umístěného
        // na HTML stránce
        element := document.Call("getElementById", "header")
 
        // změna atributu elementu
        // (text uvnitř značky)
        element.Set("innerHTML", "Hello from Go")
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintHello tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printHello", js.FuncOf(PrintHello))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

A takto vypadá zdrojový kód HTML stránky, v níž se zavolá funkce printHello (takto je funkce známá z pohledu jazyka JavaScript, v Go má či může mít odlišný název):

<!doctype html>
<html>
    <head>
        <title>Hello, world! from GopherJS</title>
    </head>
    <body>
        <h1 id="header">nothing</h2>
        Hello, world! from GopherJS
        <script src="js_interop_1.js" type="text/javascript"></script>
        <script type="text/javascript">
            printHello();
        </script>
    </body>
</html>

18. Modifikace obsahu HTML stránky po stisku tlačítka

Díky tomu, že je možné z JavaScriptu volat funkci naprogramovanou v jazyce Go (a taktéž patřičně zaregistrovanou), lze snadno realizovat například reakci na stisk tlačítka umístěného na HTML stránce:

<button type="button" onclick="printHello()">Say "Hello"</button>

Stránka s takovým tlačítkem například může po renderingu ve webovém prohlížeči vypadat následovně:

Obrázek 8: Původní obsah HTML stránky.

Po stisku tlačítka uživatelem se obsah HTML stránky pozmění, a to takto:

Obrázek 9: Modifikovaný obsah HTML stránky po stisku tlačítka.

Nejprve si opět ukažme implementaci funkce nazvané PrintHello v jazyce Go. Tato funkce je následně zaregistrována pod jménem printHello, takže bude dostupná z JavaScriptu. A nezapomeneme ani na „nekonečné čekání“ při čtení z kanálu, do kterého nikdo nezapisuje:

bitcoin školení listopad 24

// Technologie WebAssembly
//
// - rozhraní mezi jazyky Go a JavaScript
 
package main
 
import (
        "fmt"
        "syscall/js"
)
 
// funkce, která se bude volat z HTML stránky, jakoby
// se jednalo o JavaScriptovou funkci
func PrintHello(this js.Value, args []js.Value) any {
        // získání objektu typu "window" (z pohledu JavaScriptu)
        window := js.Global()
 
        // přečtení instance objektu "document"
        document := window.Get("document")
 
        // získání reference na element s ID="header" umístěného
        // na HTML stránce
        element := document.Call("getElementById", "header")
 
        // změna atributu elementu
        // (text uvnitř značky)
        element.Set("innerHTML", "Hello from Go")
 
        // je nutné vrátit nějakou hodnotu
        return nil
}
 
func main() {
        fmt.Println("started")
 
        c := make(chan bool)
 
        // export funkce PrintHello tak, aby byla volatelná
        // z JavaScriptu
        js.Global().Set("printHello", js.FuncOf(PrintHello))
 
        // realizace nekonečného čekání
        <-c
 
        fmt.Println("finished")
}

Posledním zdrojovým kódem, který si v dnešním článku ukážeme, je kód HTML stránky, v němž nalezneme jak značku s ID=header, tak i tlačítko, po jehož stisku se zavolá (JavaScriptová) funkce nazvaná printHello:

<!doctype html>
<html>
    <head>
        <title>Hello, world! from GopherJS</title>
    </head>
    <body>
        <h1 id="header">nothing</h2>
        <script src="js_interop_1.js" type="text/javascript"></script>
        <button type="button" onclick="printHello()">Say "Hello"</button>
    </body>
</html>

19. Repositář s demonstračními příklady

Demonstrační příklady napsané v jazyce Go, které jsou určené pro transpřeklad do JavaScriptu s využitím nástroje GopherJS, byly uloženy do Git repositáře, jenž je dostupný na adrese https://github.com/RedHatOf­ficial/GoCourse. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

# Příklad Stručný popis Adresa
1 hello_world.go zdrojový kód prvního demonstračního příkladu: výpis zprávy na konzoli webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/hello_world.go
1 hello_world.html HTML stránka s kódem pro načtení prvního demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/hello_world.html
       
2 dom_manipulation.go zdrojový kód druhého demonstračního příkladu: manipulace s DOMem webové stránky https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_manipulation.go
2 dom_manipulation.html HTML stránka s kódem pro načtení druhého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_manipulation.html
       
3 dom_add_element.go zdrojový kód třetího demonstračního příkladu: přidání elementů do DOMu webové stránky https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_add_element.go
3 dom_add_element.html HTML stránka s kódem pro načtení třetího demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/dom_add_element.html
       
4 draw_into_canvas.go zdrojový kód čtvrtého demonstračního příkladu: kreslení do canvasu https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/draw_into_canvas.go
4 draw_into_canvas.html HTML stránka s kódem pro načtení čtvrtého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/draw_into_canvas.html
       
5 js_interop1.go zdrojový kód pátého demonstračního příkladu: komunikace s JavaScriptem https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop1.go
5 js_interop1.html HTML stránka s kódem pro načtení pátého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop1.html
       
6 js_interop2.go zdrojový kód šestého demonstračního příkladu: komunikace s JavaScriptem https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop2.go
6 js_interop2.html HTML stránka s kódem pro načtení šestého demonstračního příkladu do webového prohlížeče https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/js_interop2.html
       
7 http_server.go implementace HTTP serveru, který dokáže webovému prohlížeči předávat obsah požadovaných souborů https://github.com/RedHatOf­ficial/GoCourse/blob/master/les­son12/http_server.go

20. Odkazy na Internetu

  1. go2js
    https://github.com/tredoe/go2js
  2. GitHub repositář projektu GopherJS
    https://github.com/gopherjs/gopherjs
  3. How to use GopherJS to turn Go code into a JavaScript library
    https://medium.com/@kentquirk/how-to-use-gopherjs-to-turn-go-code-into-a-javascript-library-1e947703db7a
  4. Source to source compiler
    https://en.wikipedia.org/wiki/Source-to-source_compiler
  5. Binary recompiler
    https://en.wikipedia.org/wi­ki/Binary_recompiler
  6. py2many na GitHubu
    https://github.com/py2many/py2many
  7. py2many na PyPi
    https://pypi.org/project/py2many/
  8. Awesome Transpilers
    https://github.com/milahu/awesome-transpilers
  9. WebAssembly
    https://webassembly.org/
  10. WebAssembly na Wiki Golangu
    https://github.com/golang/go/wi­ki/WebAssembly
  11. The future of WebAssembly – A look at upcoming features and proposals
    https://blog.scottlogic.com/2018/07/20/wasm-future.html
  12. Writing WebAssembly By Hand
    https://blog.scottlogic.com/2018/04/26/we­bassembly-by-hand.html
  13. WebAssembly Specification
    https://webassembly.github­.io/spec/core/index.html
  14. Index of Instructions
    https://webassembly.github­.io/spec/core/appendix/in­dex-instructions.html
  15. The WebAssembly Binary Toolkit
    https://github.com/WebAssembly/wabt
  16. Will WebAssembly replace JavaScript? Or Will WASM Make JavaScript More Valuable in Future?
    https://dev.to/vaibhavshah/will-webassembly-replace-javascript-or-will-wasm-make-javascript-more-valuable-in-future-5c6e
  17. Roadmap (pro WebAssemly)
    https://webassembly.org/roadmap/
  18. Transcrypt
    https://transcrypt.org/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.