使用 WebAssembly 和 Go 编写前端 Web 框架

JavaScript 前端框架无疑有助于突破以前在浏览器上下文中可能实现的界限。越来越复杂的应用程序已经出现在 React、Angular 和 VueJS 之类的基础之上,仅举几例,而且有一个众所周知的笑话是关于新的前端框架似乎每天都会出现。

然而,这种发展速度对于世界各地的开发者来说是一个非常好的消息。对于每一个新框架,我们都发现了更好的处理状态的方法,或者使用影子 DOM 之类的东西有效地渲染。

然而,最新的趋势似乎是朝着用户户 JavaScript 用另外的语言编写这些框架并将它们编译到 WebAssembly 中。多亏了Lin Clark 之类的人 ,我们开始看到 JavaScript 和 WebAssembly 通信方式的重大改进,而且随着 WebAssembly 开始在我们的生活中变得更加突出,我们无疑会看到更多重大的改进。

介绍

因此,在本教程中,我认为构建一个非常简单的前端框架的基础是一个好主意,该框架用 Go 编写,可编译成 WebAssembly。至少,这将包括以下功能:

  • 功能注册
  • 组件
  • 超级简单的路由

我现在警告你,尽管这些将非常简单,而且离生产准备还差得很远。如果这篇文章有点受欢迎,我希望能继续推进它,并尝试构建一些满足半体面前端框架要求的东西。

Github:这个项目的完整源代码可以在这里找到: elliotforbes/go-webassembly-framework。如果您愿意为该项目做出贡献,请随意,我很乐意收到任何拉取请求!

初始点

好吧,让我们深入了解我们选择的编辑器并开始编码!我们要做的第一件事是创建一个非常简单的index.html作为前端框架的入口点:

<!DOCTYPE html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>
  <head>
    <meta charset="utf-8" />
    <title>Go wasm</title>
    <script src="./static/wasm_exec.js"></script>
    <script src="./static/entrypoint.js"></script>
  </head>
  <body>
    <div class="container">
      <h2>Oak WebAssembly Framework</h2>
    </div>
  </body>
</html>

你会注意到它们js在顶部导入了2 各种文件,这些文件允许我们执行我们完成的 WebAssembly 二进制文件。其中第一行大约 414 行,为了保持本教程的可读性,我建议您从这里下载:https : //github.com/elliotforbes/go-webassembly-framework/blob/master/examples/博客/静态/wasm_exec.js

第二个是我们的entrypoint.js文件。这将获取并运行lib.wasm 我们将很快构建的。

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

最后,既然我们已经解决了这个问题,我们就可以开始研究一些 Go 代码了!创建一个名为的新文件main.go,该文件将包含我们 Oak Web 框架的入口点!

// main.go
package main

func main() {
    println("Oak Framework Initialized")
}

这很简单。我们创建了一个非常简单的 Go 程序,Oak Framework Initialized当我们打开我们的 web 应用程序时应该会打印出来。为了验证一切正常,我们需要使用以下命令编译它:

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

我们应该构建我们的 Go 代码并输出我们在lib.wasm文件中引用的entrypoint.js文件。

太棒了,如果一切正常,那么我们就可以在浏览器中试用了!我们可以像这样使用一个非常简单的文件服务器:

// server.go
package main

import (
    "flag"
    "log"
    "net/http"
)

var (
    listen = flag.String("listen", ":8080", "listen address")
    dir    = flag.String("dir", ".", "directory to serve")
)

func main() {
    flag.Parse()
    log.Printf("listening on %q...", *listen)
    log.Fatal(http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir))))
}

然后,您可以通过键入来服务您的应用程序,go run server.go并且您应该能够从http://localhost:8080.

功能注册

好的,所以我们已经有了一个相当基本的打印语句,但是从总体上看,我不认为它可以作为一个 Web 框架。

让我们看看如何在 Go 中构建函数并注册这些函数,以便我们可以在index.html. 我们将创建一个新的实用程序函数,它将接受string作为我们函数名称的 a 以及它将映射到的 Go 函数。

将以下内容添加到现有main.go文件中:

// main.go
import "syscall/js"

// RegisterFunction
func RegisterFunction(funcName string, myfunc func(i []js.Value)) {
    js.Global().Set(funcName, js.NewCallback(myfunc))
}

所以,这就是事情开始变得更有用的地方。我们的框架现在允许我们注册函数,以便框架的用户可以开始创建他们自己的功能。

使用我们框架的其他项目可以开始注册他们自己的函数,这些函数随后可以在他们自己的前端应用程序中使用。

组件

所以,我想接下来我们需要考虑添加到我们的框架中的是组件的概念。基本上,我希望能够components/ 在使用它的项目中定义一个目录,并且在该目录中,我希望能够像一个home.go组件一样构建一个包含我主页所需的所有代码的组件。

那么,我们该怎么做呢?

好吧,React 倾向于提供具有功能的类,这些功能render()返回 HTML/JSX/您希望为所述组件呈现的任何代码。让我们窃取它并在我们自己的组件中使用它。

我本质上希望能够在使用这个框架的项目中做这样的事情:

