Skip to content

Commit bb7cade

Browse files
committed
initial implementation of "serve" command (#121)
1 parent 88127b7 commit bb7cade

File tree

2 files changed

+156
-18
lines changed

2 files changed

+156
-18
lines changed

build/build.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ func (s *Session) WriteCommandPackage(pkg *PackageData, pkgObj string) error {
514514

515515
sourceMapFilter := &compiler.SourceMapFilter{Writer: codeFile}
516516
if s.options.CreateMapFile {
517-
m := sourcemap.Map{File: filepath.Base(pkgObj)}
517+
m := &sourcemap.Map{File: filepath.Base(pkgObj)}
518518
mapFile, err := os.Create(pkgObj + ".map")
519519
if err != nil {
520520
return err
@@ -526,22 +526,7 @@ func (s *Session) WriteCommandPackage(pkg *PackageData, pkgObj string) error {
526526
fmt.Fprintf(codeFile, "//# sourceMappingURL=%s.map\n", filepath.Base(pkgObj))
527527
}()
528528

529-
sourceMapFilter.MappingCallback = func(generatedLine, generatedColumn int, originalPos token.Position) {
530-
if !originalPos.IsValid() {
531-
m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn})
532-
return
533-
}
534-
file := originalPos.Filename
535-
switch hasGopathPrefix, prefixLen := hasGopathPrefix(file, s.options.GOPATH); {
536-
case hasGopathPrefix:
537-
file = filepath.ToSlash(filepath.Join("/gopath", file[prefixLen:]))
538-
case strings.HasPrefix(file, s.options.GOROOT):
539-
file = filepath.ToSlash(filepath.Join("/goroot", file[len(s.options.GOROOT):]))
540-
default:
541-
file = filepath.Base(file)
542-
}
543-
m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn, OriginalFile: file, OriginalLine: originalPos.Line, OriginalColumn: originalPos.Column})
544-
}
529+
sourceMapFilter.MappingCallback = NewMappingCallback(m, s.options.GOROOT, s.options.GOPATH)
545530
}
546531

547532
deps, err := compiler.ImportDependencies(pkg.Archive, s.ImportContext.Import)
@@ -551,6 +536,25 @@ func (s *Session) WriteCommandPackage(pkg *PackageData, pkgObj string) error {
551536
return compiler.WriteProgramCode(deps, sourceMapFilter)
552537
}
553538

539+
func NewMappingCallback(m *sourcemap.Map, goroot, gopath string) func(generatedLine, generatedColumn int, originalPos token.Position) {
540+
return func(generatedLine, generatedColumn int, originalPos token.Position) {
541+
if !originalPos.IsValid() {
542+
m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn})
543+
return
544+
}
545+
file := originalPos.Filename
546+
switch hasGopathPrefix, prefixLen := hasGopathPrefix(file, gopath); {
547+
case hasGopathPrefix:
548+
file = filepath.ToSlash(file[prefixLen+4:])
549+
case strings.HasPrefix(file, goroot):
550+
file = filepath.ToSlash(file[len(goroot)+4:])
551+
default:
552+
file = filepath.Base(file)
553+
}
554+
m.AddMapping(&sourcemap.Mapping{GeneratedLine: generatedLine, GeneratedColumn: generatedColumn, OriginalFile: file, OriginalLine: originalPos.Line, OriginalColumn: originalPos.Column})
555+
}
556+
}
557+
554558
// hasGopathPrefix returns true and the length of the matched GOPATH workspace,
555559
// iff file has a prefix that matches one of the GOPATH workspaces.
556560
func hasGopathPrefix(file, gopath string) (hasGopathPrefix bool, prefixLen int) {

tool.go

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import (
88
"go/parser"
99
"go/scanner"
1010
"go/token"
11+
"io"
1112
"io/ioutil"
13+
"net/http"
1214
"os"
1315
"os/exec"
16+
"path"
1417
"path/filepath"
1518
"strings"
1619
"syscall"
@@ -19,6 +22,7 @@ import (
1922

2023
gbuild "github.com/gopherjs/gopherjs/build"
2124
"github.com/gopherjs/gopherjs/compiler"
25+
"github.com/neelance/sourcemap"
2226
"github.com/spf13/cobra"
2327
"github.com/spf13/pflag"
2428
"golang.org/x/crypto/ssh/terminal"
@@ -419,14 +423,144 @@ func main() {
419423
}, options))
420424
}
421425

