ECMAScript/JavaScript engine in pure Go

Overview

goja

ECMAScript 5.1(+) implementation in Go.

GoDoc

Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and performance.

This project was largely inspired by otto.

Minimum required Go version is 1.14.

Features

Known incompatibilities and caveats

WeakMap

WeakMap is implemented by embedding references to the values into the keys. This means that as long as the key is reachable all values associated with it in any weak maps also remain reachable and therefore cannot be garbage collected even if they are not otherwise referenced, even after the WeakMap is gone. The reference to the value is dropped either when the key is explicitly removed from the WeakMap or when the key becomes unreachable.

To illustrate this:

var m = new WeakMap();
var key = {};
var value = {/* a very large object */};
m.set(key, value);
value = undefined;
m = undefined; // The value does NOT become garbage-collectable at this point
key = undefined; // Now it does
// m.delete(key); // This would work too

The reason for it is the limitation of the Go runtime. At the time of writing (version 1.15) having a finalizer set on an object which is part of a reference cycle makes the whole cycle non-garbage-collectable. The solution above is the only reasonable way I can think of without involving finalizers. This is the third attempt (see https://github.com/dop251/goja/issues/250 and https://github.com/dop251/goja/issues/199 for more details).

Note, this does not have any effect on the application logic, but may cause a higher-than-expected memory usage.

FAQ

How fast is it?

Although it's faster than many scripting language implementations in Go I have seen (for example it's 6-7 times faster than otto on average) it is not a replacement for V8 or SpiderMonkey or any other general-purpose JavaScript engine. You can find some benchmarks here.

Why would I want to use it over a V8 wrapper?

It greatly depends on your usage scenario. If most of the work is done in javascript (for example crypto or any other heavy calculations) you are definitely better off with V8.

If you need a scripting language that drives an engine written in Go so that you need to make frequent calls between Go and javascript passing complex data structures then the cgo overhead may outweigh the benefits of having a faster javascript engine.

Because it's written in pure Go there are no cgo dependencies, it's very easy to build and it should run on any platform supported by Go.

It gives you a much better control over execution environment so can be useful for research.

Is it goroutine-safe?

No. An instance of goja.Runtime can only be used by a single goroutine at a time. You can create as many instances of Runtime as you like but it's not possible to pass object values between runtimes.

Where is setTimeout()?

setTimeout() assumes concurrent execution of code which requires an execution environment, for example an event loop similar to nodejs or a browser. There is a separate project aimed at providing some NodeJS functionality, and it includes an event loop.

Can you implement (feature X from ES6 or higher)?

Some ES6 functionality has been implemented. So far this is mostly built-ins, not syntax enhancements. See https://github.com/dop251/goja/milestone/1 for more details.

The ongoing work is done in the es6 branch which is merged into master when appropriate. Every commit in this branch represents a relatively stable state (i.e. it compiles and passes all enabled tc39 tests), however because the version of tc39 tests I use is quite old, it may be not as well tested as the ES5.1 functionality. Because ES6 is a superset of ES5.1 it should not break your existing code.

I will be adding features in their dependency order and as quickly as my time allows. Please do not ask for ETAs. Features that are open in the milestone are either in progress or will be worked on next.

How do I contribute?

Before submitting a pull request please make sure that:

  • You followed ECMA standard as close as possible. If adding a new feature make sure you've read the specification, do not just base it on a couple of examples that work fine.
  • Your change does not have a significant negative impact on performance (unless it's a bugfix and it's unavoidable)
  • It passes all relevant tc39 tests.

Current Status

  • There should be no breaking changes in the API, however it may be extended.
  • Some of the AnnexB functionality is missing.

Basic Example

Run JavaScript and get the result value.

vm := goja.New()
v, err := vm.RunString("2 + 2")
if err != nil {
    panic(err)
}
if num := v.Export().(int64); num != 4 {
    panic(num)
}

Passing Values to JS

Any Go value can be passed to JS using Runtime.ToValue() method. See the method's documentation for more details.

Exporting Values from JS

A JS value can be exported into its default Go representation using Value.Export() method.

Alternatively it can be exported into a specific Go variable using Runtime.ExportTo() method.

