SoFunction
Updated on 2025-03-10

Example of implementation of running JavaScript with Golang

C++ is too troublesome (hard), and it is really difficult to get V8, but the Golang community has released several Javascript engines. It is a good choice to try how to integrate Javascript in other languages. The following is selected /dop251/goja as an example.

Hello world

Follow the warehouse Readme, let’s take one:

package main

import (
 "fmt"
 js "/dop251/goja"
)

func main() {
 vm := () // Create an engine instance r, _ := (`
  1 + 1
 `) // Execute javascript code v, _ : = ().(int64) // Convert the execution result to the corresponding type of Golang (r)
}

This example shows the most basic ability, given a piece of Javascript code text, it can execute a result and get the representation of the host language that 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 some global functions for Javascript use, creating an object, etc.; Golang reads some context from the Javascript engine, such as the calculation results of a calculation process. Let’s look at the first category first.

A common method is to register a variable globally through the Set method provided by the Runtime type, for example

...
rts := ()
("x", 2)
(`x+x`) // 4
...

The method signature of Set here is func (r *Runtime) Set(name string, value interface{}). For basic types, no additional wrapping is required and can be automatically converted, but when you need to pass a complex object, you need to wrap it with NewObject:

rts := ()
o := ()
("x", 2)
("o", o)
(`+`) // 4

Switching to Golang's perspective is a very natural process. If you want to create an object, you need to create a corresponding expression in Golang and then use it in Javascript. For more complex objects, just nest.

Defining functions is different. The difference is that the representation of functions in Javascript in Golang is different from other types of values. The signature of the function in the top Javascript in Golang is: func (), which contains the context information of the calling function. Based on this, we can try to add a capability to Javascript:

...
func log(call )  {
  str := (0)
  (())
  return str
}
...
rts := ()
console := ()
("log", log)
("console", console)
(`('hello world')`) // hello world

Compared to injecting some information into the Javascript engine and reading information from it is simpler, the previous hello world shows a method to execute a piece of Javascript code and then get a result. However, this method is not flexible enough. If you want to get the value of a certain context accurately, it is not so convenient. To this end, goja provides a Get method. The Runtime type Get method can read information of a certain variable from Runtime, and the Object type Get method can read the value of a certain field from the object. The signature is as follows: func (r *Runtime) Get(name string) Value, func (o *Object) Get(name string) Value. However, the types of the obtained values ​​are all Value types. If you want to convert them to the corresponding type, you need to use some methods to convert them. I will not go into details here. If you are interested, you can read its documentation.

A more complex example

Goja values ​​provide basic ability to parse and execute Javascript code, but the common capabilities provided by our hosts need to be supplemented by ourselves during use. Based on the above techniques, the following provides a simple requirement to load local Javascript code.

Loading a piece of Commjs format Javascript code through requires, an intuitive process: read text according to the file name, assemble it into an immediate execution function, execute it, and then return the module object, but some small optimizations can be made in the middle, such as if the code has been loaded, it will not be reloaded, executed, and just return. The rough implementation is as follows:

package core

import (
  "io/ioutil"
  "path/filepath"

  js "/dop251/goja"
)

func moduleTemplate(c string) string {
  return "(function(module, exports) {" + c + "\n})"
}

func createModule(c *Core) * {
  r := ()
  m := ()
  e := ()
  ("exports", e)

  return m
}

func compileModule(p string) * {
  code, _ := (p)
  text := moduleTemplate(string(code))
  prg, _ := (p, text, false)

  return prg
}

func loadModule(c *Core, p string)  {
  p = (p)
  pkg := [p]
  if pkg != nil {
    return pkg
  }

  prg := compileModule(p)

  r := ()
  f, _ := (prg)
  g, _ := (f)

  m := createModule(c)
  jsExports := ("exports")
  g(jsExports, m, jsExports)

  return ("exports")
}
To enable the engine to use this capability,It needs to berequireThis function is registered withRuntimemiddle,

// RegisterLoader register a simple commonjs style loader to runtime
func RegisterLoader(c *Core) {
  r := ()

  ("require", func(call )  {
    p := (0).String()
    return loadModule(c, p)
  })
}

If you are interested in the complete example, please see/81120/gode

Write it later

I have been unable to distinguish the boundaries between the Javascript engine and the Javascript execution environment before. Through this example, I have gained a very specific understanding. Moreover, we have a clearer understanding of the structure of Node itself. In some scenarios, some languages ​​need to be embedded into another language to achieve some more flexible functions and decoupling, such as lua in nginx, lua in game engine, Javascipt in mongodb shell, and even nginx official headers provide a castrated version of Javascript implementation as configured DSL. So in this scenario where DSL is needed, embedding an execution engine in a mature language is much simpler and more convenient than implementing a DSL by itself. Moreover, in various scenarios, the requirements for the language itself are also different. For example, in edge computing scenarios, Javascript can be developed in embedded mode, but does it require a complete V8? In scenarios where there are special requirements for the environment and performance, it is also a good idea to limit DSL and provide necessary host language expansion.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.