Detailed changes
  
  
    
    @@ -131,9 +131,12 @@
 
 [[projects]]
   branch = "master"
-  digest = "1:7ca2584fa7da0520cd2d1136a10194fe5a5b220bdb215074ab6f7b5ad91115f4"
+  digest = "1:9d29b003dc5f98647a5dd6754d65c07171fcd35761102ea56ecd3d6993adee7f"
   name = "github.com/shurcooL/httpfs"
-  packages = ["vfsutil"]
+  packages = [
+    "filter",
+    "vfsutil",
+  ]
   pruneopts = "UT"
   revision = "809beceb23714880abc4a382a00c05f89d13b1cc"
 
@@ -216,6 +219,7 @@
     "github.com/jroimartin/gocui",
     "github.com/phayes/freeport",
     "github.com/pkg/errors",
+    "github.com/shurcooL/httpfs/filter",
     "github.com/shurcooL/vfsgen",
     "github.com/skratchdot/open-golang/open",
     "github.com/spf13/cobra",
  
  
  
    
    @@ -0,0 +1,133 @@
+// Package filter offers an http.FileSystem wrapper with the ability to keep or skip files.
+package filter
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	pathpkg "path"
+	"time"
+)
+
+// Func is a selection function which is provided two arguments,
+// its '/'-separated cleaned rooted absolute path (i.e., it always begins with "/"),
+// and the os.FileInfo of the considered file.
+//
+// The path is cleaned via pathpkg.Clean("/" + path).
+//
+// For example, if the considered file is named "a" and it's inside a directory "dir",
+// then the value of path will be "/dir/a".
+type Func func(path string, fi os.FileInfo) bool
+
+// Keep returns a filesystem that contains only those entries in source for which
+// keep returns true.
+func Keep(source http.FileSystem, keep Func) http.FileSystem {
+	return &filterFS{source: source, keep: keep}
+}
+
+// Skip returns a filesystem that contains everything in source, except entries
+// for which skip returns true.
+func Skip(source http.FileSystem, skip Func) http.FileSystem {
+	keep := func(path string, fi os.FileInfo) bool {
+		return !skip(path, fi)
+	}
+	return &filterFS{source: source, keep: keep}
+}
+
+type filterFS struct {
+	source http.FileSystem
+	keep   Func // Keep entries that keep returns true for.
+}
+
+func (fs *filterFS) Open(path string) (http.File, error) {
+	f, err := fs.source.Open(path)
+	if err != nil {
+		return nil, err
+	}
+
+	fi, err := f.Stat()
+	if err != nil {
+		f.Close()
+		return nil, err
+	}
+
+	if !fs.keep(clean(path), fi) {
+		// Skip.
+		f.Close()
+		return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
+	}
+
+	if !fi.IsDir() {
+		return f, nil
+	}
+	defer f.Close()
+
+	fis, err := f.Readdir(0)
+	if err != nil {
+		return nil, err
+	}
+
+	var entries []os.FileInfo
+	for _, fi := range fis {
+		if !fs.keep(clean(pathpkg.Join(path, fi.Name())), fi) {
+			// Skip.
+			continue
+		}
+		entries = append(entries, fi)
+	}
+
+	return &dir{
+		name:    fi.Name(),
+		entries: entries,
+		modTime: fi.ModTime(),
+	}, nil
+}
+
+// clean turns a potentially relative path into an absolute one.
+//
+// This is needed to normalize path parameter for selection function.
+func clean(path string) string {
+	return pathpkg.Clean("/" + path)
+}
+
+// dir is an opened dir instance.
+type dir struct {
+	name    string
+	modTime time.Time
+	entries []os.FileInfo
+	pos     int // Position within entries for Seek and Readdir.
+}
+
+func (d *dir) Read([]byte) (int, error) {
+	return 0, fmt.Errorf("cannot Read from directory %s", d.name)
+}
+func (d *dir) Close() error               { return nil }
+func (d *dir) Stat() (os.FileInfo, error) { return d, nil }
+
+func (d *dir) Name() string       { return d.name }
+func (d *dir) Size() int64        { return 0 }
+func (d *dir) Mode() os.FileMode  { return 0755 | os.ModeDir }
+func (d *dir) ModTime() time.Time { return d.modTime }
+func (d *dir) IsDir() bool        { return true }
+func (d *dir) Sys() interface{}   { return nil }
+
+func (d *dir) Seek(offset int64, whence int) (int64, error) {
+	if offset == 0 && whence == io.SeekStart {
+		d.pos = 0
+		return 0, nil
+	}
+	return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
+}
+
+func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
+	if d.pos >= len(d.entries) && count > 0 {
+		return nil, io.EOF
+	}
+	if count <= 0 || count > len(d.entries)-d.pos {
+		count = len(d.entries) - d.pos
+	}
+	e := d.entries[d.pos : d.pos+count]
+	d.pos += count
+	return e, nil
+}
  
  
  
    
    @@ -0,0 +1,26 @@
