1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
/*
Package gopherjslib provides helpers for in-process GopherJS compilation.
All of them take the optional *Options argument. It can be used to set
a different GOROOT or GOPATH directory or to enable minification.
Example compiling Go code:
import "github.com/shurcooL/gopherjslib"
...
code := strings.NewReader(`
package main
import "github.com/gopherjs/gopherjs/js"
func main() { println(js.Global.Get("window")) }
`)
var out bytes.Buffer
err := gopherjslib.Build(code, &out, nil) // <- default options
Example compiling multiple files:
var out bytes.Buffer
builder := gopherjslib.NewBuilder(&out, nil)
fileA := strings.NewReader(`
package main
import "github.com/gopherjs/gopherjs/js"
func a() { println(js.Global.Get("window")) }
`)
builder.Add("a.go", fileA)
// And so on for each file, then:
err = builder.Build()
*/
package gopherjslib
import (
"bytes"
"go/ast"
"go/parser"
"go/token"
"io"
"os"
"path/filepath"
"github.com/gopherjs/gopherjs/build"
"github.com/gopherjs/gopherjs/compiler"
)
// Options is the subset of build.Options, that is exposed to the user of gopherjslib
// and is optional.
type Options struct {
GOROOT string // defaults to build.Default.GOROOT
GOPATH string // defaults to build.Default.GOPATH
Minify bool // should the js be minified
}
// toBuildOptions converts to the real build options in build
func (o *Options) toBuildOptions() *build.Options {
b := &build.Options{}
if o != nil {
b.GOROOT = o.GOROOT
b.GOPATH = o.GOPATH
b.Minify = o.Minify
}
return b
}
// BuildPackage builds JavaScript based on the go package in dir, writing the result to target.
// Note that dir is not relative to any GOPATH, but relative to the working directory.
// The first error during the built is returned.
// dir must be an existing directory containing at least one go file
//
// target must not be nil
// options may be nil (defaults)
func BuildPackage(dir string, target io.Writer, options *Options) error {
b, err := NewPackageBuilder(dir, target, options)
if err != nil {
return err
}
return b.Build()
}
// Build builds JavaScript based on the go code in reader, writing the result to target.
// The first error during the built is returned. All errors are typed.
//
// reader must not be nil
// target must not be nil
// options may be nil (defaults)
func Build(reader io.Reader, target io.Writer, options *Options) error {
pb := NewBuilder(target, options)
pb.Add("main.go", reader)
return pb.Build()
}
// Builder builds from added files
type Builder interface {
// Add adds the content of reader for the given filename
Add(filename string, reader io.Reader) Builder
// Build builds and returns the first error during the built or <nil>
Build() error
}
// builder is an implementation of the Builder interface
type builder struct {
files map[string]io.Reader
options *build.Options
target io.Writer
pkgName string
}
// NewBuilder creates a new Builder that will write to target.
//
// target must not be nil
// options may be nil (defaults)
func NewBuilder(target io.Writer, options *Options) Builder {
return &builder{
files: map[string]io.Reader{},
options: options.toBuildOptions(),
target: target,
pkgName: "main", // default, changed by BuildPackage
}
}
// NewPackageBuilder creates a new Builder based on the go package in dir.
func NewPackageBuilder(dir string, target io.Writer, options *Options) (Builder, error) {
abs, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
b := NewBuilder(target, options).(*builder)
b.pkgName = abs
files, err := filepath.Glob(filepath.Join(dir, "*.go"))
if err != nil {
return nil, err
}
var f *os.File
for _, file := range files {
f, err = os.Open(filepath.Join(dir, filepath.Base(file)))
if err != nil {
return nil, err
}
// make a copy in order to be able to close the file
var buf bytes.Buffer
_, err = io.Copy(&buf, f)
if err != nil {
f.Close()
return nil, err
}
b.Add(filepath.Base(file), &buf)
f.Close()
}
return b, nil
}
// Add adds a file with the given filename and the content of reader to the builder
func (b *builder) Add(filename string, reader io.Reader) Builder {
b.files[filename] = reader
return b
}
// Build creates a build and returns the first error happening. All errors are typed.
func (b *builder) Build() error {
if b.target == nil {
return ErrorMissingTarget{}
}
fileSet := token.NewFileSet()
files := []*ast.File{}
for name, reader := range b.files {
if reader == nil {
return ErrorParsing{name, "reader must not be nil"}
}
f, err := parser.ParseFile(fileSet, name, reader, 0)
if err != nil {
return ErrorParsing{name, err.Error()}
}
files = append(files, f)
}
s, err := build.NewSession(b.options)
if err != nil {
return err
}
importContext := &compiler.ImportContext{
Packages: s.Types,
Import: s.BuildImportPath,
}
archive, err := compiler.Compile(b.pkgName, files, fileSet, importContext, b.options.Minify)
if err != nil {
return ErrorCompiling(err.Error())
}
deps, err := compiler.ImportDependencies(archive, s.BuildImportPath)
if err != nil {
return ErrorImportingDependencies(err.Error())
}
return compiler.WriteProgramCode(deps, &compiler.SourceMapFilter{Writer: b.target})
}
|