Skip to content

proposal: cmd/go: option to bundle wasm output with wasm_exec.js #72055

Open
@jcbhmr

Description

@jcbhmr

Proposal Details

Similar to how there's -ldflags "-H windowsgui" for Windows I think there should be something like -ldflags "-H jsmodule" (output ES module which uses top-level await) and/or -ldflags "-H jsscript" (output classic global script which just does .run() and doesn't await it). Or it could be some completely different option. I don't know where this feature request makes the most sense.

Why do this? So that users have the option to get the correct wasm_exec.js bundled with the output instead of needing to remember to tell any downstream user of myapp.wasm "hey! this was built against Go v1.24.0 so remember to use Go v1.24.0's wasm_exec.js and NOT Go v1.23.0's wasm_exec.js" or similar that you might already have in your codebase.

This might make Go WASM more "reproducible" (is that the right word?) because now the JS can optionally be part of the included output and thus doesn't have to be specified out-of-band as "I used this wasm_exec.js (paste code)" when filing bug reports or whatever.

Here's an idea for a template. I have no idea if it works -- this is just illustrative.

#!/usr/bin/env node
// ^^ this syntax is also supported (ignored) by the ecma spec so this file
// can be <script type=module src="./a.out.js"> too!

/* copy-paste existing wasm_exec.js here. */

const base64 = /* EMBED THE WASM AS BASE64 STRING HERE */;
let bytes;
if (Uint8Array.fromBase64) {
  bytes = Uint8Array.fromBase64(base64)
} else {
  bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0))
}

const go = new Go();
go.importMeta = import.meta;

// true on Node.js, Deno, Bun. false in browsers and anywhere else.
const isNodeLike = await import("node:process").then(() => true, () => false)
if (isNodeLike) {
  const { default: process } = await import("node:process")
  const fs = await import("node:fs")
  const path = await import("node:path")
  const os = await import("node:os")

  go.argv = process.argv.slice(2);
  go.env = { TMPDIR: os.tmpdir(), ...process.env };
  go.exit = process.exit;

  process.on("exit", (code) => { // Node.js exits if no event handler is pending
    if (code === 0 && !go.exited) {
      // deadlock, make Go print error and stack traces
      go._pendingEvent = { id: 0 };
      go._resume();
    }
  });

  globalThis.process = process;
  globalThis.fs = fs;
  globalThis.path = path;
}

const { instance } = await WebAssembly.instantiate(bytes, go.importObject)

await go.run(instance)

Remember, currently js/wasm only outputs main command executables and not libraries so you can assume that it should always be run as a top-level item and not imported as a library. ex: node a.out or somePrepWork(); import "./a.out" but not import { f } from "./a.out"; restOfCode()

THIS SHOULD NOT CHANGE THE DEFAULT BEHAVIOUR I understand that changing the default output of GOOS=js GOARCH=wasm go build is a bad idea. Instead, I'm proposing an optional mode that outputs JS wrapping the wasm

The -H flag as part of https://pkg.go.dev/cmd/link seems like a good fit but idk I'm not a compiler dev lol

-H type
	Set executable format type.
	The default format is inferred from GOOS and GOARCH.
	On Windows, -H windowsgui writes a "GUI binary" instead of a "console binary."

so I thought "hey maybe -H jsmodule fits the -H <goos><type> of "windowsgui" where goos=js and type=module".

Alternatives

  • Create a golang.org/x/tools/cmd/go-toolexec-output-jsmodule that can be used with -toolexec to wrap and post-process some part of the build commands and output JS instead of WASM to the out file
  • Do nothing. Rely on out-of-band existing workflow to specify which wasm_exec.js to use
  • Change the default output of GOOS=js GOARCH=wasm to JS instead of WASM
  • Output a sibling a.out.js file that fetch()-es the ./a.out.wasm file
  • publish wasm_exec.js to npm proposal: syscall/js: publish wasm_exec.js and wasm_exec_node.js bindings to npm #58250
  • codify and standardize a stability policy on what the Go WASM target expects to be exposed from JS

Metadata

Metadata

Assignees

No one assigned

    Labels

    GoCommandcmd/goOS-JSProposalToolProposalIssues describing a requested change to a Go tool or command-line program.arch-wasmWebAssembly issues

    Type

    No type

    Projects

    Status

    Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions