...

Text file src/github.com/dop251/goja/README.md

Documentation: github.com/dop251/goja

     1goja
     2====
     3
     4ECMAScript 5.1(+) implementation in Go.
     5
     6[![Go Reference](https://pkg.go.dev/badge/github.com/dop251/goja.svg)](https://pkg.go.dev/github.com/dop251/goja)
     7
     8Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and
     9performance.
    10
    11This project was largely inspired by [otto](https://github.com/robertkrimen/otto).
    12
    13Minimum required Go version is 1.16.
    14
    15Features
    16--------
    17
    18 * Full ECMAScript 5.1 support (including regex and strict mode).
    19 * Passes nearly all [tc39 tests](https://github.com/tc39/test262) for the features implemented so far. The goal is to
    20   pass all of them. See .tc39_test262_checkout.sh for the latest working commit id.
    21 * Capable of running Babel, Typescript compiler and pretty much anything written in ES5.
    22 * Sourcemaps.
    23 * Most of ES6 functionality, still work in progress, see https://github.com/dop251/goja/milestone/1?closed=1
    24
    25Known incompatibilities and caveats
    26-----------------------------------
    27
    28### WeakMap
    29WeakMap is implemented by embedding references to the values into the keys. This means that as long
    30as the key is reachable all values associated with it in any weak maps also remain reachable and therefore
    31cannot be garbage collected even if they are not otherwise referenced, even after the WeakMap is gone.
    32The reference to the value is dropped either when the key is explicitly removed from the WeakMap or when the
    33key becomes unreachable.
    34
    35To illustrate this:
    36
    37```javascript
    38var m = new WeakMap();
    39var key = {};
    40var value = {/* a very large object */};
    41m.set(key, value);
    42value = undefined;
    43m = undefined; // The value does NOT become garbage-collectable at this point
    44key = undefined; // Now it does
    45// m.delete(key); // This would work too
    46```
    47
    48The reason for it is the limitation of the Go runtime. At the time of writing (version 1.15) having a finalizer
    49set on an object which is part of a reference cycle makes the whole cycle non-garbage-collectable. The solution
    50above is the only reasonable way I can think of without involving finalizers. This is the third attempt
    51(see https://github.com/dop251/goja/issues/250 and https://github.com/dop251/goja/issues/199 for more details).
    52
    53Note, this does not have any effect on the application logic, but may cause a higher-than-expected memory usage.
    54
    55### WeakRef and FinalizationRegistry
    56For the reason mentioned above implementing WeakRef and FinalizationRegistry does not seem to be possible at this stage.
    57
    58### JSON
    59`JSON.parse()` uses the standard Go library which operates in UTF-8. Therefore, it cannot correctly parse broken UTF-16
    60surrogate pairs, for example:
    61
    62```javascript
    63JSON.parse(`"\\uD800"`).charCodeAt(0).toString(16) // returns "fffd" instead of "d800"
    64```
    65
    66### Date
    67Conversion from calendar date to epoch timestamp uses the standard Go library which uses `int`, rather than `float` as per
    68ECMAScript specification. This means if you pass arguments that overflow int to the `Date()` constructor or  if there is
    69an integer overflow, the result will be incorrect, for example:
    70
    71```javascript
    72Date.UTC(1970, 0, 1, 80063993375, 29, 1, -288230376151711740) // returns 29256 instead of 29312
    73```
    74
    75FAQ
    76---
    77
    78### How fast is it?
    79
    80Although it's faster than many scripting language implementations in Go I have seen
    81(for example it's 6-7 times faster than otto on average) it is not a
    82replacement for V8 or SpiderMonkey or any other general-purpose JavaScript engine.
    83You can find some benchmarks [here](https://github.com/dop251/goja/issues/2).
    84
    85### Why would I want to use it over a V8 wrapper?
    86
    87It greatly depends on your usage scenario. If most of the work is done in javascript
    88(for example crypto or any other heavy calculations) you are definitely better off with V8.
    89
    90If you need a scripting language that drives an engine written in Go so that
    91you need to make frequent calls between Go and javascript passing complex data structures
    92then the cgo overhead may outweigh the benefits of having a faster javascript engine.
    93
    94Because it's written in pure Go there are no cgo dependencies, it's very easy to build and it
    95should run on any platform supported by Go.
    96
    97It gives you a much better control over execution environment so can be useful for research.
    98
    99### Is it goroutine-safe?
   100
   101No. An instance of goja.Runtime can only be used by a single goroutine
   102at a time. You can create as many instances of Runtime as you like but
   103it's not possible to pass object values between runtimes.
   104
   105### Where is setTimeout()?
   106
   107setTimeout() assumes concurrent execution of code which requires an execution
   108environment, for example an event loop similar to nodejs or a browser.
   109There is a [separate project](https://github.com/dop251/goja_nodejs) aimed at providing some NodeJS functionality,
   110and it includes an event loop.
   111
   112### Can you implement (feature X from ES6 or higher)?
   113
   114I will be adding features in their dependency order and as quickly as time permits. Please do not ask
   115for ETAs. Features that are open in the [milestone](https://github.com/dop251/goja/milestone/1) are either in progress
   116or will be worked on next.
   117
   118The ongoing work is done in separate feature branches which are merged into master when appropriate.
   119Every commit in these branches represents a relatively stable state (i.e. it compiles and passes all enabled tc39 tests),
   120however because the version of tc39 tests I use is quite old, it may be not as well tested as the ES5.1 functionality. Because there are (usually) no major breaking changes between ECMAScript revisions
   121it should not break your existing code. You are encouraged to give it a try and report any bugs found. Please do not submit fixes though without discussing it first, as the code could be changed in the meantime.
   122
   123### How do I contribute?
   124
   125Before submitting a pull request please make sure that:
   126
   127- You followed ECMA standard as close as possible. If adding a new feature make sure you've read the specification,
   128do not just base it on a couple of examples that work fine.
   129- Your change does not have a significant negative impact on performance (unless it's a bugfix and it's unavoidable)
   130- It passes all relevant tc39 tests.
   131
   132Current Status
   133--------------
   134
   135 * There should be no breaking changes in the API, however it may be extended.
   136 * Some of the AnnexB functionality is missing.
   137
   138Basic Example
   139-------------
   140
   141Run JavaScript and get the result value.
   142
   143```go
   144vm := goja.New()
   145v, err := vm.RunString("2 + 2")
   146if err != nil {
   147    panic(err)
   148}
   149if num := v.Export().(int64); num != 4 {
   150    panic(num)
   151}
   152```
   153
   154Passing Values to JS
   155--------------------
   156Any Go value can be passed to JS using Runtime.ToValue() method. See the method's [documentation](https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue) for more details.
   157
   158Exporting Values from JS
   159------------------------
   160A JS value can be exported into its default Go representation using Value.Export() method.
   161
   162Alternatively it can be exported into a specific Go variable using [Runtime.ExportTo()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ExportTo) method.
   163
   164Within a single export operation the same Object will be represented by the same Go value (either the same map, slice or
   165a pointer to the same struct). This includes circular objects and makes it possible to export them.
   166
   167Calling JS functions from Go
   168----------------------------
   169There are 2 approaches:
   170
   171- Using [AssertFunction()](https://pkg.go.dev/github.com/dop251/goja#AssertFunction):
   172```go
   173const SCRIPT = `
   174function sum(a, b) {
   175    return +a + b;
   176}
   177`
   178
   179vm := goja.New()
   180_, err := vm.RunString(SCRIPT)
   181if err != nil {
   182    panic(err)
   183}
   184sum, ok := goja.AssertFunction(vm.Get("sum"))
   185if !ok {
   186    panic("Not a function")
   187}
   188
   189res, err := sum(goja.Undefined(), vm.ToValue(40), vm.ToValue(2))
   190if err != nil {
   191    panic(err)
   192}
   193fmt.Println(res)
   194// Output: 42
   195```
   196- Using [Runtime.ExportTo()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ExportTo):
   197```go
   198const SCRIPT = `
   199function sum(a, b) {
   200    return +a + b;
   201}
   202`
   203
   204vm := goja.New()
   205_, err := vm.RunString(SCRIPT)
   206if err != nil {
   207    panic(err)
   208}
   209
   210var sum func(int, int) int
   211err = vm.ExportTo(vm.Get("sum"), &sum)
   212if err != nil {
   213    panic(err)
   214}
   215
   216fmt.Println(sum(40, 2)) // note, _this_ value in the function will be undefined.
   217// Output: 42
   218```
   219
   220The first one is more low level and allows specifying _this_ value, whereas the second one makes the function look like
   221a normal Go function.
   222
   223Mapping struct field and method names
   224-------------------------------------
   225By default, the names are passed through as is which means they are capitalised. This does not match
   226the standard JavaScript naming convention, so if you need to make your JS code look more natural or if you are
   227dealing with a 3rd party library, you can use a [FieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#FieldNameMapper):
   228
   229```go
   230vm := goja.New()
   231vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
   232type S struct {
   233    Field int `json:"field"`
   234}
   235vm.Set("s", S{Field: 42})
   236res, _ := vm.RunString(`s.field`) // without the mapper it would have been s.Field
   237fmt.Println(res.Export())
   238// Output: 42
   239```
   240
   241There are two standard mappers: [TagFieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#TagFieldNameMapper) and
   242[UncapFieldNameMapper](https://pkg.go.dev/github.com/dop251/goja#UncapFieldNameMapper), or you can use your own implementation.
   243
   244Native Constructors
   245-------------------
   246
   247In order to implement a constructor function in Go use `func (goja.ConstructorCall) *goja.Object`.
   248See [Runtime.ToValue()](https://pkg.go.dev/github.com/dop251/goja#Runtime.ToValue) documentation for more details.
   249
   250Regular Expressions
   251-------------------
   252
   253Goja uses the embedded Go regexp library where possible, otherwise it falls back to [regexp2](https://github.com/dlclark/regexp2).
   254
   255Exceptions
   256----------
   257
   258Any exception thrown in JavaScript is returned as an error of type *Exception. It is possible to extract the value thrown
   259by using the Value() method:
   260
   261```go
   262vm := goja.New()
   263_, err := vm.RunString(`
   264
   265throw("Test");
   266
   267`)
   268
   269if jserr, ok := err.(*Exception); ok {
   270    if jserr.Value().Export() != "Test" {
   271        panic("wrong value")
   272    }
   273} else {
   274    panic("wrong type")
   275}
   276```
   277
   278If a native Go function panics with a Value, it is thrown as a Javascript exception (and therefore can be caught):
   279
   280```go
   281var vm *Runtime
   282
   283func Test() {
   284    panic(vm.ToValue("Error"))
   285}
   286
   287vm = goja.New()
   288vm.Set("Test", Test)
   289_, err := vm.RunString(`
   290
   291try {
   292    Test();
   293} catch(e) {
   294    if (e !== "Error") {
   295        throw e;
   296    }
   297}
   298
   299`)
   300
   301if err != nil {
   302    panic(err)
   303}
   304```
   305
   306Interrupting
   307------------
   308
   309```go
   310func TestInterrupt(t *testing.T) {
   311    const SCRIPT = `
   312    var i = 0;
   313    for (;;) {
   314        i++;
   315    }
   316    `
   317
   318    vm := goja.New()
   319    time.AfterFunc(200 * time.Millisecond, func() {
   320        vm.Interrupt("halt")
   321    })
   322
   323    _, err := vm.RunString(SCRIPT)
   324    if err == nil {
   325        t.Fatal("Err is nil")
   326    }
   327    // err is of type *InterruptError and its Value() method returns whatever has been passed to vm.Interrupt()
   328}
   329```
   330
   331NodeJS Compatibility
   332--------------------
   333
   334There is a [separate project](https://github.com/dop251/goja_nodejs) aimed at providing some of the NodeJS functionality.

View as plain text