专栏首页Golang语言社区使用WebAssembly和Go编写前端Web框架

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

原文作者:Elliot Forbes

Table Of Contents

  • Introduction
  • Starting Point
  • Function Registration
  • Components
  • Building a Router
  • A Full Example
  • Challenges Going Forward
  • Conclusion

JavaScript Frontend frameworks have undoubtedly helped to push the boundaries of what was previously possible in the context of a browser. Ever more complex applications have come out built on top of the likes of React, Angular and VueJS to name but a few and there’s the well known joke about how a new frontend framework seems to come out every day.

However, this pace of development is exceptionally good news for developers around the world. With each new framework, we discover better ways of handling state, or rendering efficiently with things like the shadow DOM.

The latest trend however, seems to be moving towards writing these frameworks in languages other than JavaScript and compiling them into WebAssembly. We’re starting to see major improvements in the way that JavaScript and WebAssembly communicates thanks to the likes of Lin Clark and we’ll undoubtedly see more major improvements as WebAssembly starts to become more prominent in our lives.

Introduction

So, in this tutorial, I thought it would be a good idea to build the base of an incredibly simple frontend framework written in Go that compiles into WebAssembly. At a minimum, this will include the following features:

  • Function Registration
  • Components
  • Super Simplistic-Routing

I’m warning you now though that these are going to be incredibly simple and nowhere near production ready. If this is article is somewhat popular, I’ll hopefully be taking it forward however, and trying to build something that meets the requirements of a semi-decent frontend framework.

Github: The full source code of this project can be found here: elliotforbes/oak. If you fancy contributing to the project, feel free, I’d be happy to get any pull requests!

Starting Point

Right, let’s dive into our editor of choice and start coding! The first thing we’ll want to do is create a really simple index.html that will act as our entry point for our frontend framework:

 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>

You’ll notice these have 2 js files being imported at the top, these allow us to execute our finished WebAssembly binary. The first of which is about 414 lines long so, in the interest of keeping this tutorial readable, I recommend you download it from here: https://github.com/elliotforbes/oak/blob/master/examples/blog/static/wasm_exec.js

The second is our entrypoint.js file. This will fetch and run the lib.wasm that we’ll be building very shortly.

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

Finally, now that we have that out of the way, we can start diving into some Go code! Create a new file called main.go which will contain the entry point to our Oak Web Framework!

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

This is as simple as it gets. We’ve created a really simple Go program that should just print out Oak Framework Initialized when we open up our web app. To verify that everything works, we need to compile this using the following command:

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

This should then build our Go code and output our lib.wasm file which we referenced in our entrypoint.js file.

Awesome, if everything worked, then we are ready to try it out in the browser! We can use a really simple file server like this:

 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}

You can then serve your application by typing go run server.go and you should be able to access your app from http://localhost:8080.

Function Registration

Ok, so we’ve got a fairly basic print statement working, but in the grand scheme of things, I don’t quite think that qualifies it as a Web Framework just yet.

Let’s take a look at how we can build functions in Go and register these so we can call them in our index.html. We’ll create a new utility function which will take in both a string which will be the name of our function as well as the Go function it will map to.

Add the following to your existing main.go file:

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}

So, this is where things start to become a bit more useful. Our framework now allows us to register functions so users of the framework can start creating their own functionality.

Other projects using our framework can start to register their own functions that can subsequently be used within their own frontend applications.

Components

So, I guess the next thing we need to consider adding to our framework is the concept of components. Basically, I want to be able to define a components/ directory within a project that uses this, and within that directory I want to be able to build like a home.gocomponent that features all the code needed for my homepage.

So, how do we go about doing this?

Well, React tends to feature classes that feature render() functions which return the HTML/JSX/whatever code you wish to render for said component. Let’s steal this and use it within our own components.

I essentially want to be able to do something like this within a project that uses this framework:

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

So, within my components package, I define a HomeComponent which features a Render() method which returns our HTML.

In order to add components to our framework, we’ll keep it simple and just define an interface to which any components we subsequently define will have to adhere to. Create a new file called components/comopnent.go within our Oak framework:

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

What happens if we want to add new functions to our various components? Well, this allows us to do just that. We can use the oak.RegisterFunction call within the initfunction of our component to register any functions we want to use within our component!

 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}

