Obsah
1. GopherJS: transpřekladač z jazyka Go do JavaScriptu
2. Svět transpřekladačů do JavaScriptu
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
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.
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 |
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)"
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.
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>
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))
// 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:
// 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/RedHatOfficial/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/js_interop1.html |
6 | js_interop2.go | zdrojový kód šestého demonstračního příkladu: komunikace s JavaScriptem | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/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/RedHatOfficial/GoCourse/blob/master/lesson12/http_server.go |
20. Odkazy na Internetu
- go2js
https://github.com/tredoe/go2js - GitHub repositář projektu GopherJS
https://github.com/gopherjs/gopherjs - 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 - Source to source compiler
https://en.wikipedia.org/wiki/Source-to-source_compiler - Binary recompiler
https://en.wikipedia.org/wiki/Binary_recompiler - py2many na GitHubu
https://github.com/py2many/py2many - py2many na PyPi
https://pypi.org/project/py2many/ - Awesome Transpilers
https://github.com/milahu/awesome-transpilers - WebAssembly
https://webassembly.org/ - WebAssembly na Wiki Golangu
https://github.com/golang/go/wiki/WebAssembly - The future of WebAssembly – A look at upcoming features and proposals
https://blog.scottlogic.com/2018/07/20/wasm-future.html - Writing WebAssembly By Hand
https://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html - WebAssembly Specification
https://webassembly.github.io/spec/core/index.html - Index of Instructions
https://webassembly.github.io/spec/core/appendix/index-instructions.html - The WebAssembly Binary Toolkit
https://github.com/WebAssembly/wabt - 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 - Roadmap (pro WebAssemly)
https://webassembly.org/roadmap/ - Transcrypt
https://transcrypt.org/