Skip to content

Commit

Permalink
framework/view: experiment to extract out svelte into a swappable 'vi…
Browse files Browse the repository at this point in the history
…ewer'
  • Loading branch information
matthewmueller committed Jul 22, 2022
1 parent 5f5b616 commit a4794af
Show file tree
Hide file tree
Showing 16 changed files with 371 additions and 4 deletions.
50 changes: 50 additions & 0 deletions example/basic/viewer/svelte/svelte.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package svelte

import (
"encoding/json"
"fmt"
"io/fs"
"net/http"

"github.com/livebud/bud/package/js"
"github.com/livebud/bud/package/router"
)

func New(fsys fs.FS, vm js.VM) *Viewer {
return &Viewer{fsys, vm}
}

type Viewer struct {
fsys fs.FS
vm js.VM
}

func (v *Viewer) Handler(route string, props interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
v.Render(w, route, props)
})
}

func (v *Viewer) Render(w http.ResponseWriter, viewPath string, props interface{}) {
script, err := fs.ReadFile(v.fsys, "bud/view/_ssr.js")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
propBytes, err := json.Marshal(props)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
result, err := v.vm.Eval("_ssr.js", fmt.Sprintf(`%s; bud.render(%q, %s)`, script, viewPath, propBytes))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Println("got result", result)
return
}

