使用WebAssembly和圍棋編寫前端的Web框架

目錄

 ●  介紹
 ●  初始點
 ●  功能註冊
 ●  組件
 ●  構建路由器
 ●  一個完整的例子
 ●  挑戰前進
 ●  結論

JavaScript前端框架毫無疑問有助於突破以前在瀏覽器環境中可能實現的界限。更復雜的應用程序已經建立在React,Angular和VueJS之類的基礎之上,僅舉幾例,並且有一個衆所周知的笑話,關於新的前端框架似乎每天都會出現。

然而,這種發展速度對全世界的開發者來說都是一個非常好的消息。通過每個新框架,我們可以發現處理狀態的更好方法,或者使用shadow DOM等方式有效地渲染。

然而,最新的趨勢似乎是在用JavaScript以外的語言編寫這些框架並將它們編譯成WebAssembly。我們開始看到由於Lin Clark之類的人對JavaScript和WebAssembly進行通信的方式有了重大改進,我們無疑會看到更多重大改進,因爲WebAssembly開始在我們的生活中變得更加突出。

介紹

所以,在本教程中,我認爲構建一個用Go編寫的非常簡單的前端框架的基礎是一個好主意,它編譯成WebAssembly。這至少包括以下功能:

 ●  功能註冊
 ●  組件
 ●  超簡單路由

我現在警告你,雖然這些將非常簡單,並且無法準備好生產。如果這篇文章有點受歡迎,那麼我希望能夠將它推向前進,並嘗試構建滿足半正式前端框架要求的東西。

Github: 這個項目的完整源代碼可以在這裏找到:elliotforbes / oak。如果您喜歡爲項目做出貢獻,請隨意,我很樂意接受任何拉動請求!

初始點

好的,讓我們深入選擇我們的編輯器並開始編碼!我們要做的第一件事就是創建一個非常簡單的東西 index.html ,作爲我們前端框架的入口點:


1<!doctype html>
2<!--
3Copyright 2018 The Go Authors. All rights reserved.
4Use of this source code is governed by a BSD-style
5license that can be found in the LICENSE file.
6-->
7<html>
8
9<head>
10 <meta charset="utf-8">
11 <title>Go wasm</title>
12 <script src="./static/wasm_exec.js"></script>
13 <script src="./static/entrypoint.js"></script>
14</head>
15<body>
16
17 <div class="container">
18 <h2>Oak WebAssembly Framework</h2>
19 </div>
20</body>
21
22</html>

您會注意到這些js 文件在頂部導入了2個 文件,這使我們可以執行完成的WebAssembly二進制文件。第一個是大約414行,所以,爲了保持本教程的可讀性,我建議你從這裏下載它:https://github.com/elliotforbes/oak/blob/master/examples/blog/static/ wasm_exec.js

第二個是我們的 entrypoint.js 檔案。這將獲取並運行 lib.wasm 我們即將構建的內容。


1// static/entrypoint.js
2const go = new Go();
3WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {
4 go.run(result.instance);
5});

最後,既然我們已經解決了這個問題,我們就可以開始深入瞭解一些Go代碼!創建一個新文件 main.go ,其中包含Oak Web Framework的入口點!


1// main.go
2package main
3
4func main() {
5 println("Oak Framework Initialized")
6}

這很簡單。我們創建了一個非常簡單的Go程序,只需Oak Framework Initialized 打開我們的Web應用程序即可打印出來 要驗證一切正常,我們需要使用以下命令編譯它:

1$ GOOS=js GOARCH=wasm go build -o lib.wasm main.go

這應該構建我們的Go代碼並輸出我們在 lib.wasm 文件中引用的 entrypoint.js 文件。

真棒,如果一切正常,那麼我們準備在瀏覽器中試用它!我們可以使用這樣一個非常簡單的文件服務器:


1// server.go
2package main
3
4import (
5 "flag"
6 "log"
7 "net/http"
8)
9
10var (
11 listen = flag.String("listen", ":8080", "listen address")
12 dir = flag.String("dir", ".", "directory to serve")
13)
14
15func main() {
16 flag.Parse()
17 log.Printf("listening on %q...", *listen)
18 log.Fatal(http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir))))
19}

然後,您可以通過鍵入滿足您的應用程序 go run server.go ,你應該能夠從訪問你的應用程序 http://localhost:8080

功能註冊

好吧,所以我們有一個相當基本的打印聲明工作,但在宏觀方案中,我不認爲它只是作爲Web框架的資格。

讓我們來看看如何在Go中構建函數並註冊這些函數,以便我們可以在我們的函數中調用它們 index.html我們將創建一個新的實用程序函數,它將同時包含 string 我們函數的名稱以及它將映射到的Go函數。

將以下內容添加到現有 main.go 文件中:


1// main.go
2import "syscall/js"
3
4// RegisterFunction
5func RegisterFunction(funcName string, myfunc func(i []js.Value)) {
6 js.Global().Set(funcName, js.NewCallback(myfunc))
7}

所以,這就是事情開始變得有用的地方。我們的框架現在允許我們註冊函數,以便框架的用戶可以開始創建自己的功能。

使用我們框架的其他項目可以開始註冊自己的函數,這些函數隨後可以在他們自己的前端應用程序中使用。

組件

所以,我想接下來我們需要考慮添加到我們的框架中的是組件的概念。基本上,我希望能夠components/ 在項目中定義一個 使用它的目錄,並且在該目錄中我希望能夠構建一個home.go具有我的主頁所需的所有代碼的 組件。

那麼,我們該怎麼做呢?

