浏览器内Golang编译器使用指南

浏览器内Golang编译器使用指南 我最近了解到Go可以通过WebAssembly在浏览器中运行。这让我开始思考如何让Go编译器在浏览器中工作,以便编译可下载的二进制文件。

我在StackOverflow上提出了一个关于这个问题的问题,并得到了一个确认这是可能的答案,同时提供了一个需要完成事项的高层次概述。然而,在查看了Golang的GitHub仓库之后,我意识到这将有多么困难。我甚至不知道从哪里开始!

我想知道是否有人能给我一些关于如何进行此事的指导。我基本上是在考虑将Go编译器本身编译为WASM。

14 回复

抱歉久等了——我目前正在休假。我该如何执行引导代码?对WASM不太熟悉。

更多关于浏览器内Golang编译器使用指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢提供的链接。我会马上查看。至于编译器是一个带有主入口点的二进制文件,难道不能通过重命名“main”然后在准备好时从JavaScript调用新名称来解决吗?还是我误解了什么?

TR-SLimey:

这让我想到让Go编译器在浏览器中工作,以便编译可下载的二进制文件。

这能让你下载源代码并在浏览器中编译它,但你为什么想这样做,而不是直接下载适合你平台的二进制文件呢?

我想尝试一下,看起来足够简单,以下是我的操作步骤:

这个命令本应编译用于 WASM 的 Go 源代码,但我执行时遇到了一个错误。所以我认为你的挑战将是解决这个错误。

真的吗?在我的Linux系统上使用Brave浏览器,搭载Core i7-8750H处理器,大约只需要4-5秒。你用的是哪个浏览器?我记得作者提到过Edge和Safari非常慢。无论如何,对于我的项目来说,跨平台支持、安全性和易用性比速度稍微重要一些,所以我愿意做出这个妥协。更不用说,当网络连接非常慢但硬件性能良好时,这种方式实际上可能更快。

您是否使用过 play.golang.org?该网站允许您输入 Go 代码,构建并运行您的程序。它有一个下载功能,但只允许您下载编辑器中已有的源代码。我还没有查看 play.golang.org 的源代码,但我建议从这里开始,然后添加一个功能,提示用户选择 GOOS 和 GOARCH,然后下载结果。

😬 我刚刚在我的酷睿 i9-9900 上试了一下,编译并运行默认的 “hello world” 程序大约需要 17 秒,即使运行了几次之后也是如此。相比之下,在 play.golang.org 上只需要 1 秒。

经过进一步研究,看来最终还是有人抢先一步了。这里是相关 Reddit 帖子的链接:https://www.reddit.com/r/golang/comments/c95ngi/i_compiled_go_for_wasm_and_made_a_playground_that/

虚拟主机套餐是许多主机提供商提供的一种服务——用户只能访问主机系统上的一个文件夹(他们的网站根目录),并且预装了网站托管环境,无法访问机器的其他部分。例如,这些方案通常比VPS便宜得多,但限制也大得多。这就是为什么在服务器端编译并不总是可行的。我也在考虑将我的应用程序打包成类似Electron的形式,这基本上也需要在客户端进行编译,因为不会有服务器。

请原谅我的无知,但我不明白什么是网络托管包,即使在谷歌搜索之后也不明白(我不是专业程序员,我的兴趣在于底层内容,因此我对编译问题感兴趣)。编译时选项的论点对我来说是合理的,但我仍然不明白为什么你希望在浏览器中客户端编译Go。对于编译时选项,为什么不通过网页提示用户选择他们想要的选项,然后在服务器端检查之前是否请求过该组合?如果是,发送预编译版本,否则编译并发送。我个人几乎一直使用移动设备,客户端编译对我来说并不是最好的体验。我只是很难理解这个场景。

好的,我已经成功将HTML内容转换为Markdown格式,并遵循了所有严格规则。以下是最终的Markdown文档:

好的,我已经成功修改了帖子中的项目,制作了一个功能完整的浏览器内Go编译器。目前它仅支持为amd64架构的Linux系统进行编译,并且和原帖中的项目一样,只支持运行时库。但我计划在未来加入对其他架构和库的支持。同时,我将Go编译器版本从原帖的1.12更新到了1.15.1,因此应该会有一些速度上的提升。这里是演示链接,供感兴趣的人查看:https://tr-slimey.github.io/IBGC/。该演示页面也包含了主仓库的链接。

下载二进制编译器还是我正在尝试编译的二进制程序?如果是前者,我正努力使我的项目非常灵活,以便用户只需访问网站即可下载文件,无需安装任何其他软件。如果是后者,我的程序有一些编译时选项,我希望提供给用户使用。我认为这不是问题,因为Golang编译速度相当快。

至于你的第二个问题,我已经看过,但Playground的做法是将代码发送到服务器,在沙箱中执行,然后返回结果。我希望让我的编译器仅在浏览器中运行,以便可以在虚拟主机套餐中使用。我认为曾经有计划让Playground将代码编译为WASM并在浏览器中执行,但编译过程仍然会在服务器上进行。不确定他们是否已经实现了这一点。

我之前没有使用过WASM,所以在这方面可能帮不上忙。我查阅了MDN

