題。windows10安裝Git報(bào)錯(cuò)unable to set system config "http.sslcainfo":="/ssl/certs/cabndle.crt":exit code128。
是由于Win10系統(tǒng)沒(méi)有禁用驅(qū)動(dòng)程序強(qiáng)制簽名。
禁用方式:win+r 打開運(yùn)行窗口,輸入
gpedit.msc
然后點(diǎn)擊【用戶設(shè)置】->【管理模板】->【系統(tǒng)】->【驅(qū)動(dòng)程序安裝】,在右側(cè)窗口點(diǎn)擊【設(shè)備驅(qū)動(dòng)程序的代碼簽名】->【策略設(shè)置】
然后按照下圖修改
以下文章來(lái)源于GoUpUp ,作者dj
go-app是一個(gè)使用 Go + WebAssembly 技術(shù)編寫漸進(jìn)式 Web 應(yīng)用的庫(kù)。WebAssembly 是一種可以運(yùn)行在現(xiàn)代瀏覽器中的新式代碼。近兩年來(lái),WebAssembly 技術(shù)取得了較大的發(fā)展。我們現(xiàn)在已經(jīng)可以使用 C/C++/Rust/Go 等高級(jí)語(yǔ)言編寫 WebAssembly 代碼。本來(lái)就來(lái)介紹go-app這個(gè)可以方便地使用 Go 語(yǔ)言來(lái)編寫 WebAssembly 代碼的庫(kù)。
go-app對(duì) Go 語(yǔ)言版本有較高的要求(Go 1.14+),而且必須使用Go module。先創(chuàng)建一個(gè)目錄并初始化Go Module(Win10 + Git Bash):
$ mkdir go-app && cd go-app
$ go mod init
然后下載安裝go-app包:
$ go get -u -v github.com/maxence-charriere/go-app/v6
至于Go module的詳細(xì)使用,去看煎魚大佬的Go Modules 終極入門。
首先,我們要編寫 WebAssembly 程序:
package main
import "github.com/maxence-charriere/go-app/v6/pkg/app"
type Greeting struct {
app.Compo
name string
}
func (g *Greeting) Render() app.UI {
return app.Div().Body(
app.Main().Body(
app.H1().Body(
app.Text("Hello, "),
app.If(g.name != "",
app.Text(g.name),
).Else(
app.Text("World"),
),
),
),
app.Input().
Value(g.name).
Placeholder("What is your name?").
AutoFocus(true).
OnChange(g.OnInputChange),
)
}
func (g *Greeting) OnInputChange(src app.Value, e app.Event) {
g.name = src.Get("value").String()
g.Update()
}
func main() {
app.Route("/", &Greeting{})
app.Run()
}
在go-app中使用組件來(lái)劃分功能模塊,每個(gè)組件結(jié)構(gòu)中必須內(nèi)嵌app.Compo。組件要實(shí)現(xiàn)Render()方法,在需要顯示該組件時(shí)會(huì)調(diào)用此方法返回顯示的頁(yè)面。go-app使用聲明式語(yǔ)法,完全使用 Go 就可以編寫 HTML 頁(yè)面,上面繪制 HTML 的部分比較好理解。上面代碼中還實(shí)現(xiàn)了一個(gè)輸入框的功能,并為它添加了一個(gè)監(jiān)聽(tīng)器。每當(dāng)輸入框內(nèi)容有修改,OnInputChange方法就會(huì)調(diào)用,g.Update()會(huì)使該組件重新渲染顯示。
最后將該組件掛載到路徑/上。
編寫 WebAssembly 程序之后,需要使用交叉編譯的方式將它編譯為.wasm文件:
$ GOARCH=wasm GOOS=js go build -o app.wasm
如果編譯出現(xiàn)錯(cuò)誤,使用go version命令檢查 Go 是否是 1.14 或更新的版本。
接下來(lái),我們需要編寫一個(gè) Go Web 程序使用這個(gè)app.wasm:
package main
import (
"log"
"net/http"
"github.com/maxence-charriere/go-app/v6/pkg/app"
)
func main() {
h := &app.Handler{
Title: "Go-App",
Author: "dj",
}
if err := http.ListenAndServe(":8080", h); err != nil {
log.Fatal(err)
}
}
go-app提供了一個(gè)app.Handler結(jié)構(gòu),它會(huì)自動(dòng)查找同目錄下的app.wasm(這也是為什么將目標(biāo)文件設(shè)置為app.wasm的原因)。然后我們將前面編譯生成的app.wasm放到同一目錄下,執(zhí)行該程序:
$ go run main.go
默認(rèn)顯示"Hello World":
在輸入框中輸入內(nèi)容之后,顯示會(huì)隨之變化:
可以看到,go-app為我們?cè)O(shè)置了一些基本的樣式,網(wǎng)頁(yè)圖標(biāo)等。
GitHub 上這張圖很好地說(shuō)明了 HTTP 請(qǐng)求的執(zhí)行流程:
用戶請(qǐng)求先到app.Handler層,它會(huì)去app.wasm中執(zhí)行相關(guān)的路由邏輯、去磁盤上查找靜態(tài)文件。響應(yīng)經(jīng)由app.Handler中轉(zhuǎn)返回給用戶。用戶就看到了app.wasm渲染的頁(yè)面。實(shí)際上,在本文中我們只需要編寫一個(gè) Go Web 程序,每次編寫新的 WebAssembly 之后,將新編譯生成的 app.wasm 文件拷貝到 Go Web 目錄下重新運(yùn)行程序即可。注意,如果頁(yè)面未能及時(shí)刷新,可能是緩存導(dǎo)致的,可嘗試清理瀏覽器緩存。
自定義一個(gè)組件很簡(jiǎn)單,只需要將app.Compo內(nèi)嵌到結(jié)構(gòu)中即可。實(shí)現(xiàn)Render()方法可定義組件的外觀,實(shí)際上app.Compo有一個(gè)默認(rèn)的外觀,我們可以這樣來(lái)查看:
func main() {
app.Route("/app", &app.Compo{})
app.Run()
}
編譯生成app.wasm之后,一開始的 Go Web 程序不需要修改,直接運(yùn)行,打開瀏覽器查看:
在快速開始中,我們還介紹了如何使用事件。使用聲明式語(yǔ)法app.Input().OnChange(handler)即可監(jiān)聽(tīng)內(nèi)容變化。事件處理函數(shù)必須為func (src app.Value, e app.Event)類型,app.Value是觸發(fā)對(duì)象,app.Event是事件的內(nèi)容。通過(guò)app.Value我們可以得到輸入框內(nèi)容、選擇框的選項(xiàng)等信息,通過(guò)app.Event可以得到事件的信息,是鼠標(biāo)事件、鍵盤事件還是其它事件:
type ShowSelect struct {
app.Compo
option string
}
func (s *ShowSelect) Render() app.UI {
return app.Div().Body(
app.Main().Body(
app.H1().Body(
app.If(s.option == "",
app.Text("Please select!"),
).Else(
app.Text("You've selected "+s.option),
),
),
),
app.Select().Body(
app.Option().Body(
app.Text("apple"),
),
app.Option().Body(
app.Text("orange"),
),
app.Option().Body(
app.Text("banana"),
),
).
OnChange(s.OnSelectChange),
)
}
func (s *ShowSelect) OnSelectChange(src app.Value, e app.Event) {
s.option = src.Get("value").String()
s.Update()
}
func main() {
app.Route("/", &ShowSelect{})
app.Run()
}
上面代碼顯示一個(gè)選擇框,當(dāng)選項(xiàng)改變時(shí)上面顯示的文字會(huì)做相應(yīng)的改變。初始時(shí):
選擇后:
組件可以嵌套使用,即在一個(gè)組件中使用另一個(gè)組件。渲染時(shí)將內(nèi)部的組件表現(xiàn)為外部組件的一部分:
type Greeting struct {
app.Compo
}
func (g *Greeting) Render() app.UI {
return app.P().Body(
app.Text("Hello, "),
&Name{name: "dj"},
)
}
type Name struct {
app.Compo
name string
}
func (n *Name) Render() app.UI {
return app.Text(n.name)
}
func main() {
app.Route("/", &Greeting{})
app.Run()
}
上面代碼在組件Greeting中內(nèi)嵌了一個(gè)Name組件,運(yùn)行顯示:
go-app提供了組件的 3 個(gè)生命周期的鉤子函數(shù):
例如:
type Foo struct {
app.Compo
}
func (*Foo) Render() app.UI {
return app.P().Body(
app.Text("Hello World"),
)
}
func (*Foo) OnMount() {
fmt.Println("component mounted")
}
func (*Foo) OnNav(u *url.URL) {
fmt.Println("component navigated:", u)
}
func (*Foo) OnDismount() {
fmt.Println("component dismounted")
}
func main() {
app.Route("/", &Foo{})
app.Run()
}
編譯運(yùn)行,在瀏覽器中打開頁(yè)面,打開瀏覽器控制臺(tái)觀察輸出:
component mounted
component navigated: http://localhost:8080/
在前面的例子中我們已經(jīng)看到了如何使用聲明式語(yǔ)法編寫 HTML 頁(yè)面。go-app為所有標(biāo)準(zhǔn)的 HTML 元素都提供了相關(guān)的類型。創(chuàng)建這些對(duì)象的方法名也比較好記,就是元素名的首字母大寫。如app.Div()創(chuàng)建一個(gè)div元素,app.P()創(chuàng)建一個(gè)p元素,app.H1()創(chuàng)建一個(gè)h1元素等等。在go-app中,這些結(jié)構(gòu)都是暴露出對(duì)應(yīng)的接口供開發(fā)者使用的,如div對(duì)應(yīng)HTMLDiv接口:
type HTMLDiv interface {
Body(nodes ...Node) HTMLDiv
Class(v string) HTMLDiv
ID(v string) HTMLDiv
Style(k, v string) HTMLDiv
OnClick(h EventHandler) HTMLDiv
OnKeyPress(h EventHandler) HTMLDiv
OnMouseOver(h EventHandler) HTMLDiv
}
可以看到每個(gè)方法都返回該HTMLDiv自身,所以支持鏈?zhǔn)秸{(diào)用。調(diào)用這些方法可以設(shè)置元素的各方面屬性:
和設(shè)置事件監(jiān)聽(tīng):
例如下面代碼:
app.Div().Body(
app.H1().Body(
app.Text("Title"),
),
app.P().ID("id").
Class("content").Body(
app.Text("something interesting"),
),
)
相當(dāng)于 HTML 代碼:
<div>
<h1>title</h1>
<p id="id" class="content">
something interesting
</p>
</div>
我們可以在app.Raw()中直接寫 HTML 代碼,app.Raw()會(huì)生成對(duì)應(yīng)的app.UI返回:
svg := app.Raw(`
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
`)
但是這種寫法是不安全的,因?yàn)闆](méi)有檢查 HTML 的結(jié)構(gòu)。
我們?cè)谧铋_始的例子中就已經(jīng)用到了條件語(yǔ)句,條件語(yǔ)句對(duì)應(yīng) 3 個(gè)方法:If()/ElseIf()/Else()。
If和ElseIf接收兩個(gè)參數(shù),第一個(gè)參數(shù)為bool值。如果為true,則顯示第二個(gè)參數(shù)(類型為app.UI),否則不顯示。
Else必須在If或ElseIf后使用,如果前面的條件都不滿足,則顯示傳入Else方法的app.UI:
type ScoreUI struct {
app.Compo
score int
}
func (c *ScoreUI) Render() app.UI {
return app.Div().Body(
app.If(c.score >= 90,
app.H1().
Style("color", "green").
Body(
app.Text("Good!"),
),
).ElseIf(c.score >= 60,
app.H1().
Style("color", "orange").
Body(
app.Text("Pass!"),
),
).Else(
app.H1().
Style("color", "red").
Body(
app.Text("fail!"),
),
),
app.Input().
Value(c.score).
Placeholder("Input your score?").
AutoFocus(true).
OnChange(c.OnInputChange),
)
}
func (c *ScoreUI) OnInputChange(src app.Value, e app.Event) {
score, _ := strconv.ParseUint(src.Get("value").String(), 10, 32)
c.score = int(score)
c.Update()
}
func main() {
app.Route("/", &ScoreUI{})
app.Run()
}
上面我們根據(jù)輸入的分?jǐn)?shù)顯示對(duì)應(yīng)的文字,90及以上顯示綠色的Good!,60-90之間顯示橙色的Pass!,小于60顯示紅色的Fail!。下面是運(yùn)行結(jié)果:
假設(shè)我們要編寫一個(gè) HTML 列表,當(dāng)前有一個(gè)字符串的切片。如果一個(gè)個(gè)寫就太繁瑣了,而且不夠靈活,且容易出錯(cuò)。這時(shí)就可以使用Range()方法了:
type RangeUI struct {
app.Compo
name string
}
func (*RangeUI) Render() app.UI {
langs := []string{"Go", "JavaScript", "Python", "C"}
return app.Ul().Body(
app.Range(langs).Slice(func(i int) app.UI {
return app.Li().Body(
app.Text(langs[i]),
)
}),
)
}
func main() {
app.Route("/", &RangeUI{})
app.Run()
}
Range()可以對(duì)切片或map中每一項(xiàng)生成一個(gè)app.UI,然后平鋪在某個(gè)元素的Body()方法中。
運(yùn)行結(jié)果:
在go-app中,我們可以很方便的自定義右鍵彈出的菜單,并且為菜單項(xiàng)編寫響應(yīng):
type ContextMenuUI struct {
app.Compo
name string
}
func (c *ContextMenuUI) Render() app.UI {
return app.Div().Body(
app.Text("Hello, World"),
).OnContextMenu(c.OnContextMenu)
}
func (*ContextMenuUI) OnContextMenu(src app.Value, event app.Event) {
event.PreventDefault()
app.NewContextMenu(
app.MenuItem().
Label("item 1").
OnClick(func(src app.Value, e app.Event) {
fmt.Println("item 1 clicked")
}),
app.MenuItem().Separator(),
app.MenuItem().
Label("item 2").
OnClick(func(src app.Value, e app.Event) {
fmt.Println("item 2 clicked")
}),
)
}
func main() {
app.Route("/", &ContextMenuUI{})
app.Run()
}
我們?cè)贠nContextMenu中調(diào)用了event.PreventDefault()阻止默認(rèn)菜單的彈出??催\(yùn)行結(jié)果:
點(diǎn)擊菜單項(xiàng),觀察控制臺(tái)輸出~
上面我們都是使用go-app內(nèi)置的app.Handler處理客戶端的請(qǐng)求。我們只設(shè)置了簡(jiǎn)單的兩個(gè)屬性Author和Title。app.Handler還有其它很多字段可以定制:
type Handler struct {
Author string
BackgroundColor string
CacheableResources []string
Description string
Env Environment
Icon Icon
Keywords []string
LoadingLabel string
Name string
RawHeaders []string
RootDir string
Scripts []string
ShortName string
Styles []string
ThemeColor string
Title string
UseMinimalDefaultStyles bool
Version string
}
CSS 和 JS 文件必須在app.Handler中聲明。下面是一個(gè)示例app.Handler:
h := &app.Handler{
Name: "Luck",
Author: "Maxence Charriere",
Description: "Lottery numbers generator.",
Icon: app.Icon{
Default: "/web/icon.png",
},
Keywords: []string{
"EuroMillions",
"MEGA Millions",
"Powerball",
},
ThemeColor: "#000000",
BackgroundColor: "#000000",
Styles: []string{
"/web/luck.css",
},
Version: "wIKiverSiON",
}
本文中 WebAssembly 代碼都在各自的目錄中。Go Web 演示代碼在 web 目錄中。先進(jìn)入某個(gè)目錄,使用下面的命令編譯:
$ GOARCH=wasm GOOS=js go build -o app.wasm
然后將生成的app.wasm拷貝到web目錄:
$ cp app.wasm ../web/
切換到 web 目錄,啟動(dòng)服務(wù)器:
$ cd ../web/
$ go run main.go
本文介紹如何使用go-app編寫基于 WebAssembly 的 Web 應(yīng)用程序。可能有人會(huì)覺(jué)得,go-app編寫 HTML 的方式有點(diǎn)繁瑣。但是我們可以寫一個(gè)轉(zhuǎn)換程序?qū)⑵胀ǖ?HTML 代碼轉(zhuǎn)為go-app代碼,感興趣可以自己實(shí)現(xiàn)一下。WebAssembly 技術(shù)非常值得關(guān)注一波~