Initial commit
diff --git a/loop/webui/memfs.go b/loop/webui/memfs.go
new file mode 100644
index 0000000..5431862
--- /dev/null
+++ b/loop/webui/memfs.go
@@ -0,0 +1,53 @@
+package webui
+
+import (
+ "bytes"
+ "fmt"
+ "io/fs"
+ "time"
+)
+
+// memFS implements fs.FS in-memory.
+type memFS struct {
+ m map[string][]byte
+}
+
+func (m memFS) Open(name string) (fs.File, error) {
+ b, found := m.m[name]
+ if !found {
+ return nil, fmt.Errorf("esbuild.memFS(%q): %w", name, fs.ErrNotExist)
+ }
+ return &memFile{name: name, Reader: *bytes.NewReader(b)}, nil
+}
+
+func (m memFS) ReadFile(name string) ([]byte, error) {
+ b, found := m.m[name]
+ if !found {
+ return nil, fmt.Errorf("esbuild.memFS.ReadFile(%q): %w", name, fs.ErrNotExist)
+ }
+ return append(make([]byte, 0, len(b)), b...), nil
+}
+
+// memFile implements fs.File in-memory.
+type memFile struct {
+ // embedding is very important here because need more than
+ // Read, we need Seek to make http.ServeContent happy.
+ bytes.Reader
+ name string
+}
+
+func (f *memFile) Stat() (fs.FileInfo, error) { return &memFileInfo{f: f}, nil }
+func (f *memFile) Close() error { return nil }
+
+var start = time.Now()
+
+type memFileInfo struct {
+ f *memFile
+}
+
+func (i memFileInfo) Name() string { return i.f.name }
+func (i memFileInfo) Size() int64 { return i.f.Reader.Size() }
+func (i memFileInfo) Mode() fs.FileMode { return 0o444 }
+func (i memFileInfo) ModTime() time.Time { return start }
+func (i memFileInfo) IsDir() bool { return false }
+func (i memFileInfo) Sys() any { return nil }