好吧,React傾向於提供具有render() 函數的類,這些 函數返回HTML / JSX /您希望爲所述組件呈現的任何代碼。讓我們竊取它並在我們自己的組件中使用它。

我本質上希望能夠在使用此框架的項目中執行此類操作:


1package components
2
3type HomeComponent struct{}
4
5var Home HomeComponent
6
7func (h HomeComponent) Render() string {
8 return "<h2>Home Component</h2>"
9}

因此,在我的 components 包中,我定義了 HomeComponent 一個Render() 返回HTML 的 方法。

爲了向我們的框架添加組件,我們將保持簡單,並且只需定義 interface 我們隨後定義的任何組件必須遵守的組件。components/comopnent.go 在我們的Oak框架中創建一個新文件 


1// components/component.go
2package component
3
4type Component interface {
5 Render() string
6}

如果我們想要爲各種組件添加新功能會發生什麼?好吧,這讓我們可以做到這一點。我們可以oak.RegisterFunction 在init組件功能中使用 調用 來註冊我們想要在組件中使用的任何函數!


1package components
2
3import (
4 "syscall/js"
5
6 "github.com/elliotforbes/oak"
7)
8
9type AboutComponent struct{}
10
11var About AboutComponent
12
13func init() {
14 oak.RegisterFunction("coolFunc", CoolFunc)
15}
16
17func CoolFunc(i []js.Value) {
18 println("does stuff")
19}
20
21func (a AboutComponent) Render() string {
22 return `<div>
23 <h2>About Component Actually Works</h2>
24 <button onClick="coolFunc();">Cool Func</button>
25 </div>`
26}

當我們將它與路由器結合起來時,我們應該能夠看到我們 HTML 被渲染到我們的頁面,我們應該能夠點擊那個調用的按鈕, coolFunc() 它將does stuff 在我們的瀏覽器控制檯中打印出來 

太棒了,讓我們看看我們現在如何構建一個簡單的路由器。

構建路由器

好的,我們已經components 在我們的Web框架中得到了概念 我們差不多完成了嗎?

不完全是,我們可能需要的下一件事是在不同組件之間導航的方法。大多數框架似乎都 <div> 具有特定的特性 id ,它們會綁定並呈現其中的所有組件,因此我們將在Oak中竊取相同的策略。

讓我們router/router.go 在我們的橡木框架中創建一個 文件,以便我們可以開始破解。

在這個中,我們想要將string 路徑映射 到組件,我們不會進行任何URL檢查,我們現在只需將所有內容保存在內存中以保持簡單:


1// router/router.go
2package router
3
4import (
5 "syscall/js"
6
7 "github.com/elliotforbes/oak/component"
8)
9
10type Router struct {
11 Routes map[string]component.Component
12}
13
14var router Router
15
16func init() {
17 router.Routes = make(map[string]component.Component)
18}

因此,在此範圍內,我們創建了一個新 Router 結構,其中包含 Routes 了我們在上一節中定義的組件的字符串映射。

路由不是我們框架中的強制性概念,我們希望用戶在他們希望初始化新路由器時進行選擇。因此,讓我們創建一個新函數,它將註冊一個 Link 函數,並將我們映射中的第一個路徑綁定到我們的 <div id="view"/> html標記:


1// router/router.go
2// ...
3func NewRouter() {
4 js.Global().Set("Link", js.NewCallback(Link))
5 js.Global().Get("document").Call("getElementById", "view").Set("innerHTML", "")
6}
7
8func RegisterRoute(path string, component component.Component) {
9 router.Routes[path] = component
10}
11
12func Link(i []js.Value) {
13 println("Link Hit")
14
15 comp := router.Routes[i[0].String()]
16 html := comp.Render()
17
18 js.Global().Get("document").Call("getElementById", "view").Set("innerHTML", html)
19}

您應該注意到,我們已經創建了一個 RegisterRoute 函數,允許我們將a註冊 path 到給定的組件。

我們的 Link 功能也非常酷,因爲它允許我們在項目中的各個組件之間導航。我們可以指定非常簡單的 <button>元素,以允許我們導航到已註冊的路徑,如下所示:

1<button onClick="Link('link')">Clicking this will render our mapped Link component</button>

很棒,所以我們現在有一個非常簡單的路由器,如果我們想在一個簡單的應用程序中使用它,我們可以這樣做:


1// my-project/main.go
2package main
3
4import (
5 "github.com/elliotforbes/oak"
6 "github.com/elliotforbes/oak/examples/blog/components"
7 "github.com/elliotforbes/oak/router"
8)
9
10func main() {
11 // Starts the Oak framework
12 oak.Start()
13
14 // Starts our Router
15 router.NewRouter()
16 router.RegisterRoute("home", components.Home)
17 router.RegisterRoute("about", components.About)
18
19 // keeps our app running
20 done := make(chan struct{}, 0)
21 <-done
22}

一個完整的例子

將所有這些放在一起,我們可以開始構建具有組件和路由功能的非常簡單的Web應用程序。如果你想看一些關於它是如何工作的例子,那麼看一下官方回購中的例子:elliotforbes / oak / examples

挑戰前進

這個框架中的代碼絕不是生產準備好的,但是我希望這篇文章能夠開始討論如何在Go中開始構建更多生產就緒的框架。

如果不出意外,它開始了識別仍需要做什麼的旅程,以使其成爲React / Angular / VueJS之類的可行替代方案,所有這些都是大規模加速開發人員生產力的現象框架。

我希望這篇文章能激勵你們中的一些人開始研究如何在這個非常簡單的起點上進行改進。


原文發佈時間爲:2018-11-9

本文來自雲棲社區合作伙伴“Golang語言社區”,瞭解相關信息可以關注“Golang語言社區”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章