426+
cmdServe := &cobra.Command{
427+
Use: "serve",
428+
Short: "compile on-the-fly and serve",
429+
}
430+
cmdServe.Flags().AddFlag(flagVerbose)
431+
cmdServe.Flags().AddFlag(flagMinify)
432+
cmdServe.Flags().AddFlag(flagColor)
433+
cmdServe.Flags().AddFlag(flagTags)
434+
var port int
435+
cmdServe.Flags().IntVarP(&port, "port", "p", 6060, "HTTP port")
436+
cmdServe.Run = func(cmd *cobra.Command, args []string) {
437+
dirs := append(filepath.SplitList(build.Default.GOPATH), build.Default.GOROOT)
438+
sourceFiles := http.FileServer(serveCommandFileSystem{options: options, dirs: dirs, sourceMaps: make(map[string][]byte)})
439+
fmt.Printf("serving at http://localhost:%d\n", port)
440+
fmt.Println(http.ListenAndServe(fmt.Sprintf(":%d", port), sourceFiles))
441+
}
442+
422443
rootCmd := &cobra.Command{
423444
Use: "gopherjs",
424445
Long: "GopherJS is a tool for compiling Go source code to JavaScript.",
425446
}
426-
rootCmd.AddCommand(cmdBuild, cmdGet, cmdInstall, cmdRun, cmdTest, cmdTool)
447+
rootCmd.AddCommand(cmdBuild, cmdGet, cmdInstall, cmdRun, cmdTest, cmdTool, cmdServe)
427448
rootCmd.Execute()
428449
}
429450

451+
type serveCommandFileSystem struct {
452+
options *gbuild.Options
453+
dirs []string
454+
sourceMaps map[string][]byte
455+
}
456+
457+
func (fs serveCommandFileSystem) Open(name string) (http.File, error) {
458+
for _, d := range fs.dirs {
459+
file, err := http.Dir(d + "/src").Open(name)
460+
if err == nil {
461+
return file, nil
462+
}
463+
}
464+
465+
if strings.HasSuffix(name, "/main.js.map") {
466+
if content, ok := fs.sourceMaps[name]; ok {
467+
return newFakeFile("main.js.map", content), nil
468+
}
469+
}
470+
471+
isIndex := strings.HasSuffix(name, "/index.html")
472+
isMain := strings.HasSuffix(name, "/main.js")
473+
if isIndex || isMain {
474+
s := gbuild.NewSession(fs.options)
475+
buildPkg, err := gbuild.Import(path.Dir(name[1:]), 0, s.InstallSuffix(), fs.options.BuildTags)
476+
if err != nil || buildPkg.Name != "main" {
477+
return nil, os.ErrNotExist
478+
}
479+
480+
if isIndex {
481+
return newFakeFile("index.html", []byte(`<html><head><meta charset="utf-8"><script src="main.js"></script></head></html>`)), nil
482+
}
483+
484+
if isMain {
485+
buf := bytes.NewBuffer(nil)
486+
handleError(func() error {
487+
pkg := &gbuild.PackageData{Package: buildPkg}
488+
if err := s.BuildPackage(pkg); err != nil {
489+
return err
490+
}
491+
492+
sourceMapFilter := &compiler.SourceMapFilter{Writer: buf}
493+
m := &sourcemap.Map{File: "main.js"}
494+
sourceMapFilter.MappingCallback = gbuild.NewMappingCallback(m, fs.options.GOROOT, fs.options.GOPATH)
495+
496+
deps, err := compiler.ImportDependencies(pkg.Archive, s.ImportContext.Import)
497+
if err != nil {
498+
return err
499+
}
500+
if err := compiler.WriteProgramCode(deps, sourceMapFilter); err != nil {
501+
return err
502+
}
503+
504+
mapBuf := bytes.NewBuffer(nil)
505+
m.WriteTo(mapBuf)
506+
buf.WriteString("//# sourceMappingURL=main.js.map\n")
507+
fs.sourceMaps[name+".map"] = mapBuf.Bytes()
508+
509+
return nil
510+
}, fs.options)
511+
return newFakeFile("main.js", buf.Bytes()), nil
512+
}
513+
}
514+
515+
return nil, os.ErrNotExist
516+
}
517+
518+
type fakeFile struct {
519+
name string
520+
size int
521+
io.ReadSeeker
522+
}
523+
524+
func newFakeFile(name string, content []byte) *fakeFile {
525+
return &fakeFile{name: name, size: len(content), ReadSeeker: bytes.NewReader(content)}
526+
}
527+
528+
func (f *fakeFile) Close() error {
529+
return nil
530+
}
531+
532+
func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
533+
return nil, os.ErrInvalid
534+
}
535+
536+
func (f *fakeFile) Stat() (os.FileInfo, error) {
537+
return f, nil
538+
}
539+
540+
func (f *fakeFile) Name() string {
541+
return f.name
542+
}
543+
544+
func (f *fakeFile) Size() int64 {
545+
return int64(f.size)
546+
}
547+
548+
func (f *fakeFile) Mode() os.FileMode {
549+
return 0
550+
}
551+
552+
func (f *fakeFile) ModTime() time.Time {
553+
return time.Time{}
554+
}
555+
556+
func (f *fakeFile) IsDir() bool {
557+
return false
558+
}
559+
560+
func (f *fakeFile) Sys() interface{} {
561+
return nil
562+
}
563+
430564
func handleError(f func() error, options *gbuild.Options) int {
431565
switch err := f().(type) {
432566
case nil:

0 commit comments

Comments
 (0)