Skip to content

Commit

Permalink
Skip Babel for ES5 sources
Browse files Browse the repository at this point in the history
Closes #212
  • Loading branch information
Emily Ekberg committed Sep 12, 2017
1 parent cbfb1fb commit d51c3ea
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 16 deletions.
10 changes: 3 additions & 7 deletions js/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,10 @@ func collectEnv() map[string]string {

// Creates a new bundle from a source file and a filesystem.
func NewBundle(src *lib.SourceData, fs afero.Fs) (*Bundle, error) {
// Compile the main program.
code, _, err := compiler.Transform(string(src.Data), src.Filename)
// Compile sources, both ES5 and ES6 are supported.
pgm, code, err := compiler.Compile(string(src.Data), src.Filename, "", "", true)
if err != nil {
return nil, errors.Wrap(err, "Transform")
}
pgm, err := goja.Compile(src.Filename, code, true)
if err != nil {
return nil, errors.Wrap(err, "Compile")
return nil, err
}

// We want to eliminate disk access at runtime, so we set up a memory mapped cache that's
Expand Down
24 changes: 24 additions & 0 deletions js/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/GeertJohan/go.rice"
"github.com/dop251/goja"
"github.com/dop251/goja/parser"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -49,6 +50,7 @@ func New() (*Compiler, error) {
return c, nil
}

// Transform the given code into ES5.
func (c *Compiler) Transform(src, filename string) (code string, srcmap SourceMap, err error) {
opts := make(map[string]interface{})
for k, v := range DefaultOpts {
Expand Down Expand Up @@ -78,3 +80,25 @@ func (c *Compiler) Transform(src, filename string) (code string, srcmap SourceMa

return code, srcmap, nil
}

// Compiles the program, first trying ES5, then ES6.
func (c *Compiler) Compile(src, filename string, pre, post string, strict bool) (*goja.Program, string, error) {
return c.compile(src, filename, pre, post, strict, true)
}

func (c *Compiler) compile(src, filename string, pre, post string, strict, tryBabel bool) (*goja.Program, string, error) {
code := pre + src + post
ast, err := parser.ParseFile(nil, filename, code, 0)
if err != nil {
if tryBabel {
code, _, err := c.Transform(src, filename)
if err != nil {
return nil, code, err
}
return c.compile(code, filename, pre, post, strict, false)
}
return nil, src, err
}
pgm, err := goja.CompileAST(ast, strict)
return pgm, code, err
}
87 changes: 87 additions & 0 deletions js/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"strings"
"testing"

"github.com/dop251/goja"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -56,3 +57,89 @@ func TestTransform(t *testing.T) {
// assert.Equal(t, "aAAA,SAASA,GAAT,CAAaC,CAAb,EAAgBC,CAAhB,EAAmB;AACf,WAAOD,IAAIC,CAAX;AACH;;AAED,IAAIC,MAAMH,IAAI,CAAJ,EAAO,CAAP,CAAV", srcmap.Mappings)
})
}

func TestCompile(t *testing.T) {
c, err := New()
if !assert.NoError(t, err) {
return
}
t.Run("ES5", func(t *testing.T) {
src := `1+(function() { return 2; })()`
pgm, code, err := c.Compile(src, "script.js", "", "", true)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, src, code)
v, err := goja.New().RunProgram(pgm)
if assert.NoError(t, err) {
assert.Equal(t, int64(3), v.Export())
}

t.Run("Wrap", func(t *testing.T) {
pgm, code, err := c.Compile(src, "script.js", "(function(){return ", "})", true)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `(function(){return 1+(function() { return 2; })()})`, code)
v, err := goja.New().RunProgram(pgm)
if assert.NoError(t, err) {
fn, ok := goja.AssertFunction(v)
if assert.True(t, ok, "not a function") {
v, err := fn(goja.Undefined())
if assert.NoError(t, err) {
assert.Equal(t, int64(3), v.Export())
}
}
}
})

t.Run("Invalid", func(t *testing.T) {
src := `1+(function() { return 2; )()`
_, _, err := c.Compile(src, "script.js", "", "", true)
assert.IsType(t, &goja.Exception{}, err)
assert.EqualError(t, err, `SyntaxError: script.js: Unexpected token (1:26)
> 1 | 1+(function() { return 2; )()
| ^ at <eval>:2:26853(114)`)
})
})
t.Run("ES6", func(t *testing.T) {
pgm, code, err := c.Compile(`1+(()=>2)()`, "script.js", "", "", true)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `"use strict";1 + function () {return 2;}();`, code)
v, err := goja.New().RunProgram(pgm)
if assert.NoError(t, err) {
assert.Equal(t, int64(3), v.Export())
}

t.Run("Wrap", func(t *testing.T) {
pgm, code, err := c.Compile(`fn(1+(()=>2)())`, "script.js", "(function(fn){", "})", true)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `(function(fn){"use strict";fn(1 + function () {return 2;}());})`, code)
rt := goja.New()
v, err := rt.RunProgram(pgm)
if assert.NoError(t, err) {
fn, ok := goja.AssertFunction(v)
if assert.True(t, ok, "not a function") {
var out interface{}
_, err := fn(goja.Undefined(), rt.ToValue(func(v goja.Value) {
out = v.Export()
}))
assert.NoError(t, err)
assert.Equal(t, int64(3), out)
}
}
})

t.Run("Invalid", func(t *testing.T) {
_, _, err := c.Compile(`1+(=>2)()`, "script.js", "", "", true)
assert.IsType(t, &goja.Exception{}, err)
assert.EqualError(t, err, `SyntaxError: script.js: Unexpected token (1:3)
> 1 | 1+(=>2)()
| ^ at <eval>:2:26853(114)`)
})
})
}
8 changes: 8 additions & 0 deletions js/compiler/default.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package compiler