package components

type HomeComponent struct{}

var Home HomeComponent

func (h HomeComponent) Render() string {
    return "<h2>Home Component</h2>"
}

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

为了将组件添加到我们的框架中,我们将保持简单,只需定义一个interface我们随后定义的任何组件都必须遵守的。components/comopnent.go在我们的 Oak 框架中创建一个名为的新文件:

// components/component.go
package component

type Component interface {
    Render() string
}

如果我们想为各种组件添加新功能会发生什么?好吧,这使我们能够做到这一点。我们可以oak.RegisterFunction在init组件的函数中使用调用来注册我们想要在组件中使用的任何函数!

package components

import (
    "syscall/js"

    "github.com/elliotforbes/go-webassembly-framework"
)

type AboutComponent struct{}

var About AboutComponent

func init() {
    oak.RegisterFunction("coolFunc", CoolFunc)
}

func CoolFunc(i []js.Value) {
    println("does stuff")
}

func (a AboutComponent) Render() string {
    return `<div>
                        <h2>About Component Actually Works</h2>
                        <button onClick="coolFunc();">Cool Func</button>
                    </div>`
}

当我们将它与路由器结合使用时,我们应该能够看到我们HTML正在呈现到我们的页面,并且我们应该能够单击调用的那个按钮, coolFunc()它将does stuff在我们的浏览器控制台中打印出来!

太棒了,让我们看看我们现在如何构建一个简单的路由器。

构建路由器

好的,所以我们已经了解了components在我们的 Web 框架内的概念。我们差不多完成了吧?

不完全是,接下来我们可能需要的是一种在不同组件之间导航的方法。大多数框架似乎都有一个<div>特殊的属性 id,它们绑定到并在其中渲染所有组件,因此我们将在 Oak 中采取相同的策略。

让我们router/router.go在我们的橡木框架中创建一个文件,这样我们就可以开始破解了。

在此范围内,我们希望将string路径映射到组件,我们不会进行任何 URL 检查,我们现在将所有内容都保存在内存中以保持简单:

// router/router.go
package router

import (
    "syscall/js"

    "github.com/elliotforbes/go-webassembly-framework/component"
)

type Router struct {
    Routes map[string]component.Component
}

var router Router

func init() {
    router.Routes = make(map[string]component.Component)
}

因此,在此范围内,我们创建了一个新Router结构,其中包含Routes 字符串到我们在上一节中定义的组件的映射。

在我们的框架中,路由不是一个强制性的概念,我们希望用户选择他们希望何时初始化一个新的路由器。因此,让我们创建一个新函数,该Link函数将注册一个函数并将我们地图中的第一个路由绑定到我们的<div id="view"/>html 标签:

// router/router.go
// ...
func NewRouter() {
    js.Global().Set("Link", js.NewCallback(Link))
    js.Global().Get("document").Call("getElementById", "view").Set("innerHTML", "")
}

func RegisterRoute(path string, component component.Component) {
    router.Routes[path] = component
}

func Link(i []js.Value) {
    println("Link Hit")

    comp := router.Routes[i[0].String()]
    html := comp.Render()

    js.Global().Get("document").Call("getElementById", "view").Set("innerHTML", html)
}

您应该注意到,我们创建了一个RegisterRoute函数,允许我们将 a 注册path到给定的组件。

Link从某种意义上说,我们的函数也很酷,它允许我们在项目中的各种组件之间导航。我们可以指定非常简单的<button>元素来允许我们导航到注册路径,如下所示:

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

太棒了,所以我们现在已经启动并运行了一个非常简单的路由器,如果我们想在一个简单的应用程序中使用它,我们可以这样做:

// my-project/main.go
package main

import (
    "github.com/elliotforbes/go-webassembly-framework"
    "github.com/elliotforbes/go-webassembly-framework/examples/blog/components"
    "github.com/elliotforbes/go-webassembly-framework/router"
)

func main() {
    // Starts the Oak framework
    oak.Start()

    // Starts our Router
    router.NewRouter()
    router.RegisterRoute("home", components.Home)
    router.RegisterRoute("about", components.About)

    // keeps our app running
    done := make(chan struct{}, 0)
    <-done
}

一个完整的例子

将所有这些放在一起,我们可以开始构建具有组件和路由功能的非常简单的 Web 应用程序。如果您想查看几个有关其工作原理的示例,请查看官方 repo 中的示例: elliotforbes/go-webassembly-framework/examples

未来的挑战

这个框架中的代码绝不是生产就绪的,但我希望这篇文章能引发关于我们如何开始在 Go 中构建更多的生产就绪框架的良好讨论。

如果不出意外,它开始了确定仍然需要做些什么才能使其成为 React/Angular/VueJS 之类的可行替代方案的旅程,所有这些都是显着提高开发人员生产力的非凡框架。

我希望这篇文章能激励你们中的一些人开始研究如何在这个非常简单的起点上进行改进。

结论

如果您喜欢本教程,请随时将其分享给您的朋友或任何您喜欢的地方,它真的对网站有帮助,并直接支持我写更多!

原文链接:,转发请注明来源!