func Compile(ctx context.Context, source []byte, mapping bool) ([]byte, error) { vos := vosctx.FromContext(ctx) options := &build.Options{ GOROOT: vos.Getenv("GOROOT"), GOPATH: vos.Getenv("GOPATH"), CreateMapFile: mapping, } s := build.NewSession(options) packages := make(map[string]*compiler.Archive) importContext := &compiler.ImportContext{ Packages: s.Types, Import: s.BuildImportPath, } fileSet := token.NewFileSet() file, err := parser.ParseFile(fileSet, "prog.go", source, parser.ParseComments) if err != nil { return nil, kerr.Wrap("NCYFEKGCWX", err) } mainPkg, err := compiler.Compile("main", []*ast.File{file}, fileSet, importContext, false) if err != nil { return nil, kerr.Wrap("KPHUKOLTBX", err) } packages["main"] = mainPkg bufCode := bytes.NewBuffer(nil) filter := &compiler.SourceMapFilter{Writer: bufCode} allPkgs, err := compiler.ImportDependencies(mainPkg, importContext.Import) if err != nil { return nil, kerr.Wrap("TIMQHFQTWL", err) } if mapping { bufMap := bytes.NewBuffer(nil) smap := &sourcemap.Map{File: "script.js"} filter.MappingCallback = build.NewMappingCallback(smap, options.GOROOT, options.GOPATH, false) if err := compiler.WriteProgramCode(allPkgs, filter); err != nil { return nil, kerr.Wrap("YKQEKRKBPL", err) } if err := smap.WriteTo(bufMap); err != nil { return nil, kerr.Wrap("VYQGYAAADG", err) } return bufMap.Bytes(), nil } if err := compiler.WriteProgramCode(allPkgs, filter); err != nil { return nil, kerr.Wrap("DPPVHCOTBQ", err) } if _, err := bufCode.WriteString("//# sourceMappingURL=script.js.map\n"); err != nil { return nil, kerr.Wrap("CXXKWQVGUI", err) } return bufCode.Bytes(), nil }
// 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 := build.NewSession(b.options) archive, err := compiler.Compile(b.pkgName, files, fileSet, s.ImportContext, b.options.Minify) if err != nil { return ErrorCompiling(err.Error()) } deps, err := compiler.ImportDependencies(archive, s.ImportContext.Import) if err != nil { return ErrorImportingDependencies(err.Error()) } return compiler.WriteProgramCode(deps, &compiler.SourceMapFilter{Writer: b.target}) }
// BuildJSDir builds the js file and returns the content. // goPkgPath must be a package path eg. github.com/influx6/haiku-examples/app func BuildJSDir(jsession *JSSession, dir, importpath, name string, js, jsmap *bytes.Buffer) error { session, options := jsession.Session, jsession.Option buildpkg, err := build.NewBuildContext(session.InstallSuffix(), options.BuildTags).ImportDir(dir, 0) if err != nil { return err } pkg := &build.PackageData{Package: buildpkg} pkg.ImportPath = importpath //build the package using the sessios if err = session.BuildPackage(pkg); err != nil { return err } //build up the source map also smfilter := &compiler.SourceMapFilter{Writer: js} smsrc := &sourcemap.Map{File: name + ".js"} smfilter.MappingCallback = build.NewMappingCallback(smsrc, options.GOROOT, options.GOPATH) deps, err := compiler.ImportDependencies(pkg.Archive, session.ImportContext.Import) if err != nil { return err } err = compiler.WriteProgramCode(deps, smfilter) smsrc.WriteTo(jsmap) js.WriteString("//# sourceMappingURL=" + name + ".map.js\n") return nil }
func (s *Session) WriteCommandPackage(pkg *PackageData, pkgObj string) error { if !pkg.IsCommand() || pkg.UpToDate { return nil } if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil { return err } codeFile, err := os.Create(pkgObj) if err != nil { return err } defer codeFile.Close() sourceMapFilter := &compiler.SourceMapFilter{Writer: codeFile} if s.options.CreateMapFile { m := sourcemap.Map{File: filepath.Base(pkgObj)} mapFile, err := os.Create(pkgObj + ".map") if err != nil { return err } defer func() { m.WriteTo(mapFile) mapFile.Close() fmt.Fprintf(codeFile, "//# sourceMappingURL=%s.map\n", filepath.Base(pkgObj)) }() sourceMapFilter.MappingCallback = func(generatedLine, generatedColumn int, fileSet *token.FileSet, originalPos token.Pos) { if !originalPos.IsValid() { m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn}) return } pos := fileSet.Position(originalPos) file := pos.Filename switch { case strings.HasPrefix(file, s.options.GOPATH): file = filepath.ToSlash(filepath.Join("/gopath", file[len(s.options.GOPATH):])) case strings.HasPrefix(file, s.options.GOROOT): file = filepath.ToSlash(filepath.Join("/goroot", file[len(s.options.GOROOT):])) default: file = filepath.Base(file) } m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn, OriginalFile: file, OriginalLine: pos.Line, OriginalColumn: pos.Column}) } } deps, err := s.ImportDependencies(pkg.Archive) if err != nil { return err } compiler.WriteProgramCode(deps, s.ImportContext, sourceMapFilter) return nil }
// BuildJS builds the js file and returns the content. // goPkgPath must be a package path eg. github.com/influx6/haiku-examples/app func BuildJS(jsession *JSSession, goPkgPath, name string, js, jsmap *bytes.Buffer) error { session, options := jsession.Session, jsession.Option //get the build path buildpkg, err := build.Import(goPkgPath, 0, session.InstallSuffix(), options.BuildTags) if err != nil { return err } if buildpkg.Name != "main" { return ErrNotMain } //build the package data for building // pkg := &build.PackageData{Package: buildpkg} //build the package using the sessios if err = session.BuildPackage(buildpkg); err != nil { return err } //build up the source map also smfilter := &compiler.SourceMapFilter{Writer: js} smsrc := &sourcemap.Map{File: name + ".js"} smfilter.MappingCallback = build.NewMappingCallback(smsrc, options.GOROOT, options.GOPATH) deps, err := compiler.ImportDependencies(buildpkg.Archive, session.ImportContext.Import) if err != nil { return err } err = compiler.WriteProgramCode(deps, smfilter) smsrc.WriteTo(jsmap) js.WriteString("//# sourceMappingURL=" + name + ".map.js\n") return nil }
func (s *Session) WriteCommandPackage(archive *compiler.Archive, pkgObj string) error { if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil { return err } codeFile, err := os.Create(pkgObj) if err != nil { return err } defer codeFile.Close() sourceMapFilter := &compiler.SourceMapFilter{Writer: codeFile} if s.options.CreateMapFile { m := &sourcemap.Map{File: filepath.Base(pkgObj)} mapFile, err := os.Create(pkgObj + ".map") if err != nil { return err } defer func() { m.WriteTo(mapFile) mapFile.Close() fmt.Fprintf(codeFile, "//# sourceMappingURL=%s.map\n", filepath.Base(pkgObj)) }() sourceMapFilter.MappingCallback = NewMappingCallback(m, s.options.GOROOT, s.options.GOPATH) } deps, err := compiler.ImportDependencies(archive, func(path string) (*compiler.Archive, error) { if archive, ok := s.Archives[path]; ok { return archive, nil } _, archive, err := s.buildImportPathWithSrcDir(path, "") return archive, err }) if err != nil { return err } return compiler.WriteProgramCode(deps, sourceMapFilter) }
func (s *Session) WriteCommandPackage(pkg *PackageData, pkgObj string) error { if !pkg.IsCommand() || pkg.UpToDate { return nil } if err := os.MkdirAll(filepath.Dir(pkgObj), 0777); err != nil { return err } codeFile, err := os.Create(pkgObj) if err != nil { return err } defer codeFile.Close() sourceMapFilter := &compiler.SourceMapFilter{Writer: codeFile} if s.options.CreateMapFile { m := &sourcemap.Map{File: filepath.Base(pkgObj)} mapFile, err := os.Create(pkgObj + ".map") if err != nil { return err } defer func() { m.WriteTo(mapFile) mapFile.Close() fmt.Fprintf(codeFile, "//# sourceMappingURL=%s.map\n", filepath.Base(pkgObj)) }() sourceMapFilter.MappingCallback = NewMappingCallback(m, s.options.GOROOT, s.options.GOPATH) } deps, err := compiler.ImportDependencies(pkg.Archive, s.BuildImportPath) if err != nil { return err } return compiler.WriteProgramCode(deps, sourceMapFilter) }
func (fs serveCommandFileSystem) Open(requestName string) (http.File, error) { name := path.Join(fs.serveRoot, requestName[1:]) // requestName[0] == '/' dir, file := path.Split(name) base := path.Base(dir) // base is parent folder name, which becomes the output file name. isPkg := file == base+".js" isMap := file == base+".js.map" isIndex := file == "index.html" if isPkg || isMap || isIndex { // If we're going to be serving our special files, make sure there's a Go command in this folder. s := gbuild.NewSession(fs.options) pkg, err := gbuild.Import(path.Dir(name), 0, s.InstallSuffix(), fs.options.BuildTags) if err != nil || pkg.Name != "main" { isPkg = false isMap = false isIndex = false } switch { case isPkg: buf := bytes.NewBuffer(nil) browserErrors := bytes.NewBuffer(nil) exitCode := handleError(func() error { archive, err := s.BuildPackage(pkg) if err != nil { return err } sourceMapFilter := &compiler.SourceMapFilter{Writer: buf} m := &sourcemap.Map{File: base + ".js"} sourceMapFilter.MappingCallback = gbuild.NewMappingCallback(m, fs.options.GOROOT, fs.options.GOPATH) deps, err := compiler.ImportDependencies(archive, s.BuildImportPath) if err != nil { return err } if err := compiler.WriteProgramCode(deps, sourceMapFilter); err != nil { return err } mapBuf := bytes.NewBuffer(nil) m.WriteTo(mapBuf) buf.WriteString("//# sourceMappingURL=" + base + ".js.map\n") fs.sourceMaps[name+".map"] = mapBuf.Bytes() return nil }, fs.options, browserErrors) if exitCode != 0 { buf = browserErrors } return newFakeFile(base+".js", buf.Bytes()), nil case isMap: if content, ok := fs.sourceMaps[name]; ok { return newFakeFile(base+".js.map", content), nil } } } for _, d := range fs.dirs { dir := http.Dir(filepath.Join(d, "src")) f, err := dir.Open(name) if err == nil { return f, nil } // source maps are served outside of serveRoot f, err = dir.Open(requestName) if err == nil { return f, nil } } if isIndex { // If there was no index.html file in any dirs, supply our own. return newFakeFile("index.html", []byte(`<html><head><meta charset="utf-8"><script src="`+base+`.js"></script></head></html>`)), nil } return nil, os.ErrNotExist }
func (fs serveCommandFileSystem) Open(name string) (http.File, error) { for _, d := range fs.dirs { file, err := http.Dir(filepath.Join(d, "src")).Open(name) if err == nil { return file, nil } } if strings.HasSuffix(name, "/main.js.map") { if content, ok := fs.sourceMaps[name]; ok { return newFakeFile("main.js.map", content), nil } } isIndex := strings.HasSuffix(name, "/index.html") isMain := strings.HasSuffix(name, "/main.js") if isIndex || isMain { s := gbuild.NewSession(fs.options) buildPkg, err := gbuild.Import(path.Dir(name[1:]), 0, s.InstallSuffix(), fs.options.BuildTags) if err != nil || buildPkg.Name != "main" { return nil, os.ErrNotExist } if isIndex { return newFakeFile("index.html", []byte(`<html><head><meta charset="utf-8"><script src="main.js"></script></head></html>`)), nil } if isMain { buf := bytes.NewBuffer(nil) browserErrors := bytes.NewBuffer(nil) exitCode := handleError(func() error { pkg := &gbuild.PackageData{Package: buildPkg} if err := s.BuildPackage(pkg); err != nil { return err } sourceMapFilter := &compiler.SourceMapFilter{Writer: buf} m := &sourcemap.Map{File: "main.js"} sourceMapFilter.MappingCallback = gbuild.NewMappingCallback(m, fs.options.GOROOT, fs.options.GOPATH) deps, err := compiler.ImportDependencies(pkg.Archive, s.ImportContext.Import) if err != nil { return err } if err := compiler.WriteProgramCode(deps, sourceMapFilter); err != nil { return err } mapBuf := bytes.NewBuffer(nil) m.WriteTo(mapBuf) buf.WriteString("//# sourceMappingURL=main.js.map\n") fs.sourceMaps[name+".map"] = mapBuf.Bytes() return nil }, fs.options, browserErrors) if exitCode != 0 { buf = browserErrors } return newFakeFile("main.js", buf.Bytes()), nil } } return nil, os.ErrNotExist }
func main() { var location = dom.GetWindow().Top().Location() // We might be inside an iframe, but want to use the location of topmost window. codeReady := make(chan struct{}) // Used to synchronize when "code" value is ready. app := angularjs.NewModule("playground", nil, nil) app.NewController("PlaygroundCtrl", func(scope *angularjs.Scope) { if strings.HasPrefix(location.Hash, "#/") { id := location.Hash[2:] req := xhr.NewRequest("GET", "http://"+snippetStoreHost+"/p/"+id) req.ResponseType = xhr.ArrayBuffer go func() { err := req.Send(nil) if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to load snippet "` + id + `"`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) scope.Apply(func() { scope.Set("code", string(data)) close(codeReady) }) }() } else { scope.Set("code", "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gopherjs/gopherjs/js\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n\tjs.Global.Call(\"alert\", \"Hello, JavaScript\")\n\tprintln(\"Hello, JS console\")\n}\n") close(codeReady) } scope.Set("shareUrl", "") scope.Set("showShareUrl", false) packages := make(map[string]*compiler.Archive) var pkgsToLoad map[string]struct{} importContext := compiler.NewImportContext(func(path string) (*compiler.Archive, error) { if pkg, found := packages[path]; found { return pkg, nil } pkgsToLoad[path] = struct{}{} return &compiler.Archive{}, nil }) fileSet := token.NewFileSet() pkgsReceived := 0 setupEnvironment(scope) codeArea := angularjs.ElementById("code") codeArea.On("input", func(e *angularjs.Event) { scope.Set("showShareUrl", false) location.Hash = "" }) codeArea.On("keydown", func(e *angularjs.Event) { toInsert := "" switch e.KeyCode { case '\t': toInsert = "\t" case '\r': toInsert = "\n" start := codeArea.Prop("selectionStart").Int() code := scope.Get("code").String() i := strings.LastIndex(code[:start], "\n") + 1 for i < start { c := code[i] if c != ' ' && c != '\t' { break } toInsert += string(c) i++ } } if toInsert != "" { scope.Set("showShareUrl", false) location.Hash = "" start := codeArea.Prop("selectionStart").Int() end := codeArea.Prop("selectionEnd").Int() code := scope.Get("code").String() scope.Apply(func() { scope.Set("code", code[:start]+toInsert+code[end:]) }) codeArea.SetProp("selectionStart", start+len(toInsert)) codeArea.SetProp("selectionEnd", start+len(toInsert)) e.PreventDefault() } }) var run func(bool) run = func(loadOnly bool) { output = nil scope.Set("output", output) pkgsToLoad = make(map[string]struct{}) file, err := parser.ParseFile(fileSet, "prog.go", []byte(scope.Get("code").String()), parser.ParseComments) if err != nil { if list, ok := err.(scanner.ErrorList); ok { for _, entry := range list { output = append(output, Line{"type": "err", "content": entry.Error()}) } scope.Set("output", output) return } scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) return } mainPkg, err := compiler.Compile("main", []*ast.File{file}, fileSet, importContext, false) packages["main"] = mainPkg if err != nil && len(pkgsToLoad) == 0 { if list, ok := err.(compiler.ErrorList); ok { var output []Line for _, entry := range list { output = append(output, Line{"type": "err", "content": entry.Error()}) } scope.Set("output", output) return } scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) return } var allPkgs []*compiler.Archive if len(pkgsToLoad) == 0 { allPkgs, _ = compiler.ImportDependencies(mainPkg, importContext.Import) } if len(pkgsToLoad) != 0 { pkgsReceived = 0 for path := range pkgsToLoad { req := xhr.NewRequest("GET", "pkg/"+path+".a.js") req.ResponseType = xhr.ArrayBuffer go func(path string) { err := req.Send(nil) if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to load package "` + path + `"`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) packages[path], err = compiler.ReadArchive(path+".a", path, bytes.NewReader(data), importContext.Packages) if err != nil { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) }) return } pkgsReceived++ if pkgsReceived == len(pkgsToLoad) { run(loadOnly) } }(path) } return } if loadOnly { return } jsCode := bytes.NewBuffer(nil) jsCode.WriteString("try{\n") compiler.WriteProgramCode(allPkgs, &compiler.SourceMapFilter{Writer: jsCode}) jsCode.WriteString("} catch (err) {\ngoPanicHandler(err.message);\n}\n") js.Global.Set("$checkForDeadlock", true) js.Global.Call("eval", js.InternalObject(jsCode.String())) } scope.Set("run", run) go func() { <-codeReady // Wait for "code" value to be ready. run(true) }() scope.Set("format", func() { out, err := format.Source([]byte(scope.Get("code").String())) if err != nil { scope.Set("output", []Line{Line{"type": "err", "content": err.Error()}}) return } scope.Set("code", string(out)) scope.Set("output", []Line{}) }) scope.Set("share", func() { req := xhr.NewRequest("POST", "http://"+snippetStoreHost+"/share") req.ResponseType = xhr.ArrayBuffer go func() { err := req.Send([]byte(scope.Get("code").String())) // Send as binary. if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to share snippet`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) scope.Apply(func() { id := string(data) location.Hash = "#/" + id scope.Set("shareUrl", location.String()) scope.Set("showShareUrl", true) // TODO: Do this better using AngularJS. // Perhaps using http://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field/18295416. go func() { time.Sleep(time.Millisecond) dom.GetWindow().Document().GetElementByID("share-url").(*dom.HTMLInputElement).Select() }() }) }() }) // Start watching for hashchange events, and reload snippet if it happens. dom.GetWindow().Top().AddEventListener("hashchange", false, func(event dom.Event) { event.PreventDefault() if strings.HasPrefix(location.Hash, "#/") { id := location.Hash[2:] req := xhr.NewRequest("GET", "http://"+snippetStoreHost+"/p/"+id) req.ResponseType = xhr.ArrayBuffer go func() { err := req.Send(nil) if err != nil || req.Status != 200 { scope.Apply(func() { scope.Set("output", []Line{Line{"type": "err", "content": `failed to load snippet "` + id + `"`}}) }) return } data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte) scope.Apply(func() { scope.Set("code", string(data)) }) }() } }) }) }
func control(scope *angularjs.Scope) { scope.Set("code", initCode) // scope.Set("showGenerated", false) // scope.Set("generated", `(generated code will be shown here after clicking "Run")`) packages := make(map[string]*compiler.Archive) var pkgsToLoad []string importContext := compiler.NewImportContext( func(path string) (*compiler.Archive, error) { if pkg, found := packages[path]; found { return pkg, nil } pkgsToLoad = append(pkgsToLoad, path) return &compiler.Archive{}, nil }, ) fileSet := token.NewFileSet() pkgsReceived := 0 setupEnvironment(scope) var run func(bool) run = func(loadOnly bool) { output = nil scope.Set("output", output) pkgsToLoad = nil file, err := parser.ParseFile(fileSet, "prog.go", getCode(), parser.ParseComments, ) if err != nil { if list, ok := err.(scanner.ErrorList); ok { for _, entry := range list { output = append(output, errErrLine(entry)) } scope.Set("output", output) return } scope.Set("output", []Line{errErrLine(err)}) return } mainPkg, err := compiler.Compile("main", []*ast.File{file}, fileSet, importContext, false, ) packages["main"] = mainPkg if err != nil && len(pkgsToLoad) == 0 { if list, ok := err.(compiler.ErrorList); ok { output := make([]Line, 0) for _, entry := range list { output = append(output, errErrLine(entry)) } scope.Set("output", output) return } scope.Set("output", []Line{errErrLine(err)}) return } var allPkgs []*compiler.Archive if len(pkgsToLoad) == 0 { for _, depPath := range mainPkg.Dependencies { dep, _ := importContext.Import(string(depPath)) allPkgs = append(allPkgs, dep) } allPkgs = append(allPkgs, mainPkg) } if len(pkgsToLoad) != 0 { pkgsReceived = 0 for _, p := range pkgsToLoad { path := p req := js.Global.Get("XMLHttpRequest").New() req.Call("open", "GET", "pkg/"+path+".a", true) req.Set("responseType", "arraybuffer") req.Set("onload", func() { if req.Get("status").Int() != 200 { f := func() { emsg := fmt.Sprintf("cannot load package \"%s\"", path) scope.Set("output", []Line{errLine(emsg)}) } scope.Apply(f) return } data := js.Global.Get("Uint8Array").New(req.Get("response")).Interface().([]byte) packages[path], err = compiler.UnmarshalArchive( path+".a", path, []byte(data), importContext, ) if err != nil { scope.Apply(func() { scope.Set("output", []Line{errErrLine(err)}) }) return } pkgsReceived++ if pkgsReceived == len(pkgsToLoad) { run(loadOnly) } }) req.Call("send") } return } if loadOnly { return } mainPkgCode := bytes.NewBuffer(nil) compiler.WritePkgCode(packages["main"], false, &compiler.SourceMapFilter{Writer: mainPkgCode}, ) // scope.Set("generated", mainPkgCode.String()) jsCode := bytes.NewBuffer(nil) jsCode.WriteString("try{\n") compiler.WriteProgramCode(allPkgs, importContext, &compiler.SourceMapFilter{Writer: jsCode}, ) jsCode.WriteString("} catch (err) {\ngoPanicHandler(err.message);\n}\n") js.Global.Call("eval", js.InternalObject(jsCode.String())) } scope.Set("run", run) run(true) scope.Set("format", func() { out, err := format.Source(getCode()) if err != nil { scope.Set("output", []Line{errErrLine(err)}) return } setCode(string(out)) scope.Set("output", []Line{}) }) }