+package filter
+
+import (
+	"os"
+	pathpkg "path"
+)
+
+// FilesWithExtensions returns a filter func that selects files (but not directories)
+// that have any of the given extensions. For example:
+//
+// 	filter.FilesWithExtensions(".go", ".html")
+//
+// Would select both .go and .html files. It would not select any directories.
+func FilesWithExtensions(exts ...string) Func {
+	return func(path string, fi os.FileInfo) bool {
+		if fi.IsDir() {
+			return false
+		}
+		for _, ext := range exts {
+			if pathpkg.Ext(path) == ext {
+				return true
+			}
+		}
+		return false
+	}
+}
  
  
  
    
    @@ -9,13 +9,19 @@ import (
 	"os"
 	"path/filepath"
 
+	"github.com/shurcooL/httpfs/filter"
 	"github.com/shurcooL/vfsgen"
 )
 
 func main() {
 	var cwd, _ = os.Getwd()
 
-	webUIAssets := http.Dir(filepath.Join(cwd, "webui/build"))
+	webUIAssets := filter.Skip(
+		http.Dir(filepath.Join(cwd, "webui/build")),
+		func(path string, fi os.FileInfo) bool {
+			return filter.FilesWithExtensions(".map")(path, fi)
+		},
+	)
 
 	fmt.Println("Packing Web UI files ...")
 
  
  
  
    
    @@ -21,64 +21,57 @@ var WebUIAssets = func() http.FileSystem {
 	fs := vfsgen۰FS{
 		"/": &vfsgen۰DirInfo{
 			name:    "/",
-			modTime: time.Date(2018, 8, 8, 17, 0, 5, 812171708, time.UTC),
+			modTime: time.Date(2018, 8, 13, 23, 54, 21, 858686490, time.UTC),
 		},
 		"/asset-manifest.json": &vfsgen۰CompressedFileInfo{
 			name:             "asset-manifest.json",
-			modTime:          time.Date(2018, 8, 8, 17, 0, 5, 608173974, time.UTC),
+			modTime:          time.Date(2018, 8, 13, 23, 54, 21, 758684604, time.UTC),
 			uncompressedSize: 96,
 
-			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xe6\x52\x50\x50\xca\x4d\xcc\xcc\xd3\xcb\x2a\x56\xb2\x52\x50\x2a\x2e\x49\x2c\xc9\x4c\xd6\xcf\x2a\xd6\x07\x0b\x9a\x58\x24\x1b\xa5\x1a\x58\x9a\x82\x64\x75\x90\x94\xea\xe5\x26\x16\xe0\x57\x0e\x56\xc1\x55\x0b\x08\x00\x00\xff\xff\xf7\xc2\x31\xef\x60\x00\x00\x00"),
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xaa\xe6\x52\x50\x50\xca\x4d\xcc\xcc\xd3\xcb\x2a\x56\xb2\x52\x50\x2a\x2e\x49\x2c\xc9\x4c\xd6\xcf\x2a\xd6\x07\x0b\x5a\x24\xa5\x98\x1a\x98\x59\x26\x83\x64\x75\x90\x94\xea\xe5\x26\x16\xe0\x57\x0e\x56\xc1\x55\x0b\x08\x00\x00\xff\xff\x0d\xb2\xb0\x73\x60\x00\x00\x00"),
 		},
 		"/favicon.ico": &vfsgen۰CompressedFileInfo{
 			name:             "favicon.ico",
-			modTime:          time.Date(2018, 8, 8, 16, 59, 55, 448286873, time.UTC),
+			modTime:          time.Date(2018, 8, 13, 23, 54, 14, 218542451, time.UTC),
 			uncompressedSize: 3870,