func (v *Viewer) Serve(r *router.Router) {
// Serving files!
}
6 changes: 5 additions & 1 deletion framework/app/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func (l *loader) Load() (state *State, err error) {

func (l *loader) loadProvider() *di.Provider {
jsVM := di.ToType("github.com/livebud/bud/package/js", "VM")
fsFS := di.ToType("io/fs", "FS")
fn := &di.Function{
Name: "loadWeb",
Imports: l.imports,
Expand All @@ -67,7 +68,10 @@ func (l *loader) loadProvider() *di.Provider {
di.ToType(l.module.Import("bud/internal/app/web"), "*Server"),
&di.Error{},
},
Aliases: di.Aliases{},
Aliases: di.Aliases{
fsFS: di.ToType("github.com/livebud/bud/package/budclient", "Client"),
jsVM: di.ToType("github.com/livebud/bud/package/budclient", "Client"),
},
}
if l.flag.Embed {
fn.Aliases[jsVM] = di.ToType("github.com/livebud/bud/package/js/v8", "*VM")
Expand Down
3 changes: 2 additions & 1 deletion framework/controller/controller.gotext
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type {{ $.Pascal }}Controller struct {
type {{ $.Pascal }}{{$action.Pascal}}Action struct {
{{- if $action.View }}
View view.Server
Viewer view.Viewer
{{- end }}
{{- with $provider := $action.Provider }}
{{- range $param := $provider.Hoisted }}
Expand Down Expand Up @@ -113,7 +114,7 @@ func ({{$action.Short}} *{{ $.Pascal }}{{$action.Pascal}}Action) handler(httpRes
return &response.Format{
{{- if eq $action.Method "GET" }}
{{- if $action.View }}
HTML: {{ $action.Short }}.View.Handler("{{$action.View.Route}}", {{ $action.Results.ViewResult }}),
HTML: {{ $action.Short }}.Viewer.Handler("{{$action.View.Route}}", {{ $action.Results.ViewResult }}),
{{- else if $action.RespondHTML }}
HTML: response.HTML({{ $action.Results.Result }}),
{{- end }}
Expand Down
2 changes: 1 addition & 1 deletion framework/view/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ func (l *loader) Load(ctx context.Context) (state *State, err error) {
})
}
}
// fmt.Println(l.Flag.Embed, l.Transform.SSR, views)
if l.flag.Embed {
l.imports.AddNamed("overlay", "github.com/livebud/bud/package/overlay")
l.imports.AddNamed("mod", "github.com/livebud/bud/package/gomod")
Expand All @@ -87,6 +86,7 @@ func (l *loader) Load(ctx context.Context) (state *State, err error) {
l.imports.AddNamed("budclient", "github.com/livebud/bud/package/budclient")
}
l.imports.AddNamed("viewrt", "github.com/livebud/bud/framework/view/viewrt")
l.imports.AddNamed("svelte", l.module.Import("viewer/svelte"))
state.Imports = l.imports.List()
return state, nil
}
6 changes: 6 additions & 0 deletions framework/view/view.gotext
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ func New(module *mod.Module, fsys *overlay.FileSystem, vm js.VM) Server {
{{- end }}

type Server = viewrt.Server

func Register(svelte *svelte.Viewer) Viewer {
return svelte
}

type Viewer = viewrt.Viewer
14 changes: 14 additions & 0 deletions framework/view/viewrt/viewer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package viewrt

import (
"net/http"

"github.com/livebud/bud/package/router"
)

type Viewer interface {
// TODO: remove handler
Handler(route string, props interface{}) http.Handler
Render(w http.ResponseWriter, viewPath string, props interface{})
Serve(router *router.Router)
}
1 change: 1 addition & 0 deletions framework/web/web.gotext
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func New(
{{- end }}
{{- if $.HasView }}
view view.Server,
viewer view.Viewer,
{{- end }}
{{- if $.ShowWelcome }}
welcome welcome.Middleware,
Expand Down
67 changes: 67 additions & 0 deletions internal/virtual/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package virtual

import (
"io"
"io/fs"
"time"
)

// File struct
type File struct {
Name string
Data []byte
Mode fs.FileMode
ModTime time.Time
Sys interface{}
offset int64
}

var _ fs.File = (*File)(nil)
var _ io.ReadSeeker = (*File)(nil)

// Reset the read data offset to 0
func (f *File) Reset() {
f.offset = 0
}

func (f *File) Close() error {
return nil
}

func (f *File) Read(b []byte) (int, error) {
if f.offset >= int64(len(f.Data)) {
return 0, io.EOF
}
if f.offset < 0 {
return 0, &fs.PathError{Op: "read", Path: f.Name, Err: fs.ErrInvalid}
}
n := copy(b, f.Data[f.offset:])
f.offset += int64(n)
return n, nil
}

func (f *File) Stat() (fs.FileInfo, error) {
return &fileInfo{
name: f.Name,
mode: f.Mode &^ fs.ModeDir,
modTime: f.ModTime,
size: int64(len(f.Data)),
sys: f.Sys,
}, nil
}

func (f *File) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
// offset += 0
case 1:
offset += f.offset
case 2:
offset += int64(len(f.Data))
}
if offset < 0 || offset > int64(len(f.Data)) {
return 0, &fs.PathError{Op: "seek", Path: f.Name, Err: fs.ErrInvalid}
}
f.offset = offset
return offset, nil
}
24 changes: 24 additions & 0 deletions internal/virtual/fileinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package virtual

import (
"io/fs"
"time"
)

// A fileInfo implements fs.FileInfo and fs.DirEntry for a given map file.
type fileInfo struct {
name string
size int64
mode fs.FileMode
modTime time.Time
sys interface{}
}

func (i *fileInfo) Name() string { return i.name }
func (i *fileInfo) Mode() fs.FileMode { return i.mode }
func (i *fileInfo) Type() fs.FileMode { return i.mode.Type() }
func (i *fileInfo) ModTime() time.Time { return i.modTime }
func (i *fileInfo) IsDir() bool { return i.mode&fs.ModeDir != 0 }
func (i *fileInfo) Sys() interface{} { return i.sys }
func (i *fileInfo) Info() (fs.FileInfo, error) { return i, nil }
func (i *fileInfo) Size() int64 { return i.size }
35 changes: 35 additions & 0 deletions package/budclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import (
"encoding/json"
"fmt"
"io"
"io/fs"
"net/http"
"strings"

"github.com/livebud/bud/framework/view/ssr"
"github.com/livebud/bud/internal/urlx"
"github.com/livebud/bud/internal/virtual"
"github.com/livebud/bud/package/js"
"github.com/livebud/bud/package/socket"
)

type Client interface {
js.VM
fs.FS
Render(route string, props interface{}) (*ssr.Response, error)
Proxy(w http.ResponseWriter, r *http.Request)
Publish(topic string, data []byte) error
Expand Down Expand Up @@ -55,6 +60,36 @@ type client struct {
}

var _ Client = (*client)(nil)
var _ js.VM = (*client)(nil)
var _ fs.FS = (*client)(nil)

func (c *client) Open(name string) (fs.File, error) {
res, err := c.httpClient.Get(c.baseURL + "/open/" + name)
if err != nil {
return nil, err
}
defer res.Body.Close()
resBody, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("budclient: render returned unexpected %d. %s", res.StatusCode, resBody)
}
var vfile virtual.File
if err := json.Unmarshal(resBody, &vfile); err != nil {
return nil, err
}
return &vfile, nil
}

func (c *client) Script(path, script string) error {
return fmt.Errorf("budclient: script not implemented yet")
}

func (c *client) Eval(path, expression string) (string, error) {
return "", fmt.Errorf("budclient: eval not implemented yet")
}

// Render a path with props on the dev server
func (c *client) Render(route string, props interface{}) (*ssr.Response, error) {
Expand Down
40 changes: 40 additions & 0 deletions package/budclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package budclient_test
import (
"context"
"io"
"io/fs"
"net/http/httptest"
"testing"

Expand Down Expand Up @@ -205,3 +206,42 @@ func TestEvents(t *testing.T) {
t.Fatalf("missing event")
}
}

func TestFS(t *testing.T) {
ctx := context.Background()
is := is.New(t)
dir := t.TempDir()
td := testdir.New(dir)
td.Files["view/index.svelte"] = `
<script>
export let _string = "cupcake"
</script>
<h1>Hello, {_string}!</h1>
`
td.NodeModules["svelte"] = versions.Svelte
is.NoErr(td.Write(ctx))
bus := pubsub.New()
server, err := loadServer(bus, dir)
is.NoErr(err)
defer server.Close()
client, err := budclient.Load(server.URL)
is.NoErr(err)

// Check the entrypoint
data, err := fs.ReadFile(client, "bud/view/_index.svelte.js")
is.NoErr(err)
is.In(string(data), `Hello, `)
is.In(string(data), `cupcake`)

// Check the component
data, err = fs.ReadFile(client, "bud/view/index.svelte")
is.NoErr(err)
is.In(string(data), `Hello, `)
is.In(string(data), `cupcake`)

// Check the node_modules
data, err = fs.ReadFile(client, "bud/node_modules/svelte/internal")
is.NoErr(err)
is.In(string(data), `function element(`)
is.In(string(data), `function text(`)
}
13 changes: 13 additions & 0 deletions package/budclient/discard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package budclient

import (
"fmt"
"io/fs"
"net/http"

"github.com/livebud/bud/framework/view/ssr"
Expand All @@ -13,6 +14,18 @@ type discard struct {

var _ Client = discard{}

func (discard) Script(path, script string) error {
return fmt.Errorf("budclient: discard script not implemented yet")
}

func (discard) Eval(path, expression string) (string, error) {
return "", fmt.Errorf("budclient: discard eval not implemented yet")
}

func (discard) Open(name string) (fs.File, error) {
return nil, fmt.Errorf("budclient: discard open not implemented yet")
}

func (discard) Render(route string, props interface{}) (*ssr.Response, error) {
return nil, fmt.Errorf("budclient: discard client does not support render")
}
Expand Down
Loading

0 comments on commit a4794af

Please sign in to comment.