Within a single export operation the same Object will be represented by the same Go value (either the same map, slice or a pointer to the same struct). This includes circular objects and makes it possible to export them.

Calling JS functions from Go

There are 2 approaches:

vm := New()
_, err := vm.RunString(`
function sum(a, b) {
    return a+b;
}
`)
if err != nil {
    panic(err)
}
sum, ok := AssertFunction(vm.Get("sum"))
if !ok {
    panic("Not a function")
}

res, err := sum(Undefined(), vm.ToValue(40), vm.ToValue(2))
if err != nil {
    panic(err)
}
fmt.Println(res)
// Output: 42
const SCRIPT = `
function f(param) {
    return +param + 2;
}
`

vm := New()
_, err := vm.RunString(SCRIPT)
if err != nil {
    panic(err)
}

var fn func(string) string
err = vm.ExportTo(vm.Get("f"), &fn)
if err != nil {
    panic(err)
}

fmt.Println(fn("40")) // note, _this_ value in the function will be undefined.
// Output: 42

The first one is more low level and allows specifying this value, whereas the second one makes the function look like a normal Go function.

Mapping struct field and method names

By default, the names are passed through as is which means they are capitalised. This does not match the standard JavaScript naming convention, so if you need to make your JS code look more natural or if you are dealing with a 3rd party library, you can use a FieldNameMapper:

vm := New()
vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
type S struct {
    Field int `json:"field"`
}
vm.Set("s", S{Field: 42})
res, _ := vm.RunString(`s.field`) // without the mapper it would have been s.Field
fmt.Println(res.Export())
// Output: 42

There are two standard mappers: TagFieldNameMapper and UncapFieldNameMapper, or you can use your own implementation.

Native Constructors

In order to implement a constructor function in Go use func (goja.ConstructorCall) *goja.Object. See Runtime.ToValue() documentation for more details.

Regular Expressions

Goja uses the embedded Go regexp library where possible, otherwise it falls back to regexp2.

Exceptions

Any exception thrown in JavaScript is returned as an error of type *Exception. It is possible to extract the value thrown by using the Value() method:

vm := New()
_, err := vm.RunString(`

throw("Test");

`)

if jserr, ok := err.(*Exception); ok {
    if jserr.Value().Export() != "Test" {
        panic("wrong value")
    }
} else {
    panic("wrong type")
}

If a native Go function panics with a Value, it is thrown as a Javascript exception (and therefore can be caught):

var vm *Runtime

func Test() {
    panic(vm.ToValue("Error"))
}

vm = New()
vm.Set("Test", Test)
_, err := vm.RunString(`

try {
    Test();
} catch(e) {
    if (e !== "Error") {
        throw e;
    }
}

`)

if err != nil {
    panic(err)
}

Interrupting

func TestInterrupt(t *testing.T) {
    const SCRIPT = `
    var i = 0;
    for (;;) {
        i++;
    }
    `

    vm := New()
    time.AfterFunc(200 * time.Millisecond, func() {
        vm.Interrupt("halt")
    })

    _, err := vm.RunString(SCRIPT)
    if err == nil {
        t.Fatal("Err is nil")
    }
    // err is of type *InterruptError and its Value() method returns whatever has been passed to vm.Interrupt()
}

NodeJS Compatibility

There is a separate project aimed at providing some of the NodeJS functionality.

