Skip to content

Commit

Permalink
Evaluator with amd module support (#5)
Browse files Browse the repository at this point in the history
* Added AMD-based almond module loader and evaluator that can evaluate scripts and transpile them if needed

* Allow transpiler to not use context cancellation (handle cancellation in the runtime)

* Added transpiler option for custom typescript source

* Removed examples directory and updated readme

* Added support for Typescript v4.2.4

* Added support for pre-evaluation script modifier hooks and custom evaluation runtimes

Co-authored-by: Clark McCauley <clark.mccauley@printtrackerpro.com>
  • Loading branch information
clarkmcc and clarkmcc committed May 20, 2021
1 parent a5193e1 commit 793d7c0
Show file tree
Hide file tree
Showing 13 changed files with 444 additions and 149 deletions.
69 changes: 44 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Goja Typescript Transpiler
This package provides a simple interface using [github.com/dop251/goja](github.com/dop251/goja) under the hood to allow you to transpile Typescript to Javascript in Go. This package has no direct dependencies besides testing utilities and has a 95% test coverage rate.
# Goja Typescript Transpiler and Evaluator (with AMD module support)
This package provides a simple interface using [github.com/dop251/goja](github.com/dop251/goja) under the hood to allow you to transpile Typescript to Javascript in Go. In addition it provides an evaluator with a built-in AMD module loader which allows you to run Typescript code against a compiled typescript bundle. This package has no direct dependencies besides testing utilities and has a 95% test coverage rate.

Feel free to contribute. This package is fresh and may experience some changes before it's first tagged release.

## Example
For more examples, see the `examples/` directory of this repository
## Transpiling Examples
### Transpile Strings
```go
output, err := typescript.TranspileString("let a: number = 10;", nil)
Expand All @@ -19,47 +18,67 @@ output, err := typescript.Transpile(reader, nil)
### Custom Typescript Compile Options
You can optionally specify alternative compiler options that are used by Typescript. Any of the options [https://www.typescriptlang.org/docs/handbook/compiler-options.html](https://www.typescriptlang.org/docs/handbook/compiler-options.html) can be added.
```go
output, err = typescript.TranspileString(script, nil, typescript.WithCompileOptions(map[string]interface{}{
output, err = typescript.TranspileString(script, typescript.WithCompileOptions(map[string]interface{}{
"module": "none",
"strict": true,
}))
```

### Custom Typescript Version
#### Default Registry
You can optionally specify which typescript version you want to compile using. These versions are based on the Git tags from the Typescript repository. If you're using a version that is supported in this package, you'll need to import the version package as a side-effect and will automatically be registered to the default registry.
```go
import _ "github.com/clarkmcc/go-typescript/versions/v4.2.2"

func main() {
output, err := typescript.Transpile(reader, nil, typescript.WithVersion("v4.2.2"))
output, err := typescript.Transpile(reader, typescript.WithVersion("v4.2.2"))
}
```

#### Custom Registry
You may want to use a custom version registry rather than the default registry.
### Custom Typescript Source
You may want to use a custom typescript version.

```go
import version "github.com/clarkmcc/go-typescript/versions/v4.2.2"

func main() {
registry := versions.NewRegistry()
registry.MustRegister("v4.2.3", version.Source)

output, err := typescript.TranspileString("let a:number = 10;", &typescript.Config{
TypescriptSource: program,
})
output, err := typescript.TranspileString("let a:number = 10;",
WithTypescriptSource("/* source code for typescript*/"))
}
```

#### Custom Version
Need a different typescript version than the tags we support in this repo? No problem, you can load your own:
## Evaluate Examples
### Basic Evaluation
You can evaluate pure Javascript code with:

```go
result, err := Evaluate(strings.NewReader('var a = 10;')) // returns 10;
```

### Transpile and Evaluate
Or you can transpile first:

```go
program, err := goja.Compile("typescript", "<typescript source code here>", true)
output, err := typescript.Transpile(reader, &typescript.Config{
CompileOptions: map[string]interface{}{},
TypescriptSource: program,
Runtime: goja.New(),
})
result, err := Evaluate(strings.NewReader('let a: number = 10;'), WithTranspile()) // returns 10;
```

### Run Script with AMD Modules
You can load in an AMD module bundle, then execute a Typescript script with access to the modules.

```go
// This is the module we're going to import
modules := strings.TrimSpace(`
define("myModule", ["exports"], function (exports, core_1) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.multiply = void 0;
var multiply = function (a, b) { return a * b; };
exports.multiply = multiply;
});
`)

// This is the script we're going to transpile and evaluate
script := "import { multiply } from 'myModule'; multiply(5, 5)"

// Returns 25
result, err := EvaluateCtx(context.Background(), strings.NewReader(script),
WithAlmondModuleLoader(),
WithTranspile(),
WithEvaluateBefore(strings.NewReader(amdModuleScript)))
```
52 changes: 44 additions & 8 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@ import (
"fmt"
"github.com/clarkmcc/go-typescript/utils"
"github.com/clarkmcc/go-typescript/versions"
_ "github.com/clarkmcc/go-typescript/versions/v4.2.3"
_ "github.com/clarkmcc/go-typescript/versions/v4.2.4"
"github.com/dop251/goja"
)

// OptionFunc allows for easy chaining of pre-built config modifiers such as WithVersion.
type OptionFunc func(*Config)
// TranspileOptionFunc allows for easy chaining of pre-built config modifiers such as WithVersion.
type TranspileOptionFunc func(*Config)

// Config defines the behavior of the typescript compiler.
type Config struct {
CompileOptions map[string]interface{}
TypescriptSource *goja.Program
Runtime *goja.Runtime

// If a module is exported by the typescript compiler, this is the name the module will be called
ModuleName string

// Verbose enables built-in verbose logging for debugging purposes.
Verbose bool

// PreventCancellation indicates that the transpiler should not handle context cancellation. This
// should be used when external runtimes are configured AND cancellation is handled by those runtimes.
PreventCancellation bool

// decoderName refers to a random generated string assigned to a function in the runtimes
// global scope which is analogous to atob(), or a base64 decoding function. This function
// is needed in the transpile process to ensure that we don't have any issues with string
Expand Down Expand Up @@ -49,34 +60,59 @@ func NewDefaultConfig() *Config {
return &Config{
Runtime: goja.New(),
CompileOptions: nil,
TypescriptSource: versions.DefaultRegistry.MustGet("v4.2.3"),
TypescriptSource: versions.DefaultRegistry.MustGet("v4.2.4"),
ModuleName: "default",
}
}

// WithVersion loads the provided tagged typescript source from the default registry
func WithVersion(tag string) OptionFunc {
func WithVersion(tag string) TranspileOptionFunc {
return func(config *Config) {
config.TypescriptSource = versions.DefaultRegistry.MustGet(tag)
}
}

// WithTypescriptSource configures a Typescript source from the provided typescript source string which
// is compiled by goja when the config is initialized. This function will panic if the Typescript source
// is invalid.
func WithTypescriptSource(src string) TranspileOptionFunc {
return func(config *Config) {
config.TypescriptSource = goja.MustCompile("", src, true)
}
}

// WithCompileOptions sets the compile options that will be passed to the typescript compiler.
func WithCompileOptions(options map[string]interface{}) OptionFunc {
func WithCompileOptions(options map[string]interface{}) TranspileOptionFunc {
return func(config *Config) {
config.CompileOptions = options
}
}

// WithRuntime allows you to over-ride the default runtime
func WithRuntime(runtime *goja.Runtime) OptionFunc {
func WithRuntime(runtime *goja.Runtime) TranspileOptionFunc {
return func(config *Config) {
config.Runtime = runtime
}
}

// WithModuleName determines the module name applied to the typescript module if applicable. This is only needed to
// customize the module name if the typescript module mode is AMD or SystemJS.
func WithModuleName(name string) TranspileOptionFunc {
return func(config *Config) {
config.ModuleName = name
}
}

// WithPreventCancellation prevents the transpiler runtime from handling its own context cancellation.
func WithPreventCancellation() TranspileOptionFunc {
return func(config *Config) {
config.PreventCancellation = true
}
}

// withFailOnInitialize used to test a config initialization failure. This is not exported because
// it's used only for testing.
func withFailOnInitialize() OptionFunc {
func withFailOnInitialize() TranspileOptionFunc {
return func(config *Config) {
config.failOnInitialize = true
}
Expand Down
58 changes: 32 additions & 26 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,58 +25,47 @@ func TestConfig_Initialize(t *testing.T) {

func TestVersionLoading(t *testing.T) {
t.Run("v3.8.3", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v3.8.3"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v3.8.3"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v3.9.9", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v3.9.9"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v3.9.9"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v4.1.2", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v4.1.2"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v4.1.2"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v4.1.3", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v4.1.3"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v4.1.3"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v4.1.4", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v4.1.4"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v4.1.4"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v4.1.5", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v4.1.5"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v4.1.5"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v4.2.2", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v4.2.2"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v4.2.2"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v4.2.3", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: versions.DefaultRegistry.MustGet("v4.2.3"),
})
output, err := TranspileString("let a: number = 10;", WithVersion("v4.2.3"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
t.Run("v4.2.4", func(t *testing.T) {
output, err := TranspileString("let a: number = 10;", WithVersion("v4.2.4"))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
})
Expand All @@ -86,9 +75,26 @@ func TestCustomRegistry(t *testing.T) {
registry := versions.NewRegistry()
registry.MustRegister("v4.2.3", v423.Source)

output, err := TranspileString("let a: number = 10;", &Config{
TypescriptSource: registry.MustGet("v4.2.3"),
output, err := TranspileString("let a: number = 10;", func(config *Config) {
config.TypescriptSource = registry.MustGet("v4.2.3")
})
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
}

func TestWithModuleName(t *testing.T) {
output, err := TranspileString("let a: number = 10;",
WithModuleName("myModuleName"),
WithCompileOptions(map[string]interface{}{
"module": "amd",
}))
require.NoError(t, err)
require.Contains(t, output, "define(\"myModuleName\"")
}

func TestWithTypescriptSource(t *testing.T) {
output, err := TranspileString("let a: number = 10;",
WithTypescriptSource(v423.Source))
require.NoError(t, err)
require.Equal(t, "var a = 10;", output)
}

0 comments on commit 793d7c0

Please sign in to comment.