跳转到主要内容

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>

你会注意到它们在顶部导入了 2 个 js 文件,它们允许我们执行我们完成的 WebAssembly 二进制文件。其中第一行大约 414 行,为了保持本教程的可读性,我建议您从这里下载: https://github.com/elliotforbes/go-webassembly-framework/blob/master/examples/blog/static/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 程序,当我们打开我们的 Web 应用程序时,它应该只打印出 Oak Framework Initialized。为了验证一切正常,我们需要使用以下命令编译它:

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


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

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

// 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 中调用它们。我们将创建一个新的实用程序函数,该函数将接收一个字符串,该字符串将是我们函数的名称以及它将映射到的 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>"
}

因此,在我的组件包中,我定义了一个 HomeComponent,它具有一个返回我们的 HTML 的 Render() 方法。

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

// components/component.go
package component

type Component interface {
    Render() string
}

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

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() 的那个按钮,它会在我们的浏览器控制台中打印出来!

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

构建路由器


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

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

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

在此,我们希望将字符串路径映射到组件,我们不会进行任何 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 函数,它允许我们注册一个给定组件的路径。

从某种意义上说,我们的 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 之类的可行替代方案的旅程,所有这些都是显着提高开发人员生产力的非凡框架。

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

结论


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

我也在 YouTube 上,所以请随时订阅我的频道以获取更多 Go 内容! - https://youtube.com/tutorialedge

Oak 框架的完整源代码可以在这里找到:github.com/elliotforbes/go-webassembly-framework。随意提交 PR!

 

文章链接