C++ is too cumbersome to mess around with V8, but the Golang community has several Javascript engines that are a good place to experiment with how to integrate Javascript into other languages. github.com/dop251/goja is selected as an example.
Hello world
Follow the Readme from the warehouse:
package main
import (
"fmt"
js "github.com/dop251/goja"
)
func main() {vm := js.new () // Create engine instance r, _ := vm.RunString(' 1 + 1 ') // Execute javascript code v, _ : = r.xport ().(int64) // Convert the result of the execution to the corresponding type of Golang fmt.println (r)}Copy the code
This example demonstrates the basic ability to execute a result given a piece of Javascript code text, and to get a representation of the host language in which the result is executed.
interaction
The interaction between Javascript and Golang is divided into two aspects: Golang injects some context into the Javascript engine, such as registering global functions for Javascript to use, creating an object, etc. Golang reads some context from the Javascript engine, such as the results of a computation. Let’s look at the first category.
A common approach is to register a variable globally through the Set method provided by the Runtime type, for example
. rts := js.New() rts.Set("x", 2)
rts.RunString(`x+x`) // 4
...Copy the code
Func (r *Runtime) Set(name string, value interface{}); We need to wrap it with NewObject:
rts := js.New()
o := rts.NewObject()
o.Set("x", 2)
rts.Set("o", o)
rts.RunString(`o.x+o.x`) // 4Copy the code
Switching to Golang’s perspective is a natural process. To create an object, you need to create a corresponding representation in Golang before you can use it in Javascript. For more complex objects, nesting is fine.
Defining functions is different. The difference is that Javascript functions are represented differently in Golang than other types of values. The signature of a table Javascript function in Golang is: Func (js.FunctionCall) js.Value, js.FunctionCall contains the context information for calling the function, so we can try to add a console.log capability to Javascript:
. funclog(call js.FunctionCall) js.Value {
str := call.Argument(0)
fmt.Print(str.String())
return str
}
...
rts := js.New()
console := rts.NewObject()
console.Set("log".log)
rts.Set("console", console)
rts.RunString(`console.log('hello world')`) // hello worldCopy the code
Reading information from a Javascript engine is easier than injecting it into a Javascript engine, as shown earlier in Hello World, where you execute a piece of Javascript code and get a result. But this method is not flexible enough, if you want to get the exact value of a context variable, it is not so convenient. To do this, GoJA provides Get methods. A Get of type Runtime reads information about a variable from the Runtime, and a Get of type Object reads the value of a field from an Object. Func (r *Runtime) Get(name string) Value, func (O *Object) Get(name string) Value. However, the Value obtained is of Value type. If you want to convert the Value to the corresponding type, you need to use some methods to convert the Value, which will not be described here. If you are interested, you can refer to its documentation.
A more complicated example
The GOJA value provides the basic ability to parse and execute Javascript code, but the capabilities provided by our common hosts need to be replenished as they go along. The following provides the ability to load native Javascript code with a simple require based on the above tips.
Load a section of Commjs Javascript code by require, intuitive flow: Based on the file name, read the text, assemble it into a function that executes immediately, executes, and returns the Module object, but you can make some minor optimizations in between, like if the code has already been loaded, you don’t reload it, execute it, and just return it. The approximate implementation is as follows:
package core
import (
"io/ioutil"
"path/filepath"
js "github.com/dop251/goja"
)
func moduleTemplate(c string) string {
return "(function(module, exports) {" + c + "\n})"
}
func createModule(c *Core) *js.Object {
r := c.GetRts()
m := r.NewObject()
e := r.NewObject()
m.Set("exports", e)
return m
}
func compileModule(p string) *js.Program {
code, _ := ioutil.ReadFile(p)
text := moduleTemplate(string(code))
prg, _ := js.Compile(p, text, false)
return prg
}
func loadModule(c *Core, p string) js.Value {
p = filepath.Clean(p)
pkg := c.Pkg[p]
ifpkg ! = nil {return pkg
}
prg := compileModule(p)
r := c.GetRts()
f, _ := r.RunProgram(prg)
g, _ := js.AssertFunction(f)
m := createModule(c)
jsExports := m.Get("exports")
g(jsExports, m, jsExports)
return m.Get("exports")}Copy the code
To enable the engine to use this capability, you need to register the require function with the Runtime,
// RegisterLoader register a simple commonjs style loader to runtime
func RegisterLoader(c *Core) {
r := c.GetRts()
r.Set("require", func(call js.FunctionCall) js.Value {
p := call.Argument(0).String()
return loadModule(c, p)
})
}Copy the code
The full example can be found at github.com/81120/gode
Write in the back
The distinction between a Javascript engine and a Javascript execution environment has been unclear until now. Also, you get a better understanding of the structure of Node itself. In some scenarios, languages need to be embedded in another language for more flexible decoupling, such as Lua in Nginx, Lua in game engines, Javascipt in mongodb shell, Even the nginx official header provides a castrated version of Javascript implementation as a configured DSL. In this scenario, embedding a mature language execution engine is much easier than implementing a DSL yourself. Also, the requirements for the language itself vary from scenario to scenario. For example, for edge computing scenarios, Javascript can be used for embedded development, but is a full V8 required? It is also a good idea to limit DSLS and provide the necessary host language extensions for scenarios with specific environmental and performance requirements.