import (
"github.com/dop251/goja"
)

func init() {
c, err := New()
if err != nil {
Expand All @@ -13,3 +17,7 @@ var DefaultCompiler *Compiler
func Transform(src, filename string) (code string, srcmap SourceMap, err error) {
return DefaultCompiler.Transform(src, filename)
}

func Compile(src, filename string, pre, post string, strict bool) (*goja.Program, string, error) {
return DefaultCompiler.Compile(src, filename, pre, post, strict)
}
18 changes: 9 additions & 9 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,38 +115,38 @@ func (i *InitContext) requireFile(name string) (goja.Value, error) {
i.pwd = loader.Dir(filename)
defer func() { i.pwd = pwd }()

// Swap the importing scope's imports out, then put it back again.
// Swap the importing scope's exports out, then put it back again.
oldExports := i.runtime.Get("exports")
defer i.runtime.Set("exports", oldExports)
oldModule := i.runtime.Get("module")
defer i.runtime.Set("module", oldModule)

exports := i.runtime.NewObject()
i.runtime.Set("exports", exports)
module := i.runtime.NewObject()
_ = module.Set("exports", exports)
i.runtime.Set("module", module)

// Read sources, transform into ES6 and cache the compiled program.
// First, check if we have a cached program already.
pgm, ok := i.programs[filename]
if !ok {
// Load the sources; the loader takes care of remote loading, etc.
data, err := loader.Load(i.fs, pwd, name)
if err != nil {
return goja.Undefined(), err
}
src, _, err := compiler.Transform(string(data.Data), data.Filename)
src = "(function(){" + src + "})()"
if err != nil {
return goja.Undefined(), err
}
pgm_, err := goja.Compile(data.Filename, src, true)

// Compile the sources; this handles ES5 vs ES6 automatically.
pgm_, src, err := compiler.Compile(string(data.Data), data.Filename, "(function(){", "})()", true)
if err != nil {
return goja.Undefined(), err
}

// Cache the compiled program.
pgm = programWithSource{pgm_, src}
i.programs[filename] = pgm
}

// Run the program.
if _, err := i.runtime.RunProgram(pgm.pgm); err != nil {
return goja.Undefined(), err
}
Expand Down

0 comments on commit d51c3ea

Please sign in to comment.