Comments
  • Adding native constructors

    Adding native constructors

    I've forked and started dabbling into allowing package users to create "native" constructors and instances.

    I'm implementing part of the Web API (Response, Request, Headers, etc.) and needed some way to create more complex instances that kept a state outside of goja.

    Here's my rough implementation so far:

    // constructor.go
    
    package goja
    
    type NativeFunction func(FunctionCall) Value
    type ConstructorFunction func(args []Value, inst *Object) *Object
    
    type Method struct {
    	call   NativeFunction
    	length int
    }
    
    type Constructor struct {
    	Name string
    	r    *Runtime
    
    	proto  *Object
    	static *Object
    
    	ctor   ConstructorFunction
    	length int
    
    	methods map[string]Method
    }
    
    func (r *Runtime) NewConstructor(name string, ctor ConstructorFunction, length int) *Constructor {
    	return &Constructor{
    		Name:    name,
    		r:       r,
    		ctor:    ctor,
    		length:  length,
    		methods: map[string]Method{},
    	}
    }
    
    func (c *Constructor) createProto(val *Object) objectImpl {
    	o := &baseObject{
    		class:      c.Name,
    		val:        val,
    		extensible: true,
    		prototype:  c.r.global.ObjectPrototype,
    	}
    	o.init()
    
    	for name, method := range c.methods {
    		o._putProp(name, c.r.newNativeFunc(method.call, nil, name, nil, method.length), true, false, true)
    	}
    
    	return o
    }
    
    func (c *Constructor) createStatic(val *Object) objectImpl {
    	o := c.r.newNativeFuncConstructObj(val, c.ctor, c.Name, c.proto, c.length)
    
    	// _putProp here...
    
    	return o
    }
    
    func (c *Constructor) NewInstance(args []Value, proto *Object) *Object {
    	bo := c.r.newBaseObject(proto, c.Name)
    	val := c.ctor(args, bo.val)
    	return val
    }
    
    func (c *Constructor) Init() {
    	c.proto = c.r.newLazyObject(c.createProto)
    	c.static = c.r.newLazyObject(c.createStatic)
    
    	c.r.addToGlobal(c.Name, c.static)
    }
    
    func (c *Constructor) DefineFunction(name string, call NativeFunction, length int) {
    	c.methods[name] = Method{call, length}
    }
    

    Surprisingly, this works ok. Here's an example of how I'm using it:

    func constructResponse(vm *goja.Runtime) goja.ConstructorFunction {
    	return func(args []goja.Value, res *goja.Object) *goja.Object {
    		res.Set("body", nil)
    		res.Set("bodyUsed", false)
    		res.Set("ok", true)
    		res.Set("redirected", false)
    		res.Set("status", 200)
    		res.Set("statusText", "OK")
    		res.Set("type", "default")
    		res.Set("url", "")
    
    		res.Set("text", bodyText(vm, strings.NewReader("")))
    
    		return res
    	}
    }
    
    func initResponse(vm *goja.Runtime) {
    	c := vm.NewConstructor("Response", constructResponse(vm), 2)
    	c.Init()
    }
    

    This works. Except I'm left with the same issue trying to keep state around. I have to create a bunch of closures.

    For instance, for the text() function: I'd like it to keep around a io.Reader and read from it from the text() function.

    As far as I can tell, there's already a similar pattern in goja. Namely, the newDateObject function seems to create a special kind of object that embed baseObject and a few more fields. It makes sense, except that's all unexported and I'm not sure how I can make it generic enough.

    I'm mostly looking for advice on how to proceed. Ideally, from a package user's perspective, the developer would only have to provide a constructor and a struct instance to make it work.

    I think maybe abstracting and exporting a few more structures would help. I guess I'd mostly need to create a few structs embedding baseObject or implementing objectImpl.

    Any thoughts?

    enhancement 
    opened by jeromegn 18
  • Misbehavior of ToValue() on map type with methods

    Misbehavior of ToValue() on map type with methods

    I found an issue while trying to enumerate the keys of an object that I created with ToValue() from a map type having helper methods.

    As an example, let's use http.Header type which is a Go map type with methods. When converting a http.Header value using ToValue(), this line of code in ToValue() https://github.com/dop251/goja/blob/master/runtime.go#L1067 prevents from making it a objectGoMapReflect and ends up as a objectGoReflect.

    But the its key enumeration only happens on methods and not on the map keys.

    Here is a test example that demonstrates the issue:

    	func TestMapTypeBinding(t *testing.T) {
    		headers := http.Header{
    			"k0": []string{},
    			"k1": []string{"v1"},
    			"k2": []string{"v21", "v22"},
    			"k3": []string{"v3"},
    		}
    		vm := goja.New()
    		vm.Set("headers", headers)
    		v, err := vm.RunString(`Object.keys(headers)`)
    		require.NoError(t, err)
    		result := v.Export()
    		require.Equal(t, []interface{}{"k0", "k1", "k2", "k3"}, result)
    	})
    

    Failing with:

    Expected :[]interface {}{"k0", "k1", "k2", "k3"} Actual :[]interface {}{"Add", "Del", "Get", "Set", "Write", "WriteSubset"}

    I'd be happy to provide a fix, but I'd like to understand first in which case it should be methods only as suggested by the current implementation.

    question wontfix 
    opened by Julio-Guerra 14
  • Multiple functions in my Javascript file

    Multiple functions in my Javascript file

    Hello, I come from Otto world and I want to migrate to this library because of very bad performance issues. I am using it for a normalisation pipeline.

    I used to put 4 or 5 functions into a JS file, and then call one of them into my Golang code.

    Example (here I call the function Exlcude) :

    // Run
    returnRaw, err := vm.Run(`Exclude(` + string(incidentJSON) + `);`)
    if err != nil {
    	return false, fmt.Errorf("Error while running the JS code : %s", err)
    }
    

    How can I do this with your lib please ? Thanks a lot ! :)

    question 
    opened by fallais 12
  • ES6 Proxy (WIP)

    ES6 Proxy (WIP)

    I'm working on an ES6 Proxy implementation. Traps can be implemented in JavaScript as in the specification or using a Go struct and the specific function implementations.

    Currently still work in progress, some tests are still missing, therefore most probably doesn't work yet in all specified situations.

    Anyhow would be happy to get some feedback, as this is my first bigger change to commit back to the official goja repository :-)

    I bet there would have to be changes here and there to integrate before it can be merged eventually

    opened by noctarius 12
  • Question: how feasible is it to share Freezed objects between goja.Runtimes

    Question: how feasible is it to share Freezed objects between goja.Runtimes

    The question is more or less in the title. The specific use case is an array with objects that mostly will contain strings, numbers, or other array/objects containing the same.

    Obviously having a function and calling that function will be bad and having a regex can trigger #214, but are there such situations for the other types, and are there other things that need to be taken into account ... apart from me thinking that this is a bad idea and that w/e answer you give will change over time.

    For more context, you can look at this discussion https://github.com/loadimpact/k6/pull/1739#discussion_r540119684

    question 
    opened by mstoykov 11
  • (*vm).Interrupt causes a data race

    (*vm).Interrupt causes a data race

    Per pull request #14, (*vm).Interrupt causes a data race. This results in undefined behavior and means that interrupts are currently unsafe to use. It can be resolved in the short term by using atomics (as in #14) or other explicit synchronization to signal the interrupt from a separate goroutine, and in the long term may be fixed in another manner.

    As discussed in #14, it is unacceptable for a data race to be included in a critical part of a VM, especially as part of an interrupt. According to @dop251, the data race and undefined behavior here is intentional in the pursuit of performance, and objects to the use of atomics to resolve this. The goal, then, is to find another method of causing an interrupt that both satisfies @dop251's performance requirements (currently unstated) and eliminates the data race, preferably without introducing unsafe code that cannot be checked by the race detector.

    This issue is intended to discuss other approaches to removing the interrupt data race, knowing that atomics are off the table (or somehow too slow).

    opened by nilium 11
  • Test for and fix (*vm).Interrupt data race

    Test for and fix (*vm).Interrupt data race

    This patch adds a test for a data race under (*vm).Interrupt, where an interrupt flag (boolean) is set to true inside of a lock but read outside of a lock. Since the lock is only taken at the end of (*vm).run to clear the interrupt value and un-set the interrupt flag, it doesn't make sense to lock for every check on the interrupt flag.

    I assume there's a reason for not using a channel here (even though it'd be simpler), but the most minimal change to be made right now is to replace the bool with a uint32 (or some other integer) and read/write it via atomics.

    The patch adds an (*vm).interrupted method to check the interrupt flag atomically and stores it only via atomics. The type change to uint32 also ensures that there are no other uses of the interrupt bool field (since a uint32 isn't ever treated as a bool and vice-versa). The lock remains in place to serialize setting and clearing the interrupt value.

    To test the race, checkout the first commit (73d6439) and run go test -race -run TestInterruptRace -- this should be enough to trigger it.

    opened by nilium 11
  • Structs

    Structs

    type str struct { A [5]float64 B [5]float64 C [5]float64 D [5]float64 }

    func main(){ mystr:= str{ A: [5]float64{11,22,333,444,55}, B: [5]float64{11,22,333,444,55}, C: [5]float64{11,22,333,444,55}, D: [5]float64{11,22,333,444,55}, } vm.Set("mystr", mystr) var prg = "" + "mystr.A[0];" } I get a nil from this. Am I not indexing the array properly? mystr.A works fine just not mystr.A[0] access.

    'mystr.A' yields [11 22 333 444 55]

    question 
    opened by konethmelathil 10
  • Memory leaks?

    Memory leaks?

    Hi

    I'm building a universal web app, where goja handles JS rendering on the server.

    Now I'm in the phase of doing performance tests, and for some reason goja seems to be leaking memory.

    I'm using artillery.io to perform load tests, and after the load test it seems vm.run is holding memory and not releasing it. I'm not too sure if that's goja fault (probably not), but maybe you know anything off the top of your head how to prevent memory leaks?

    Here's the heap pprof BEFORE the load test: http://jarosinski.uk/files/heap_base.svg

    And here's the one after the load test: http://jarosinski.uk/files/heap_current.svg

    And this is the code that handles VM creation: https://gist.github.com/iKonrad/a046de73c8e478b3cac07fa9e419af58

    Any help or suggestions will be appreciated.

    opened by iKonrad 10
  • Check if something is still running. Kill the VM?

    Check if something is still running. Kill the VM?

    Can we check if something is still running so maybe we can have a limited runtime time? So we could maybe check if the thing is finished executing? That also applies to the event loop. On top of that can we have something to just stop the execution mid-process?

    opened by JSH32 9
  • Issue when compiling typescript 5.0 compiler

    Issue when compiling typescript 5.0 compiler

    Hi,

    It looks like the next typescript compiler version (currently I tested with 5.0.0-dev.20221116) is compiled to a newer js dialect (es2018 ?) with features that goja does not support.

    Would it be possible to add support for rest and spread properties for object literals in goja ? It looks like this would allow to run the current development typescript compiler properly

    https://github.com/tc39/proposal-object-rest-spread

    bug 
    opened by vanackere 8
  • Implement async/await syntax

    Implement async/await syntax

    async/await syntax was added in ES2017 and makes the asynchrnous and non-blocking Promises from ES2015 more syncrhnous in syntax while still being non-blocking.

    The issue is about adding basic async/await syntax:

    1. async marking async functions in which you can use
    2. await to signal to the VM that it should unwind the stack and come back to this particular point after the provided promise is resolved or rejected.
    3. The handling of restoring the stack and continuing the execution from that point after the promise is resolved or rejected. Including throwing exceptions on rejects.
    4. Being able to interact with them from go code.

    This like #436 is blocked on the ability of the goja runtime save and restore the execution context.

    Things that should (likely) not be part of this issue:

    • AsyncGenerator as it is blocked on #436
    • top-level-await as it is blocked on #430

    This also blocks #430 as mentioned in it.

    opened by mstoykov 3
  • Store object using SharedDynamicObject

    Store object using SharedDynamicObject

    func TestSharedDynamicObjectMe(t *testing.T) {
    	dynObj := &testSharedDynObject{
    		RWMutex: sync.RWMutex{},
    		m:       make(map[string]Value, 10000),
    	}
    	o := NewSharedDynamicObject(dynObj)
    
    	vm2 := New()
    	me := vm2.NewObject()
    	me.Set("num", 3)
    	dynObj.Set("me", me)
    
    	vm := New()
    	vm.Set("o", o)
    	vm.Set("me", dynObj.Get("me").ToObject(vm))
    
    	v, _ := vm.RunString(`me`)
    	fmt.Printf("v: %#v\n", v)
    
    	v, _ = vm.RunString(`o["me"].num`)
    	fmt.Printf("v: %v\n", v)
    }
    
    === RUN   TestSharedDynamicObjectMe
    v: <nil>
    v: 3
    

    I do not understand why the first result is nil.

    And is it how SharedDynamicObject supposed to be used? Read and write in different vms.

    question 
    opened by qazwsxedckll 1
  • Implement generators

    Implement generators

    Generators were introduced in ES6.

    AFAIK there are currently one of the two ES6 features that goja doesn't have support - the other one being modules.

    It is also needed in order to implement async/await (will open issue and link it here later) which is ther other blocking thing on the modules PR #430 from this comment

    I would want to work on this (already have a very WIP PR - still not to the "interesting" parts). But it will be nice to know that there is nothing blocking it and what the scope will be.

    I would implement all the syntax that is not async related: -function * as both methods and functions

    • yield both with and without returning or taking arguments
    • yield* which delegates the execution to a different generator
    • .next(), return() and throw() on the generator
    • Makign a generator iterable so it is used with for cycles
    • pass all test262 tests that aren't blocked on something else.
    • Expose some interface for go code to make Generators Anything in particular that I am missing from this list?

    I also will be basing it on the 13th edition of the specification.

    Does this seem okay @dop251 and are you okay with me having a try at actually implemetning this in teh following month or two(I will not be able to start right away full time on this :( )

    opened by mstoykov 1
  • parse error: Malformed arrow function parameter list

    parse error: Malformed arrow function parameter list

    I tried to parse this file https://pkg.go.dev/static/frontend/unit/main/main.js, but failed, here is the snippet of the error

    import (
    	"fmt"
    
    	goja "github.com/dop251/goja/parser"
    )
    
    func main() {
    	b := `
    fetch("/play/compile", {
      method: "POST",
      body: JSON.stringify({
        body: (e = this.inputEl) == null ? void 0 : e.value,
        version: 2,
      }),
    })
      .then((t) => t.json())
      .then(async ({ Events: t, Errors: i }) => {
        this.setOutputText(i || "");
        for (let s of t || [])
          this.setOutputText(s.Message),
            await new Promise((r) => setTimeout(r, s.Delay / 1e6));
      })
      .catch((t) => {
        this.setErrorText(t);
      });
    `
    	_, err := goja.ParseFile(nil, "", b, 0, goja.WithDisableSourceMaps)
    	if err != nil {
    		fmt.Printf("err: %v", err)
    	}
    
    }
    

    https://go.dev/play/p/NV8ldPsDb_a

    opened by ttttmr 3
  • Modules support

    Modules support

    This is a WIP PR implementing ECMAScript Modules (ESM) in goja.

    The PR does add every ESM feature as of ES2022 including:

    • basic import/export syntax.
    • module namespace import * as ns from "somewhere".
    • dynamic import.
    • async module evaluation, even though the source text module do not support top level await. This definitely needs more testing as it was added fairly recently and required ... a bit of refactoring.

    The WIP part of this PR comes from the fact that there are many open implementation specifics that I am not certain about and at least for some of them I would need your input, @dop251.

    (For not @dop251, while I will appreciate any input, if you just want to try it out - this might be a bit hard as there is no documentation. Also, as mentioned below a lot of the APIs will change. But if you really want look at modules_integration_test.go as that likely is the best place for a small(ish) but fully working example)

    Why open an unfinished PR

    I have worked on this on and off since I guess December, but mostly in the last 3 months. Unforunately while I did manage to implement most stuff I just kept getting more and more questions that likely I should've asked earlier. Also, I am getting more and more other work that also needs to get done. Because of that I decided to try to get some feedback on the current implementation in hopes that will make things easier for both of us.

    I will try to ask most of the questions inline/incode, this hopefully will help with threading, but we can also move any discussion in another place if you want to. For some of the questions I have - I also have ideas, which I will probably go with when I have the time even if you haven't come back to me. Also, all my links will be to my branch, because links to code in big PRs just plain don't work, and you would likely want to open them locally either way.

    Again I will continue working on this, but hopefully with some help the time it takes me will be smaller. It likely also will make this easier for you to review in the end ;)

    Tthere are a bunch of:

    • commented fmt.Print* lines that will be removed in any final PR.
    • TODOs coments which will also be removed as I go through them.
    • big functions that will likely be broken in smaller ones.

    You can probably ignore those unless you want something to stay or want to given some specific feedback there.

    I have tried to not change anything in goja that I don't need, but I still needed to touch stuff which aren't directly about modules. Hopefully all of are fine.

    Really sorry for the formatting changes in some places. I have tried to bring them to a minimal as to not just move code around. I should've probably disabled gofumpt for this entire project and that would've helped more πŸ€·β€β™‚. I can probably try to remove them one final time after everything else is done.

    This whole PR should be squashed in the end. Some commits are kind of useful if you need to figure out what I have tried, but most of it is likely very outdated and will definitely not be useful once we are done with this feature.

    I guess first:

    Are you interested in goja having ESM support in general? And do you see any fundamental issues with the approach I have taken and have not asked questions for? It's quite a big change, I will fully understand if you don't want it or, do not have time to code review it and help me right now or until I am actually of the opinion I am done.

    Background on why I am working on this and a particular problem I still need to fix in k6

    I specifically wanted to work on this as in k6 we do have this, I guess unfortunate, requirement that import "commonjsModule" needs to work as well as require("ESMmodule"). Cycles of both of them aren't that necessary to be supported (although I guess it will be nice if I can make them work πŸ€·β€β™‚).

    Due to k6 currently using babel internally to transpile ESM to CommonJS, users have been using both and because transpiling takes time we have historically made helper modules as CommonJS :(. So this all leads to the fact that some interoperability is heavily required.

    Luckily I haven't really done anything to the code design to make it possible πŸ€·β€β™‚it just kind of works on the most basic level as shown in the k6 PoC PR

    That k6 PR apart from lacking a ton of tests, mostly to test cycles and interoperability, has only 2 known bugs/problems - so probably a lot more 😒.

    Both open and require should be relative to the script/module that is the current "root" of execution, instead they are currently (in that PR) relative to the file that is the main script/module - the argument to k6 run this/file/here.js.

    This is one of those really hard to explain with just words problems. But I basically need activeScriptOrModule, but instead of returning the module we are currently "in" (as in the code/text place), I need the one that is the root of the execution.

    And yes I (now) know that require is supposed to be relative to the file it's written in just like import()... but that is literally not how it has been since k6 was made and even if we change that ... open still exists. I have opened an issue to figure out if k6 would not want to change this.

    You do currently do this in goja_nodejs here, but I am not sure this works with source maps (it seems to, I guess I am wrong where the "rename" happens πŸ€·β€β™‚) but in order to get the "root" module/script I will need to go through the whole stack ... which might be big :(. I kind of feel like that is an okay thing for goja to provide. What do you think?

    (Note: currently k6 can know which the currently executing script file as it is the only thing that is executing stuff, once goja starts evaluating cyclic modules this will no longer work)

    Edit: I just posted all of the comments inline and noticed, while copying the messages I had prepared, that the order of the code in the PR as always does not in any way reflect how the code actually flows :facepalm:

    I would recommend looking at something like modules_integration_test.go to see how the code is used and then following the code from there. I would argue also that the most important comments and questions are in modules.go and compiler.go.

    Also thanks again for working on goja :bow:

    opened by mstoykov 5
  • Fix missing or wrong implementations in node.go

    Fix missing or wrong implementations in node.go

    Some types in ast/node.go were not implementing the Node type, and some implementations were missing Idx0 and Idx1 functions for their type, which results in errors when attempting to use/import some of the types. I have corrected all the faulty implementations. I believe I have implemented the Idx0 and Idx1 functions correctly, but please review those changes and see if I did anything wrong.

    opened by leviinz 0
Owner
Dmitry Panov
Dmitry Panov
🦁 A Super fast and lightweight runtime for JavaScript Scripts

Kimera.js - A super fast and lightweight JavaScript Runtime for Scripts.

Eliaz Bobadilla 22 Aug 14, 2022
Esprima-Go: A high performance JavaScript parser written in Go

Esprima-Go is a JavaScript parser written in Go. Esprima is a JavaScript parser written in TypeScript, it's widely used in javascript-realt

CHEN Yuan 0 Jan 9, 2022
Expression evaluation engine for Go: fast, non-Turing complete, dynamic typing, static typing

Expr Expr package provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not lim

Anton Medvedev 3.2k Dec 1, 2022
ECMAScript/JavaScript engine in pure Go

goja ECMAScript 5.1(+) implementation in Go. Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and performan

Dmitry Panov 3.4k Dec 9, 2022
ECMAScript/JavaScript engine in pure Go

goja ECMAScript 5.1(+) implementation in Go. Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and performan

Dmitry Panov 3.4k Dec 8, 2022
A full-featured regex engine in pure Go based on the .NET engine

regexp2 - full featured regular expressions for Go Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time guarantees like the b

Doug Clark 645 Dec 5, 2022
Duktape JavaScript engine bindings for Go

Duktape bindings for Go(Golang) Duktape is a thin, embeddable javascript engine. Most of the api is implemented. The exceptions are listed here. Usage

Oleg Lebedev 777 Nov 26, 2022
v8 javascript engine binding for golang

Go-V8 V8 JavaScript engine bindings for Go. Features Thread safe Thorough and careful testing Boolean, Number, String, Object, Array, Regexp, Function

Hoping White 208 Nov 21, 2022
A Go API for the V8 javascript engine.

V8 Bindings for Go The v8 bindings allow a user to execute javascript from within a go executable. The bindings are tested to work with several recent

Augusto Roman 381 Nov 13, 2022
This library provides WebAssembly capability for goja Javascript engine

This module provides WebAssembly functions into goja javascript engine.

YC-L 1 Jan 10, 2022
A pure Go game engine

Oak A pure Go game engine Table of Contents Installation Motivation Features Support Quick Start Implementation and Examples Finished Games Installati

Oakmound Studio 1.3k Dec 6, 2022
A MySQL-compatible relational database with a storage agnostic query engine. Implemented in pure Go.

go-mysql-server is a SQL engine which parses standard SQL (based on MySQL syntax) and executes queries on data sources of your choice. A simple in-memory database and table implementation are provided, and you can query any data source you want by implementing a few interfaces.

DoltHub 932 Dec 7, 2022
A MySQL-compatible relational database with a storage agnostic query engine. Implemented in pure Go.

go-mysql-server go-mysql-server is a SQL engine which parses standard SQL (based on MySQL syntax) and executes queries on data sources of your choice.

DoltHub 935 Dec 8, 2022
Qt binding for Go (Golang) with support for Windows / macOS / Linux / FreeBSD / Android / iOS / Sailfish OS / Raspberry Pi / AsteroidOS / Ubuntu Touch / JavaScript / WebAssembly

Introduction Qt is a free and open-source widget toolkit for creating graphical user interfaces as well as cross-platform applications that run on var

null 9.5k Dec 2, 2022
Glue - Robust Go and Javascript Socket Library (Alternative to Socket.io)

Glue - Robust Go and Javascript Socket Library Glue is a real-time bidirectional socket library. It is a clean, robust and efficient alternative to so

DesertBit 408 Nov 25, 2022
Glue - Robust Go and Javascript Socket Library (Alternative to Socket.io)

Glue - Robust Go and Javascript Socket Library Glue is a real-time bidirectional socket library. It is a clean, robust and efficient alternative to so

DesertBit 408 Nov 25, 2022
A JavaScript interpreter in Go (golang)

otto -- import "github.com/robertkrimen/otto" Package otto is a JavaScript parser and interpreter written natively in Go. http://godoc.org/github.com/

Robert Krimen 7k Dec 1, 2022
A compiler from Go to JavaScript for running Go code in a browser

GopherJS - A compiler from Go to JavaScript GopherJS compiles Go code (golang.org) to pure JavaScript code. Its main purpose is to give you the opport

GopherJS 11.6k Dec 5, 2022
Golang->Haxe->CPP/CSharp/Java/JavaScript transpiler

TARDIS Go -> Haxe transpiler Haxe -> C++ / C# / Java / JavaScript Project status: a non-working curiosity, development currently on-ice The advent of

TARDIS Go 423 Nov 27, 2022
An extremely fast JavaScript bundler and minifier

An extremely fast JavaScript bundler and minifier

Evan Wallace 34k Dec 3, 2022