cmd/gogio: [wasm] embed .js files

Similar to the Android, which includes all .jar files into the .apk.
Now, the `gogio -target js` will include all `*_js.js` files
into the resulting `wasm.js`.

That change make possible to adds custom wasm-imports, which might
be used in future versions of Gio.

Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
Inkeliz
2020-12-11 18:22:17 +00:00
committed by Elias Naur
parent 0dfb04eb2d
commit 745bb949bb
+112 -35
View File
@@ -4,10 +4,13 @@ package main
import (
"fmt"
"golang.org/x/tools/go/packages"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
func buildJS(bi *buildInfo) error {
@@ -35,34 +38,93 @@ func buildJS(bi *buildInfo) error {
if err != nil {
return err
}
const indexhtml = `<!doctype html>
if err := ioutil.WriteFile(filepath.Join(out, "index.html"), []byte(jsIndex), 0600); err != nil {
return err
}
goroot, err := runCmd(exec.Command("go", "env", "GOROOT"))
if err != nil {
return err
}
wasmJS := filepath.Join(goroot, "misc", "wasm", "wasm_exec.js")
if _, err := os.Stat(wasmJS); err != nil {
return fmt.Errorf("failed to find $GOROOT/misc/wasm/wasm_exec.js driver: %v", err)
}
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
Env: append(os.Environ(), "GOOS=js", "GOARCH=wasm"),
}, bi.pkgPath)
if err != nil {
return err
}
extraJS, err := findPackagesJS(pkgs[0], make(map[string]bool))
if err != nil {
return err
}
return mergeJSFiles(filepath.Join(out, "wasm.js"), append([]string{wasmJS}, extraJS...)...)
}
func findPackagesJS(p *packages.Package, visited map[string]bool) (extraJS []string, err error) {
if len(p.GoFiles) == 0 {
return nil, nil
}
js, err := filepath.Glob(filepath.Join(filepath.Dir(p.GoFiles[0]), "*_js.js"))
if err != nil {
return nil, err
}
extraJS = append(extraJS, js...)
for _, imp := range p.Imports {
if !visited[imp.ID] {
extra, err := findPackagesJS(imp, visited)
if err != nil {
return nil, err
}
extraJS = append(extraJS, extra...)
visited[imp.ID] = true
}
}
return extraJS, nil
}
// mergeJSFiles will merge all files into a single `wasm.js`. It will prepend the jsSetGo
// and append the jsStartGo.
func mergeJSFiles(dst string, files ...string) (err error) {
w, err := os.Create(dst)
if err != nil {
return err
}
defer func() {
if cerr := w.Close(); err != nil {
err = cerr
}
}()
_, err = io.Copy(w, strings.NewReader(jsSetGo))
if err != nil {
return err
}
for i := range files {
r, err := os.Open(files[i])
if err != nil {
return err
}
_, err = io.Copy(w, r)
r.Close()
if err != nil {
return err
}
}
_, err = io.Copy(w, strings.NewReader(jsStartGo))
return err
}
const (
jsIndex = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="mobile-web-app-capable" content="yes">
<script src="wasm_exec.js"></script>
<script>
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
const go = new Go();
// Pick up argv from the argv query argument (if set).
const params = new URLSearchParams(location.search);
const argv = params.get("argv");
if (argv) {
go.argv = go.argv.concat(argv.split(" "));
}
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
</script>
<script src="wasm.js"></script>
<style>
body,pre { margin:0;padding:0; }
</style>
@@ -70,16 +132,31 @@ func buildJS(bi *buildInfo) error {
<body>
</body>
</html>`
if err := ioutil.WriteFile(filepath.Join(out, "index.html"), []byte(indexhtml), 0600); err != nil {
return err
// jsSetGo sets the `window.go` variable.
jsSetGo = `(() => {
window.go = {argv: [], env: {}, importObject: {go: {}}};
window.go["argv"] = (new URLSearchParams(location.search).get("argv") ?? "").split(" ");
})();`
// jsStartGo initializes the main.wasm.
jsStartGo = `(() => {
defaultGo = new Go();
Object.assign(defaultGo["argv"], go["argv"]);
Object.assign(defaultGo["env"], go["env"]);
for (let key in go["importObject"]) {
if (typeof defaultGo["importObject"][key] === "undefined") {
defaultGo["importObject"][key] = {};
}
Object.assign(defaultGo["importObject"][key], go["importObject"][key]);
}
goroot, err := runCmd(exec.Command("go", "env", "GOROOT"))
if err != nil {
return err
}
wasmjs := filepath.Join(goroot, "misc", "wasm", "wasm_exec.js")
if _, err := os.Stat(wasmjs); err != nil {
return fmt.Errorf("failed to find $GOROOT/misc/wasm/wasm_exec.js driver: %v", err)
}
return copyFile(filepath.Join(out, "wasm_exec.js"), wasmjs)
}
window.go = defaultGo;
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
})();`
)