File: gopherjslib.go

package info (click to toggle)
golang-github-shurcool-gopherjslib 0.0~git20200209.30f639d-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid, trixie
  • size: 68 kB
  • sloc: makefile: 2
file content (215 lines) | stat: -rw-r--r-- 5,317 bytes parent folder | download
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})
}