@@ -8,9 +8,12 @@ import (
8
8
"go/parser"
9
9
"go/scanner"
10
10
"go/token"
11
+ "io"
11
12
"io/ioutil"
13
+ "net/http"
12
14
"os"
13
15
"os/exec"
16
+ "path"
14
17
"path/filepath"
15
18
"strings"
16
19
"syscall"
@@ -19,6 +22,7 @@ import (
19
22
20
23
gbuild "github.com/gopherjs/gopherjs/build"
21
24
"github.com/gopherjs/gopherjs/compiler"
25
+ "github.com/neelance/sourcemap"
22
26
"github.com/spf13/cobra"
23
27
"github.com/spf13/pflag"
24
28
"golang.org/x/crypto/ssh/terminal"
@@ -419,14 +423,144 @@ func main() {
419
423
}, options ))
420
424
}
421
425
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
+
422
443
rootCmd := & cobra.Command {
423
444
Use : "gopherjs" ,
424
445
Long : "GopherJS is a tool for compiling Go source code to JavaScript." ,
425
446
}
426
- rootCmd .AddCommand (cmdBuild , cmdGet , cmdInstall , cmdRun , cmdTest , cmdTool )
447
+ rootCmd .AddCommand (cmdBuild , cmdGet , cmdInstall , cmdRun , cmdTest , cmdTool , cmdServe )
427
448
rootCmd .Execute ()
428
449
}
429
450
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
+
430
564
func handleError (f func () error , options * gbuild.Options ) int {
431
565
switch err := f ().(type ) {
432
566
case nil :
0 commit comments