MDN Web Docs MDN Web Docs

MDN Logo

加载和运行WebAssembly代码

要在JavaScript中使用WebAssembly,你首先需要在编译/实例化之前将模块加载到内存中。本文提供了可用于获取WebAssembly字节码的不同机制的参考,以及如何…

其中涉及实例化二进制文件,然后从中导出函数。在我们的案例中,它是一个带有主入口点的可执行二进制文件,而不是一个库,因此我在该页面上看到的方法与你在这里尝试实现的目标不太相关。

将Go编译器编译为WebAssembly并在浏览器中运行是一个复杂但可行的项目。以下是实现这一目标的关键步骤和示例代码:

1. 编译Go编译器为WASM

首先,你需要将Go编译器本身编译为WebAssembly格式:

// 在构建环境中设置目标为wasm
GOOS=js GOARCH=wasm go build -o compiler.wasm cmd/compile/*

2. 浏览器端执行环境

创建HTML页面加载WASM编译器并设置执行环境:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Go Compiler in Browser</title>
    <script src="wasm_exec.js"></script>
</head>
<body>
    <textarea id="source" rows="20" cols="80">
package main

import "fmt"

func main() {
    fmt.Println("Hello from browser Go!")
}
    </textarea>
    <button onclick="compile()">Compile</button>
    <div id="output"></div>
    
    <script>
        const go = new Go();
        
        async function compile() {
            const source = document.getElementById('source').value;
            
            // 加载WASM编译器
            const response = await fetch('compiler.wasm');
            const bytes = await response.arrayBuffer();
            const { instance } = await WebAssembly.instantiate(bytes, go.importObject);
            
            await go.run(instance);
            
            // 调用编译器功能
            const result = window.compileGo(source);
            document.getElementById('output').innerText = result;
        }
        
        // 暴露给WASM调用的函数
        window.compileGo = function(source) {
            // 这里实现编译逻辑
            return "Compilation result";
        };
    </script>
</body>
</html>

3. Go编译器适配层

创建适配层,让Go编译器能在浏览器环境中工作:

// main.go - 编译器的浏览器适配版本
package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "os"
    "syscall/js"
)

func compile(this js.Value, args []js.Value) interface{} {
    source := args[0].String()
    
    // 创建临时文件
    tmpfile, err := ioutil.TempFile("", "*.go")
    if err != nil {
        return fmt.Sprintf("Error creating temp file: %v", err)
    }
    defer os.Remove(tmpfile.Name())
    
    // 写入源代码
    if _, err := tmpfile.Write([]byte(source)); err != nil {
        return fmt.Sprintf("Error writing source: %v", err)
    }
    tmpfile.Close()
    
    // 调用Go编译器
    var buf bytes.Buffer
    oldStdout := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w
    
    // 模拟命令行参数
    os.Args = []string{"compile", tmpfile.Name()}
    
    // 运行编译
    go func() {
        // 这里需要调用实际的编译器逻辑
        w.Close()
    }()
    
    // 读取输出
    output, _ := ioutil.ReadAll(r)
    os.Stdout = oldStdout
    
    return string(output)
}

func main() {
    // 注册JavaScript可调用的函数
    js.Global().Set("compileGo", js.FuncOf(compile))
    
    // 保持程序运行
    select {}
}

4. 文件系统模拟

由于浏览器没有真实的文件系统,需要实现虚拟文件系统:

// vfs.go - 虚拟文件系统实现
package main

import (
    "syscall/js"
)

type BrowserFS struct {
    files map[string][]byte
}

func NewBrowserFS() *BrowserFS {
    return &BrowserFS{
        files: make(map[string][]byte),
    }
}

func (fs *BrowserFS) WriteFile(name string, data []byte) error {
    fs.files[name] = data
    return nil
}

func (fs *BrowserFS) ReadFile(name string) ([]byte, error) {
    data, exists := fs.files[name]
    if !exists {
        return nil, fmt.Errorf("file not found: %s", name)
    }
    return data, nil
}

// 集成到编译器
func setupVirtualFS() {
    fs := NewBrowserFS()
    // 重定向文件操作到虚拟文件系统
}

5. 构建脚本

创建构建脚本自动化整个过程:

#!/bin/bash
# build.sh

# 1. 构建Go编译器为WASM
echo "Building Go compiler for WASM..."
GOOS=js GOARCH=wasm go build -o wasm/compiler.wasm ./cmd/compile

# 2. 复制WASM执行支持文件
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" wasm/

# 3. 构建用户界面
echo "Building interface..."
go build -o server main.go

echo "Build complete!"

6. 限制和注意事项

// 需要注意的浏览器环境限制:
// 1. 没有真正的文件系统
// 2. 网络访问受限(需要CORS)
// 3. 内存限制
// 4. 无法直接生成可执行文件(但可生成WASM)

// 可能的解决方案:
// - 使用IndexedDB模拟文件系统
// - 通过Web Workers处理编译任务
// - 使用Service Worker缓存编译器

这个实现展示了将Go编译器移植到浏览器的基本架构。实际实现需要处理更多细节,包括完整的编译器工具链、依赖管理和二进制输出生成。

回到顶部