V8go
Provides Go bindings for V8 JavaScript engine
Install / Use
/learn @gost-dom/V8goREADME
Execute JavaScript from Go
[!caution]
Do not base work off this project!
This is the version of v8go used by Gost-DOM. This project was forked from tommie's v8go fork, but has additional features needed by Gost-DOM not yet integrated back to tommie's fork.
All branches in this repository are subject to force-pushing (if you don't know what that means, it means: if you base work off that branch, you will regret it)
[!note]
So if you see functionality here that you'd like to use, let me know, and I can prioritise PR'ing it back to Tommie. It is the intention to contribute all new features in this reposition back to tommie's, but my priority is Gost-DOM itself.
<a href="https://github.com/tommie/v8go/releases"><img src="https://img.shields.io/github/v/release/tommie/v8go" alt="Github release"></a>
Relation to rogchap.com/v8go
This is a fork of https://github.com/rogchap/v8go at v0.9.0.
Major differences include
- Android amd64/arm64 support.
- Works with the new Chromium release dashboard (used to find what the stable version of V8 is).
- Actually upgrades V8. See https://github.com/rogchap/v8go/issues/399.
- Splits the v8 static libraries to work around the GitHub file size limit of 100 MB.
The splitter doesn't care about dependencies, so binutils
ldrequires--start-grouparound them. Notably, the XCodelddoesn't care about ordering. - Support for JS Symbols.
- Support for native exceptions and
FunctionCallbackreturning an error. - A rebuilt build pipeline, being more consistent.
- We now build everything at once. Originally, the build pipeline left the master branch inconsistent between header files and libraries of individual architectures.
- The library builder commits directly, without a PR, avoiding PR blow-up.
- Using ccache, based on https://github.com/kuoruan/libv8.
Usage
import v8 "github.com/tommie/v8go"
Running a script
ctx := v8.NewContext() // creates a new V8 context with a new Isolate aka VM
ctx.RunScript("const add = (a, b) => a + b", "math.js") // executes a script on the global context
ctx.RunScript("const result = add(3, 4)", "main.js") // any functions previously added to the context can be called
val, _ := ctx.RunScript("result", "value.js") // return a value in JavaScript back to Go
fmt.Printf("addition result: %s", val)
One VM, many contexts
iso := v8.NewIsolate() // creates a new JavaScript VM
ctx1 := v8.NewContext(iso) // new context within the VM
ctx1.RunScript("const multiply = (a, b) => a * b", "math.js")
ctx2 := v8.NewContext(iso) // another context on the same VM
if _, err := ctx2.RunScript("multiply(3, 4)", "main.js"); err != nil {
// this will error as multiply is not defined in this context
}
JavaScript function with Go callback
iso := v8.NewIsolate() // create a new VM
// a template that represents a JS function
printfn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value {
fmt.Printf("%v", info.Args()) // when the JS function is called this Go callback will execute
return nil // you can return a value back to the JS caller if required
})
global := v8.NewObjectTemplate(iso) // a template that represents a JS Object
global.Set("print", printfn) // sets the "print" property of the Object to our function
ctx := v8.NewContext(iso, global) // new Context with the global Object set to our object template
ctx.RunScript("print('foo')", "print.js") // will execute the Go callback with a single argunent 'foo'
Update a JavaScript object from Go
ctx := v8.NewContext() // new context with a default VM
obj := ctx.Global() // get the global object from the context
obj.Set("version", "v1.0.0") // set the property "version" on the object
val, _ := ctx.RunScript("version", "version.js") // global object will have the property set within the JS VM
fmt.Printf("version: %s", val)
if obj.Has("version") { // check if a property exists on the object
obj.Delete("version") // remove the property from the object
}
JavaScript errors
val, err := ctx.RunScript(src, filename)
if err != nil {
e := err.(*v8.JSError) // JavaScript errors will be returned as the JSError struct
fmt.Println(e.Message) // the message of the exception thrown
fmt.Println(e.Location) // the filename, line number and the column where the error occurred
fmt.Println(e.StackTrace) // the full stack trace of the error, if available
fmt.Printf("javascript error: %v", e) // will format the standard error message
fmt.Printf("javascript stack trace: %+v", e) // will format the full error stack trace
}
Pre-compile context-independent scripts to speed-up execution times
For scripts that are large or are repeatedly run in different contexts, it is beneficial to compile the script once and used the cached data from that compilation to avoid recompiling every time you want to run it.
source := "const multiply = (a, b) => a * b"
iso1 := v8.NewIsolate() // creates a new JavaScript VM
ctx1 := v8.NewContext(iso1) // new context within the VM
script1, _ := iso1.CompileUnboundScript(source, "math.js", v8.CompileOptions{}) // compile script to get cached data
val, _ := script1.Run(ctx1)
cachedData := script1.CreateCodeCache()
iso2 := v8.NewIsolate() // create a new JavaScript VM
ctx2 := v8.NewContext(iso2) // new context within the VM
script2, _ := iso2.CompileUnboundScript(source, "math.js", v8.CompileOptions{CachedData: cachedData}) // compile script in new isolate with cached data
val, _ = script2.Run(ctx2)
Terminate long running scripts
vals := make(chan *v8.Value, 1)
errs := make(chan error, 1)
go func() {
val, err := ctx.RunScript(script, "forever.js") // exec a long running script
if err != nil {
errs <- err
return
}
vals <- val
}()
select {
case val := <- vals:
// success
case err := <- errs:
// javascript error
case <- time.After(200 * time.Milliseconds):
vm := ctx.Isolate() // get the Isolate from the context
vm.TerminateExecution() // terminate the execution
err := <- errs // will get a termination error back from the running script
}
CPU Profiler
func createProfile() {
iso := v8.NewIsolate()
ctx := v8.NewContext(iso)
cpuProfiler := v8.NewCPUProfiler(iso)
cpuProfiler.StartProfiling("my-profile")
ctx.RunScript(profileScript, "script.js") # this script is defined in cpuprofiler_test.go
val, _ := ctx.Global().Get("start")
fn, _ := val.AsFunction()
fn.Call(ctx.Global())
cpuProfile := cpuProfiler.StopProfiling("my-profile")
printTree("", cpuProfile.GetTopDownRoot()) # helper function to print the profile
}
func printTree(nest string, node *v8.CPUProfileNode) {
fmt.Printf("%s%s %s:%d:%d\n", nest, node.GetFunctionName(), node.GetScriptResourceName(), node.GetLineNumber(), node.GetColumnNumber())
count := node.GetChildrenCount()
if count == 0 {
return
}
nest = fmt.Sprintf("%s ", nest)
for i := 0; i < count; i++ {
printTree(nest, node.GetChild(i))
}
}
// Output
// (root) :0:0
// (program) :0:0
// start script.js:23:15
// foo script.js:15:13
// delay script.js:12:15
// loop script.js:1:14
// bar script.js:13:13
// delay script.js:12:15
// loop script.js:1:14
// baz script.js:14:13
// delay script.js:12:15
// loop script.js:1:14
// (garbage collector) :0:0
Documentation
Go Reference & more examples: https://pkg.go.dev/github.com/tommie/v8go
Support
If you would like to ask questions about this library or want to keep up-to-date with the latest changes and releases, please join the #v8go channel on Gophers Slack. Click here to join the Gophers Slack community!
Windows
There used to be Windows binary support. For further information see, rogchap PR #234.
The v8go library would welcome contributions from anyone able to get an external windows
build of the V8 library linking with v8go, using the version of V8 checked out in the
deps/v8 git submodule, and documentation of the process involved. This process will likely
involve passing a linker flag when building v8go (e.g. using the CGO_LDFLAGS environment
variable.
V8 Dependency
See deps/v8/ for the version of V8 we're currently on.
In order to make v8go usable as a standard Go package, prebuilt static libraries of V8 are included for Linux and macOS. you should not require to build V8 yourself.
Project Goals
To provide a high quality, idiomatic, Go binding to the V8 C++ API.
The API should match the original API as closely as possible, but with an API that Gophers (Go enthusiasts) expect. For example: using multiple return values to return both result and error from a function, rather than throwing an exception.
This project also aims to keep up-to-date with the latest (stable) release of V8.
License
[