Skip to content

proposal: syscall/js: add way to import() es modules  #67797

Open
@jcbhmr

Description

@jcbhmr

Proposal Details

I don't know if this is a thing that already exists or not. I want to be able to import ESM modules in Go. I can do this in JavaScript via the dynamic import() expression. Right now the only way I know how to do this without extra modifications (where I expect the consumer to just copy-paste wasm_exec.js, wasm_exec_node.js, or wasm_exec.html into their source tree and use that) is to use eval() which seems... not ideal.

package main

import (
	"syscall/js"
	"log"
)

// import is a function-like keyword, not a global function
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
var jsImportFunc = js.Global().Get("eval").Invoke("(s,o)=>import(s,o)") // 🟥🟥🟥
func jsImport(specifier string, options any) js.Value {
	return jsImportFunc.Invoke(specifier, options)
}
// https://stackoverflow.com/questions/68415674/await-js-async-function-promise-inside-a-go-function
func jsAwait(promise js.Value) js.Value {
	c := make(chan js.Value)
	jsOnResolve := js.FuncOf(func(this js.Value, args []js.Value) any {
        c <- args[0]
		return nil
    })
	jsOnReject := js.FuncOf(func(this js.Value, args []js.Value) any {
		// act the same as if a function threw
		panic(&js.Error{Value:args[0]})
		return nil
	})
	promise.Call("then", jsOnResolve, jsOnReject)
	return <-c
}

func main() {
	jsPrettier := jsAwait(jsImport("prettier", js.Value{}))

	jsFormatted := jsAwait(jsPrettier.Call("format", "hello();world();", map[string]any{
		"parser": "typescript", // https://prettier.io/docs/en/options#parser
	}))
	log.Print(jsFormatted.String())
}

The eval() breaks when a user of the Go wasm blob disallows unsafe eval() via Content-Security-Policy.

I know, I know. I could just do globalThis.import = (s,o)=>import(s,o) in JavaScript before I run the Go test.wasm blob. But I don't control how the user uses my WASM blob and it would be really nice if I didn't have to ask them to "hey please add this thing when you use the wasm_exec.js to load this wasm file".

A cool minor change to do this would be to add a Go.prototype._import() method that is just this:

class Go {
	_import(specifier, options) {
		return import(specifier, options)
	}
}

and then in the syscall/js Go code:

func Import(specifier string, options any) js.Value {
	return whateverThePrivateGoRuntimeVariableIsCalled.Call("_import", specifier, options)
}

or you could add it to the importObject thing instead. The point is that this syntax thing (not a global function) is currently unobtainable without resorting to eval() or forcing the user of the my-cool-go-thing.wasm to include a special snippet in addition to the wasm_exec.js standard Go wasm+js runtime.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions