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