当前位置: 首页 > 科技观察

WebAssembly和Go:对未来拭目以待

时间:2023-03-13 15:03:11 科技观察

我反对学习JavaScript和前端开发已经不是什么秘密了。其实我学HTML早于学CSS,但是JavaScript是我从做web开发开始就一直在做的东西。当我看到现代网络的发展时,我不寒而栗。这样的生态,让久违的我如此迷茫。Node、webpack、yarn、npm、框架、UMD、AMD,天啊!我关注WebAssembly已经有一段时间了,希望它能让我在没有典型的JavaScript构建的情况下编写Web应用程序。当听说WebAssembly(wasm)最近支持Go语言时,我知道实验时机已经成熟,迫不及待地想尝试一下。在尝试之前我阅读了一些不错的文章,这篇文章将记录我的一些经验。为了在Go中编写wasm,您需要下载Go源代码并进行编译。从Go1.11开始,WebAssembly将被原生支持,但还没有发布。您可以按照此处的步骤编译Go。因为Go本身是用Go语言实现的,所以你需要在编译之前用一个可用的Go二进制版本来引导自己。最终,您的系统上将有两个不同的Go版本。注意:如果您后来忘记您的系统上安装了两个版本的Go,可能会让您感到困惑。你可以使用direnv来管理Go的版本,这样你就可以为不同的项目配置不同的Go。安装最新的Go后,就可以体验WebAssembly了。您需要一个HTML文件和一个JavaScript脚本来加载生成的wasm文件。这些都包含在Go安装路径的misc/wasm目录中。您可以将它们复制到项目目录并修改它们以加载您的wasm文件。我的第一个项目有点雄心勃勃,我打算用Go构建一些看起来像Web组件的东西,编译成WebAssembly。我没有完成整件事,因为我对每件事的完成情况感到分心。首先,我将HTML和JavaScript文件从GOROOT/misc/wasm复制到一个新目录并添加了一个main.go文件。根据我预先考虑的计划,我将HTML放入DOM的现有节点中,以便在HTML中声明。所以我用thing作为ID创建了一个HTMLsection标签。Pleasewait...我将其插入到HTML文件底部的脚本标签上方。接下来,我知道我想以编程方式替换这个节点,所以我在Go的wasm库中查找了与DOM交互的语法。向Go添加了一个syscall/js包,允许与DOM交互。我使用此Go代码获取对ID为thing的节点的HTML引用:el:=js.Global.Get("document").Call("getElementById","thing")现在我有一个空的DOM引用我可以用呈现的HTML填充的节点。所以下一步实际上是创建一些HTML并填充它。我以著名的TodoMVC应用程序为灵感。首先我创建了两个文件:todo.go和todolist.go。这些文件包含一些Go结构来表示待办事项和待办事项列表。typeTodostruct{TitlestringCompletedbool//Rootjs.Valuetree*vdom.Tree}typeTodoListstruct{Todos[]TodoComponent}typeComponentstruct{NamestringRootjs.ValueTree*vdom.TreeTemplatestring}我也有点傲慢,开始将东西提取到Component类型中,并认为我可以将它嵌入在我的自定义类型中为它们提供Web组件功能。我没有完成这个想法。..稍后你会明白为什么。这些自定义Go类型中的每一个都有一个Render()方法和模板:vartodolisttemplate=`

    {{range$i,$x:=$.Todos}}{{$x.Render}}{{end}}
`func(todoList*TodoList)Render()error{tmpl,err:=template.New("todolist").Parse(todoList.Template)iferr!=nil{returnerr}//执行模板withthegiventodoandwritetoabufferbuf:=bytes.NewBuffer([]byte{})iferr:=tmpl.Execute(buf,todoList);err!=nil{returnerr}//ParsetheresultinghtmlintoavirtualtreenewTree,err:=vdom.Parse(buf.Bytes())iferr!=nil{returnrr}iftodoList.Tree!=nil{//Calculatethediffbetweenthisrenderandthelastrender//patches,err:=vdom.Diff(todo.tree,newTree)}//iferr!=nil{//returnerr//}//有效地应用changestotheactualDOM//iferr:=patches.Patch(todo.Root);err!=nil{//returnerr//}}else{todoList.Tree=newTree}//记住虚拟DOM状态forthenextrendertodiffagainsttodoList.Tree=newTreetodoList.Root.Set("innerHTML",string(newTree.HTML()))returnnil}我的想法是使用我找到的vdom包来做这些渲染,这样渲染效率会更高。这是我遇到的第一个问题。GopherJS和Go/wasm的区别vdom包是为GopherJS编写的,而GopherJS是一个GotoJavascript转译器。为方便起见,GopherJS使用js.Object类型。Go的新wasm库syscall/js使用js.Value类型。它们在精神上相似,但在实现上却大不相同。这意味着我使用vdom渲染的想法是行不通的,除非我移植vdom使用的js.Object来使用js.Value。尽管vdom的tree.HTML()函数无需修改即可工作,因此我可以将HTML节点的内部HTML设置为vdom解析出的内容。Render()函数解析Go结构模板并将Go结构实例作为上下文传递。然后它使用vdom库创建一个解析的dom树,并在函数的第一行渲染树:todoList.Root.Set("innerHTML",string(newTree.HTML()))在这一点上,我已经在没有任何事件连接的情况下运行Go/was原型。但是确实可以渲染成dom显示在浏览器中。这是一个巨大的进步;我是如此兴奋。我创建了一个Makefile,这样我就不必一次又一次地输入冗长的构建命令:wasm2:GOROOT=~/gowasmGOARCH=wasmGOOS=js~/gowasm/bin/gobuild-oexample.wasmmarkdown.gowasm:GOROOT=~/gowasmGOARCH=wasmGOOS=js~/gowasm/bin/gobuild-oexample.wasm.build-server:gobuild-oserver-appserver/server.gorun:build-serverwasm./server-app基于WebAssembly的当前状态,这个makefile也指出了一个关键问题。现代浏览器忽略WASM文件,除非它们被赋予适当的MIME类型。本文有一个简单的HTTP文件服务器,可以为Web程序集文件设置正确的MIME类型。我将它复制到我的项目中并在应用程序中使用它。如果您的Web服务器确实针对.sasm文件进行了配置,那么您就不需要自定义服务器。提出挑战在这一点上,我意识到WebAssembly可以工作,也许更重要的是:许多GopherJS代码在WebAssembly中工作,只需很少修改或无需修改。我挑战自己(书呆子狙击)。我尝试的下一件事是找到一个vecty应用程序并编译它。由于vecty是专门为GopherJS编写的,并且使用js.Object类型而不是js.Vaule,因此很难失败。为了让vecty在wasm中编译,我fork了vecty,然后做了一些修改,一些处理,注释了很多代码。最终结果是放置在vecty/example目录下的markdown编辑器可以在WebAssembly中完美运行。这篇文章有点长,所以我会在这里让你看到源代码。总结:和GopherJS版本几乎一样,但是webassembly在main()退出的时候也会退出,所以为了防止退出并保持app运行,我在main()的末尾添加了一个空的channelreceive。EventsGo的syscall/js使用了非常不同的事件注册方式,我不得不修改vecty的事件注册代码以使用wasm的新回调注册,这花了我很多时间。但直到现在,这种方法都运行良好。结论在学习了这些事件课程之后,我决定WebAssembly是Web开发的未来。它可以使用任何语言作为Web开发的“前端语言”,然后编译成wasm。这对于像我这样不想再学习Javascript,却又可以使用自己喜欢的语言进行web开发的人来说,带来了很多好处。