Documentation ¶
Overview ¶
Package v8 provides a Go API for the the V8 javascript engine.
This allows running javascript within a go executable. The bindings have been tested with v8 builds between 5.1.281.16 through 6.7.77.
V8 provides two main concepts for managing javascript state: Isolates and Contexts. An isolate represents a single-threaded javascript engine that can manage one or more contexts. A context is a sandboxed javascript execution environment.
Thus, if you have one isolate, you could safely execute independent code in many different contexts created in that isolate. The code in the various contexts would not interfere with each other, however no more than one context would ever be executing at a given time.
If you have multiple isolates, they may be executing in separate threads simultaneously.
This work is based off of several existing libraries:
Example ¶
package main import ( "fmt" "github.com/augustoroman/v8" ) func main() { // Easy-peasy to create a new VM: ctx := v8.NewIsolate().NewContext() // You can load your js from a file, create it dynamically, whatever. ctx.Eval(` // This is javascript code! add = (a,b)=>{ return a + b }; // whoa, ES6 arrow functions. `, "add.js") // <-- supply filenames for stack traces // State accumulates in a context. Add still exists. // The last statements' value is returned to Go. res, _ := ctx.Eval(`add(3,4)`, "compute.js") // don't ignore errors! fmt.Println("add(3,4) =", res.String()) // I hope it's 7. // You can also bind Go functions to javascript: product := func(in v8.CallbackArgs) (*v8.Value, error) { var result float64 = 1 for _, arg := range in.Args { result *= arg.Float64() } return in.Context.Create(result) // ctx.Create is great for mapping Go -> JS. } cnt := ctx.Bind("product_function", product) ctx.Global().Set("product", cnt) res, _ = ctx.Eval(` // Now we can call that function in JS product(1,2,3,4,5) `, "compute2.js") fmt.Println("product(1,2,3,4,5) =", res.Int64()) _, err := ctx.Eval(` // Sometimes there's a mistake in your js code: functin broken(a,b) { return a+b; } `, "ooops.js") fmt.Println("Err:", err) // <-- get nice error messages }
Output: add(3,4) = 7 product(1,2,3,4,5) = 120 Err: Uncaught exception: SyntaxError: Unexpected identifier at ooops.js:3:20 functin broken(a,b) { return a+b; } ^^^^^^ Stack trace: SyntaxError: Unexpected identifier
Example (Microtasks) ¶
package main import ( "fmt" "github.com/augustoroman/v8" ) func main() { // Microtasks are automatically run when the Eval'd js code has finished but // before Eval returns. ctx := v8.NewIsolate().NewContext() // Register a simple log function in js. ctx.Global().Set("log", ctx.Bind("log", func(in v8.CallbackArgs) (*v8.Value, error) { fmt.Println("log>", in.Arg(0).String()) return nil, nil })) // Run some javascript that schedules microtasks, like promises. output, err := ctx.Eval(` log('start'); let p = new Promise(resolve => { // this is called immediately log('resolve:5'); resolve(5); }); log('promise created'); p.then(v => log('then:'+v)); // this is scheduled in a microtask log('done'); // this is run before the microtask 'xyz' // this is the output of the script `, "microtasks.js") fmt.Println("output:", output) fmt.Println("err:", err) }
Output: log> start log> resolve:5 log> promise created log> done log> then:5 output: xyz err: <nil>
Index ¶
- Variables
- type Callback
- type CallbackArgs
- type Context
- func (ctx *Context) Bind(name string, cb Callback) *Value
- func (ctx *Context) Create(val interface{}) (*Value, error)
- func (ctx *Context) Eval(jsCode, filename string) (*Value, error)
- func (ctx *Context) Global() *Value
- func (ctx *Context) ParseJson(json string) (*Value, error)
- func (ctx *Context) Terminate()
- type HeapStatistics
- type Isolate
- type Kind
- type Loc
- type PromiseState
- type Snapshot
- type Value
- func (v *Value) Bool() bool
- func (v *Value) Bytes() []byte
- func (v *Value) Call(this *Value, args ...*Value) (*Value, error)
- func (v *Value) Date() (time.Time, error)
- func (v *Value) Float64() float64
- func (v *Value) Get(name string) (*Value, error)
- func (v *Value) GetIndex(idx int) (*Value, error)
- func (v *Value) Int64() int64
- func (v *Value) IsKind(k Kind) bool
- func (v *Value) MarshalJSON() ([]byte, error)
- func (v *Value) New(args ...*Value) (*Value, error)
- func (v *Value) PromiseInfo() (PromiseState, *Value, error)
- func (v *Value) Set(name string, value *Value) error
- func (v *Value) SetIndex(idx int, value *Value) error
- func (v *Value) String() string
- Bugs
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var Version = struct{ Major, Minor, Build, Patch int }{ Major: int(C.version.Major), Minor: int(C.version.Minor), Build: int(C.version.Build), Patch: int(C.version.Patch), }
Version exposes the compiled-in version of the linked V8 library. This can be used to test for specific javascript functionality support (e.g. ES6 destructuring isn't supported before major version 5.).
Functions ¶
This section is empty.
Types ¶
type Callback ¶
type Callback func(CallbackArgs) (*Value, error)
Callback is the signature for callback functions that are registered with a V8 context via Bind(). Never return a Value from a different V8 isolate. A return value of nil will return "undefined" to javascript. Returning an error will throw an exception. Panics are caught and returned as errors to avoid disrupting the cgo stack.
type CallbackArgs ¶
CallbackArgs provide the context for handling a javascript callback into go. Caller is the script location that javascript is calling from. If the function is called directly from Go (e.g. via Call()), then "Caller" will be empty. Args are the arguments provided by the JS code. Context is the V8 context that initiated the call.
func (*CallbackArgs) Arg ¶
func (c *CallbackArgs) Arg(n int) *Value
Arg returns the specified argument or "undefined" if it doesn't exist.
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
Context is a sandboxed js environment with its own set of built-in objects and functions. Values and javascript operations within a context are visible only within that context unless the Go code explicitly moves values from one context to another.
func (*Context) Bind ¶
Bind creates a V8 function value that calls a Go function when invoked. This value is created but NOT visible in the Context until it is explicitly passed to the Context (either via a .Set() call or as a callback return value).
The name that is provided is the name of the defined javascript function, and generally doesn't affect anything. That is, for a call such as:
val, _ = ctx.Bind("my_func_name", callback)
then val is a function object in javascript, so calling val.String() (or calling .toString() on the object within the JS VM) would result in:
function my_func_name() { [native code] }
NOTE: Once registered, a callback function will be stored in the Context until it is GC'd, so each Bind for a given context will take up a little more memory each time. Normally this isn't a problem, but many many Bind's on a Context can gradually consume memory.
Example ¶
package main import ( "fmt" "strconv" "github.com/augustoroman/v8" ) // AddAllNumbers is the callback function that we'll make accessible the JS VM. // It will accept 2 or more numbers and return the sum. If fewer than two args // are passed or any of the args are not parsable as numbers, it will fail. func AddAllNumbers(in v8.CallbackArgs) (*v8.Value, error) { if len(in.Args) < 2 { return nil, fmt.Errorf("add requires at least 2 numbers, but got %d args", len(in.Args)) } result := 0.0 for i, arg := range in.Args { n, err := strconv.ParseFloat(arg.String(), 64) if err != nil { return nil, fmt.Errorf("Arg %d [%q] cannot be parsed as a number: %v", i, arg.String(), err) } result += n } return in.Context.Create(result) } func main() { ctx := v8.NewIsolate().NewContext() // First, we'll bind our callback function into a *v8.Value that we can // use as we please. The string "my_add_function" here is the used by V8 as // the name of the function. That is, we've defined: // val.toString() = (function my_add_function() { [native code] }); // However the name "my_add_function" isn't actually accessible in the V8 // global scope anywhere yet. val := ctx.Bind("my_add_function", AddAllNumbers) // Next we'll set that value into the global context to make it available to // the JS. if err := ctx.Global().Set("add", val); err != nil { panic(err) } // Now we'll call it! result, err := ctx.Eval(`add(1,2,3,4,5)`, `example.js`) if err != nil { panic(err) } fmt.Println(`add(1,2,3,4,5) =`, result) }
Output: add(1,2,3,4,5) = 15
func (*Context) Create ¶
Create maps Go values into corresponding JavaScript values. This value is created but NOT visible in the Context until it is explicitly passed to the Context (either via a .Set() call or as a callback return value).
Create can automatically map the following types of values:
- bool
- all integers and floats are mapped to JS numbers (float64)
- strings
- maps (keys must be strings, values must be convertible)
- time.Time values (converted to js Date object)
- structs (exported field values must be convertible)
- slices of convertible types
- pointers to any convertible field
- v8.Callback function (automatically bind'd)
- *v8.Value (returned as-is)
Any nil pointers are converted to undefined in JS.
Values for elements in maps, structs, and slices may be any of the above types.
When structs are being converted, any fields with json struct tags will respect the json naming entry. For example:
var x = struct { Ignored string `json:"-"` Renamed string `json:"foo"` DefaultName string `json:",omitempty"` Bar string }{"a", "b", "c", "d"}
will be converted as:
{ foo: "a", DefaultName: "b", Bar: "c", }
Also, embedded structs (or pointers-to-structs) will get inlined.
Byte slices tagged as 'v8:"arraybuffer"' will be converted into a javascript ArrayBuffer object for more efficient conversion. For example:
var y = struct { Buf []byte `v8:"arraybuffer"` }{[]byte{1,2,3}}
will be converted as
{ Buf: new Uint8Array([1,2,3]).buffer }
Example (Basic) ¶
package main import ( "fmt" "github.com/augustoroman/v8" ) func main() { ctx := v8.NewIsolate().NewContext() type Info struct{ Name, Email string } val, _ := ctx.Create(map[string]interface{}{ "num": 3.7, "str": "simple string", "bool": true, "struct": Info{"foo", "bar"}, "list": []int{1, 2, 3}, }) // val is now a *v8.Value that is associated with ctx but not yet accessible // from the javascript scope. _ = ctx.Global().Set("created_value", val) res, _ := ctx.Eval(` created_value.struct.Name = 'John'; JSON.stringify(created_value.struct) `, `test.js`) fmt.Println(res) }
Output: {"Name":"John","Email":"bar"}
Example (Callbacks) ¶
package main import ( "fmt" "github.com/augustoroman/v8" ) func main() { ctx := v8.NewIsolate().NewContext() // A typical use of Create is to return values from callbacks: var nextId int getNextIdCallback := func(in v8.CallbackArgs) (*v8.Value, error) { nextId++ return ctx.Create(nextId) // Return the created corresponding v8.Value or an error. } // Because Create will use reflection to map a Go value to a JS object, it // can also be used to easily bind a complex object into the JS VM. resetIdsCallback := func(in v8.CallbackArgs) (*v8.Value, error) { nextId = 0 return nil, nil } myIdAPI, _ := ctx.Create(map[string]interface{}{ "next": getNextIdCallback, "reset": resetIdsCallback, // Can also include other stuff: "my_api_version": "v1.2", }) // now let's use those two callbacks and the api value: _ = ctx.Global().Set("ids", myIdAPI) var res *v8.Value res, _ = ctx.Eval(`ids.my_api_version`, `test.js`) fmt.Println(`ids.my_api_version =`, res) res, _ = ctx.Eval(`ids.next()`, `test.js`) fmt.Println(`ids.next() =`, res) res, _ = ctx.Eval(`ids.next()`, `test.js`) fmt.Println(`ids.next() =`, res) res, _ = ctx.Eval(`ids.reset(); ids.next()`, `test.js`) fmt.Println(`ids.reset()`) fmt.Println(`ids.next() =`, res) }
Output: ids.my_api_version = v1.2 ids.next() = 1 ids.next() = 2 ids.reset() ids.next() = 1
func (*Context) Eval ¶
Eval runs the javascript code in the VM. The filename parameter is informational only -- it is shown in javascript stack traces.
func (*Context) Global ¶
Global returns the JS global object for this context, with properties like Object, Array, JSON, etc.
type HeapStatistics ¶
type HeapStatistics struct { TotalHeapSize uint64 TotalHeapSizeExecutable uint64 TotalPhysicalSize uint64 TotalAvailableSize uint64 UsedHeapSize uint64 HeapSizeLimit uint64 MallocedMemory uint64 PeakMallocedMemory uint64 DoesZapGarbage bool }
HeapStatistics represent v8::HeapStatistics which are statistics about the heap memory usage.
type Isolate ¶
type Isolate struct {
// contains filtered or unexported fields
}
Isolate represents a single-threaded V8 engine instance. It can run multiple independent Contexts and V8 values can be freely shared between the Contexts, however only one context will ever execute at a time.
func NewIsolateWithSnapshot ¶
NewIsolateWithSnapshot creates a new V8 Isolate using the supplied Snapshot to initialize all Contexts created from this Isolate.
func (*Isolate) GetHeapStatistics ¶
func (i *Isolate) GetHeapStatistics() HeapStatistics
GetHeapStatistics gets statistics about the heap memory usage.
func (*Isolate) NewContext ¶
NewContext creates a new, clean V8 Context within this Isolate.
func (*Isolate) SendLowMemoryNotification ¶
func (i *Isolate) SendLowMemoryNotification()
SendLowMemoryNotification sends an optional notification that the system is running low on memory. V8 uses these notifications to attempt to free memory.
type Kind ¶
type Kind uint8
Kind is an underlying V8 representation of a *Value. Javascript values may have multiple underyling kinds. For example, a function will be both KindObject and KindFunction.
const ( KindUndefined Kind = iota KindNull KindName KindString KindSymbol KindFunction KindArray KindObject KindBoolean KindNumber KindExternal KindInt32 KindUint32 KindDate KindArgumentsObject KindBooleanObject KindNumberObject KindStringObject KindSymbolObject KindNativeError KindRegExp KindAsyncFunction KindGeneratorFunction KindGeneratorObject KindPromise KindMap KindSet KindMapIterator KindSetIterator KindWeakMap KindWeakSet KindArrayBuffer KindArrayBufferView KindTypedArray KindUint8Array KindUint8ClampedArray KindInt8Array KindUint16Array KindInt16Array KindUint32Array KindInt32Array KindFloat32Array KindFloat64Array KindDataView KindProxy KindWebAssemblyCompiledModule )
type PromiseState ¶
type PromiseState uint8
PromiseState defines the state of a promise: either pending, resolved, or rejected. Promises that are pending have no result value yet. A promise that is resolved has a result value, and a promise that is rejected has a result value that is usually the error.
const ( PromiseStatePending PromiseState = iota PromiseStateResolved PromiseStateRejected )
func (PromiseState) String ¶
func (s PromiseState) String() string
type Snapshot ¶
type Snapshot struct {
// contains filtered or unexported fields
}
Snapshot contains the stored VM state that can be used to quickly recreate a new VM at that particular state.
Example ¶
package main import ( "fmt" "github.com/augustoroman/v8" ) func main() { snapshot := v8.CreateSnapshot(` // Concantenate all the scripts you want at startup, e.g. lodash, etc. _ = { map: function() { /* ... */ }, etc: "etc, etc..." }; // Setup my per-context global state: myGlobalState = { init: function() { this.initialized = true; }, foo: 3, }; // Run some functions: myGlobalState.init(); `) iso := v8.NewIsolateWithSnapshot(snapshot) // Create a context with the state from the snapshot: ctx1 := iso.NewContext() fmt.Println("Context 1:") val, _ := ctx1.Eval("myGlobalState.foo = 37; myGlobalState.initialized", "") fmt.Println("myGlobalState.initialized:", val) val, _ = ctx1.Eval("myGlobalState.foo", "") fmt.Println("myGlobalState.foo:", val) // In the second context, the global state is reset to the state at the // snapshot: ctx2 := iso.NewContext() fmt.Println("Context 2:") val, _ = ctx2.Eval("myGlobalState.foo", "") fmt.Println("myGlobalState.foo:", val) }
Output: Context 1: myGlobalState.initialized: true myGlobalState.foo: 37 Context 2: myGlobalState.foo: 3
func CreateSnapshot ¶
CreateSnapshot creates a new Snapshot after running the supplied JS code. Because Snapshots cannot have refences to external code (no Go callbacks), all of the initialization code must be pure JS and supplied at once as the arg to this function.
func RestoreSnapshotFromExport ¶
RestoreSnapshotFromExport creates a Snapshot from a byte slice that should have previous come from Snapshot.Export().
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value represents a handle to a value within the javascript VM. Values are associated with a particular Context, but may be passed freely between Contexts within an Isolate.
func (*Value) Bool ¶
Bool returns this Value as a boolean. If the underlying value is not a boolean, it will be coerced to a boolean using Javascript's coercion rules.
func (*Value) Bytes ¶
Bytes returns a byte slice extracted from this value when the value is of type ArrayBuffer. The returned byte slice is copied from the underlying buffer, so modifying it will not be reflected in the VM. Values of other types return nil.
func (*Value) Call ¶
Call this value as a function. If this value is not a function, this will fail.
func (*Value) Date ¶
Date returns this Value as a time.Time. If the underlying value is not a KindDate, this will return an error.
func (*Value) Float64 ¶
Float64 returns this Value as a float64. If this value is not a number, then NaN will be returned.
func (*Value) GetIndex ¶
Get the value at the specified index. If this value is not an object or an array, this will fail.
func (*Value) Int64 ¶
Int64 returns this Value as an int64. If this value is not a number, then 0 will be returned.
func (*Value) IsKind ¶
IsKind will test whether the underlying value is the specified JS kind. The kind of a value is set when the value is created and will not change.
func (*Value) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface using the JSON.stringify function from the VM to serialize the value and fails if that cannot be found.
Note that JSON.stringify will ignore function values. For example, this JS object:
{ foo: function() { return "x" }, bar: 3 }
will serialize to this:
{"bar":3}
func (*Value) New ¶
New creates a new instance of an object using this value as its constructor. If this value is not a function, this will fail.
func (*Value) PromiseInfo ¶
func (v *Value) PromiseInfo() (PromiseState, *Value, error)
PromiseInfo will return information about the promise if this value's underlying kind is KindPromise, otherwise it will return an error. If there is no error, then the returned value will depend on the promise state:
pending: nil fulfilled: the value of the promise rejected: the rejected result, usually a JS error
Notes ¶
Bugs ¶
Unhandled promise rejections are silently dropped (see https://github.com/augustoroman/v8/issues/21)
Directories ¶
Path | Synopsis |
---|---|
cmd
|
|
v8-runjs
v8-runjs is a command-line tool to run javascript.
|
v8-runjs is a command-line tool to run javascript. |
Package v8console provides a simple console implementation to allow JS to log messages.
|
Package v8console provides a simple console implementation to allow JS to log messages. |