When we combine this with a router, we should be able to see our HTML being rendered to our page and we should be able to click that button which calls coolFunc() and it will print out does stuff within our browser console!

Awesome, let’s see how we can go about building a simple router now.

Building a Router

Ok, so we’ve got the concept of components within our web framework down. We’ve almost finished right?

Not quite, the next thing we’ll likely need is a means to navigate between different components. Most frameworks seem to have a <div> with a particular id that they bind to and render all their components within, so we’ll steal that same tactic within Oak.

Let’s create a router/router.go file within our oak framework so that we can start hacking away.

Within this, we’ll want to map string paths to components, we wont do any URL checking, we’ll just keep everything in memory for now to keep things simple:

 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}

So within this, we’ve created a new Router struct which contains Routes which are a map of strings to the components we defined in the previous section.

Routing won’t be a mandatory concept within our framework, we’ll want users to choose when they wish to initialize a new router. So let’s create a new function that will register a Link function and also bind the first route in our map to our <div id="view"/> html tag:

 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}

You should notice, we’ve created a RegisterRoute function which allows us to register a path to a given component.

Our Link function is also pretty cool in the sense that it will allow us to navigate between various components within a project. We can specify really simple <button>elements to allow us to navigate to registered paths like so:

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

Awesome, so we’ve got a really simple router up and running now, if we wanted to use this in a simple application we could do so like this:

 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}

A Full Example

With all of this put together, we can start building really simple web applications that feature components and routing. If you want to see a couple of examples as to how this works, then check out the examples within the official repo: elliotforbes/oak/examples

Challenges Going Forward

The code in this framework is in no way production ready, but I’m hoping this post kicks off good discussion as to how we can start building more production ready frameworks in Go.

If nothing else, it starts the journey of identifying what still has to be done to make this a viable alternative to the likes of React/Angular/VueJS, all of which are phenomenal frameworks that massively speed up developer productivity.

I’m hoping this article motivates some of you to go off and start looking at how you can improve on this incredibly simple starting point.

Conclusion

If you enjoyed this tutorial, then please feel free to share it to your friends, on your twitter, or wherever you feel like, it really helps the site and directly supports me writing more!

I’m also on YouTube, so feel free to subscribe to my channel for more Go content! - TutorialEdge.

The full source code for the Oak framework can be found here: github.com/elliotforbes/oak. Feel free to submit PRs!


版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

本文分享自微信公众号 - Golang语言社区(Golangweb)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 在Go中使用服务对象模式

    NOTE: Most of the code and ideas in this post are things I have been experimenti...

    李海彬
  • Golang国际化(i18n)和本地化(l10n)指南

    Go is a statically compiled language that gained a lot of popularity lately due ...

    李海彬
  • PHP was never meant to die

    For the last 10 years, we have been developing software for fortune 500 business...

    李海彬
  • C++核心准则C.152:永远不要将派生类数组的指针赋值给基类指针

    Subscripting the resulting base pointer will lead to invalid object access and p...

    面向对象思考
  • 乐器分类的端对端对抗性白盒攻击行为(CS SD)

    对输入数据的小规模对抗性扰动会在很大程度上改变机器学习系统的性能,从而对这种系统的有效性提出挑战。我们提出了第一个针对乐器分类系统的端到端对抗性攻击,允许直接在...

    Rosalie
  • Tracking Emerges by Colorizing Videos

    Carl Vondrick , Abhinav Shrivastava , Alireza Fathi , Sergio Guadarrama ,Kevin M...

    用户1908973
  • Python学习四周小结-课堂笔记篇

    #coding:gbk或#coding:utf-8或##-*- coding : gbk -*-

    汐楓
  • 通过共分割实现基于涂鸦的域自适应

    中文摘要:虽然深卷积网络在许多医学图像分割任务中已经达到了最先进的性能,但它们通常表现出较差的泛化能力。为了能够从一个领域(例如,一种成像模式)归纳到另一个领域...

    用户7454122
  • css的知识点

    水平导航栏 有两种方法创建横向导航栏。使用内联(inline)或浮动(float)的列表项。

    东风冷雪
  • 使用Apache Server 的ab进行web请求压力测试

    参考:http://www.cnblogs.com/spring3mvc/archive/2010/11/23/2414741.html 自己写代码经常是顺着逻...

    Ryan-Miao

扫码关注云+社区

领取腾讯云代金券