Skip to content

Commit

Permalink
public: integrate transpiler support into public (#371)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewmueller authored Jan 30, 2023
1 parent 6735ec7 commit 061c0f1
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 24 deletions.
2 changes: 2 additions & 0 deletions example/hn/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/RyanCarrier/dijkstra v1.1.0 h1:/NDihjfJA3CxFaZz8EdzTwdFKFZDvvB881OVLdakRcI=
github.com/ajg/form v1.5.2-0.20200323032839-9aeb3cf462e1 h1:8Qzi+0Uch1VJvdrOhJ8U8FqoPLbUdETPgMqGJ6DSMSQ=
github.com/ajg/form v1.5.2-0.20200323032839-9aeb3cf462e1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
Expand Down Expand Up @@ -40,6 +41,7 @@ github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffkt
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/livebud/bud-test-nested-plugin v0.0.5/go.mod h1:M3QujkGG4ggZ6h75t5zF8MEJFrLTwa2USeIYHQdO2YQ=
github.com/livebud/bud-test-plugin v0.0.9/go.mod h1:GTxMZ8W4BIyGIOgAA4hvPHMDDTkaZtfcuhnOcSu3y8M=
github.com/livebud/transpiler v0.0.1 h1:raG4W8qMq534VYrWv5sFQieQCa4McrJhQOy0YXk63kk=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matthewmueller/diff v0.0.0-20220104030700-cb2fe910d90c h1:yjGBNrCIE7IghJAwrFcyDzwzwJKf0oRPeOHx60wfkmA=
Expand Down
8 changes: 5 additions & 3 deletions framework/app/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func (l *loader) loadProvider() *di.Provider {
// TODO: the public generator should be able to configure this
publicFS := di.ToType("github.com/livebud/bud/framework/public/publicrt", "FS")
viewFS := di.ToType("github.com/livebud/bud/framework/view/viewrt", "FS")
transpilerFS := di.ToType("github.com/livebud/bud/runtime/transpiler", "FS")
fn := &di.Function{
Name: "loadWeb",
Imports: l.imports,
Expand All @@ -72,9 +73,10 @@ func (l *loader) loadProvider() *di.Provider {
&di.Error{},
},
Aliases: di.Aliases{
publicFS: di.ToType("github.com/livebud/bud/package/remotefs", "*Client"),
viewFS: di.ToType("github.com/livebud/bud/package/remotefs", "*Client"),
jsVM: di.ToType("github.com/livebud/bud/package/budhttp", "Client"),
transpilerFS: di.ToType("github.com/livebud/bud/package/remotefs", "*Client"),
publicFS: di.ToType("github.com/livebud/bud/runtime/transpiler", "*Proxy"),
viewFS: di.ToType("github.com/livebud/bud/package/remotefs", "*Client"),
jsVM: di.ToType("github.com/livebud/bud/package/budhttp", "Client"),
},
}
if l.flag.Embed {
Expand Down
18 changes: 13 additions & 5 deletions framework/public/loader.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package public

import (
"errors"
"io/fs"
"path"
"strings"

"github.com/livebud/bud/internal/valid"
"github.com/livebud/bud/runtime/transpiler"

"github.com/livebud/bud/framework"
"github.com/livebud/bud/package/finder"
Expand Down Expand Up @@ -68,14 +70,20 @@ func (l *loader) loadFiles(paths []string) (files []*File) {
return files
}

func (l *loader) loadFile(path string) *File {
func (l *loader) loadFile(fpath string) *File {
file := new(File)
file.Path = path
file.Route = strings.TrimPrefix(path, "public")
file.Path = fpath
file.Route = strings.TrimPrefix(fpath, "public")
if l.flag.Embed {
data, err := fs.ReadFile(l.fsys, path)
data, err := transpiler.TranspileFile(l.fsys, fpath, path.Ext(fpath))
if err != nil {
l.Bail(err)
if !errors.Is(err, fs.ErrNotExist) {
l.Bail(err)
}
data, err = fs.ReadFile(l.fsys, fpath)
if err != nil {
l.Bail(err)
}
}
file.Data = data
}
Expand Down
90 changes: 88 additions & 2 deletions framework/public/public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,48 @@ func TestGetChangeGet(t *testing.T) {
is.NoErr(err)
is.Equal(200, res.Status())
is.Equal(res.Body().Bytes(), favicon2)
// is.Equal(result.Stdout(), "")
// is.Equal(result.Stderr(), "")
}

func TestTranspiledGetChangeGet(t *testing.T) {
is := is.New(t)
ctx := context.Background()
dir := t.TempDir()
td := testdir.New(dir)
favicon := []byte{0x01, 0x02, 0x03}
td.BFiles["public/favicon.ico"] = favicon
td.Files["transpiler/favicon/favicon.go"] = `
package favicon
import "github.com/livebud/bud/runtime/transpiler"
type Transpiler struct{}
func (t *Transpiler) IcoToIco(file *transpiler.File) error {
for i, b := range file.Data {
file.Data[i] = b + 1
}
return nil
}
`
is.NoErr(td.Write(ctx))
cli := testcli.New(dir)
app, err := cli.Start(ctx, "run")
is.NoErr(err)
defer app.Close()
res, err := app.Get("/favicon.ico")
is.NoErr(err)
is.Equal(200, res.Status())
is.Equal(res.Body().Bytes(), []byte{0x02, 0x03, 0x04})
is.NoErr(td.Exists("bud/internal/web/public/public.go"))
// Favicon2
favicon2 := []byte{0x10, 0x11, 0x12}
td.BFiles["public/favicon.ico"] = favicon2
is.NoErr(td.Write(ctx))
readyCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
is.NoErr(app.Ready(readyCtx))
cancel()
is.NoErr(td.Exists("bud/internal/web/public/public.go"))
res, err = app.Get("/favicon.ico")
is.NoErr(err)
is.Equal(200, res.Status())
is.Equal(res.Body().Bytes(), []byte{0x11, 0x12, 0x13})
}

func TestEmbedFavicon(t *testing.T) {
Expand Down Expand Up @@ -183,3 +223,49 @@ func TestEmbedFavicon(t *testing.T) {
is.Equal(res.Body().Bytes(), favicon)
is.NoErr(app.Close())
}

func TestTranspiledEmbedFavicon(t *testing.T) {
is := is.New(t)
ctx := context.Background()
dir := t.TempDir()
td := testdir.New(dir)
td.BFiles["public/favicon.ico"] = favicon
td.Files["transpiler/favicon/favicon.go"] = `
package favicon
import "github.com/livebud/bud/runtime/transpiler"
type Transpiler struct{}
func (t *Transpiler) IcoToIco(file *transpiler.File) error {
file.Data = []byte{0x01, 0x02, 0x03}
return nil
}
`
is.NoErr(td.Write(ctx))
cli := testcli.New(dir)
result, err := cli.Run(ctx, "build")
is.NoErr(err)
is.Equal(result.Stdout(), "")
is.Equal(result.Stderr(), "")
app, err := cli.StartApp(ctx)
is.NoErr(err)
defer app.Close()
res, err := app.Get("/favicon.ico")
is.NoErr(err)
is.Equal(200, res.Status())
is.Equal(res.Body().Bytes(), []byte{0x01, 0x02, 0x03})
// Replace favicon
favicon2 := []byte{0x00, 0x00, 0x01}
td.BFiles["public/favicon.ico"] = favicon2
is.NoErr(td.Write(ctx))
is.NoErr(app.Close())
// Restart app
app, err = cli.StartApp(ctx)
is.NoErr(err)
defer app.Close()
// Favicon shouldn't have changed because non-Go files don't trigger
// full rebuilds and server restarts
res, err = app.Get("/favicon.ico")
is.NoErr(err)
is.Equal(200, res.Status())
is.Equal(res.Body().Bytes(), []byte{0x01, 0x02, 0x03})
is.NoErr(app.Close())
}
71 changes: 71 additions & 0 deletions internal/testcli/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package testcli

import (
"bytes"
"context"
"io"
"net/http"
"os"
"path/filepath"

"github.com/livebud/bud/internal/extrafile"

"github.com/livebud/bud/internal/once"
"github.com/livebud/bud/internal/shell"
)

// StartApp starts bud/app. It's meant to be used after running `bud build`.
// TODO: integrate better with testcli
func (c *CLI) StartApp(ctx context.Context, args ...string) (*Process, error) {
webLn, webc, err := listen(":0")
if err != nil {
return nil, err
}
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := shell.Command{
Dir: c.dir,
Env: c.Env.List(),
Stdin: c.Stdin,
Stdout: io.MultiWriter(os.Stdout, stdout),
Stderr: io.MultiWriter(os.Stderr, stderr),
}
closer := new(once.Closer)
webFile, err := webLn.File()
if err != nil {
return nil, err
}
closer.Add(webFile.Close)
extrafile.Inject(&cmd.ExtraFiles, &cmd.Env, "WEB", webFile)
args = append(args, "--listen", webLn.Addr().String())
process, err := cmd.Start(ctx, filepath.Join("bud", "app"), prependFlags(args)...)
if err != nil {
return nil, err
}
closer.Add(process.Close)
return &Process{
closer: closer,
stdout: stdout,
stderr: stderr,
webc: webc,
}, nil
}

type Process struct {
closer *once.Closer
stdout *bytes.Buffer
stderr *bytes.Buffer
webc *http.Client
}

func (p *Process) Close() error {
return p.closer.Close()
}

func (p *Process) Get(path string) (*Response, error) {
req, err := getRequest(path)
if err != nil {
return nil, err
}
return do(p.webc, req)
}
28 changes: 16 additions & 12 deletions internal/testcli/testcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,11 @@ func coerceMimes(res *http.Response) error {
}

func (c *Client) Do(req *http.Request) (*Response, error) {
res, err := c.webc.Do(req)
return do(c.webc, req)
}

func do(client *http.Client, req *http.Request) (*Response, error) {
res, err := client.Do(req)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -367,24 +371,24 @@ func (c *Client) Ready(ctx context.Context) error {

func (c *Client) Get(path string) (*Response, error) {
c.log.Debug("testcli: get request %q", path)
req, err := c.GetRequest(path)
req, err := getRequest(path)
if err != nil {
return nil, err
}
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) GetJSON(path string) (*Response, error) {
c.log.Debug("testcli: get json request %q", path)
req, err := c.GetRequest(path)
req, err := getRequest(path)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) GetRequest(path string) (*http.Request, error) {
func getRequest(path string) (*http.Request, error) {
return http.NewRequest(http.MethodGet, getURL(path), nil)
}

Expand All @@ -394,7 +398,7 @@ func (c *Client) Post(path string, body io.Reader) (*Response, error) {
if err != nil {
return nil, err
}
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) PostJSON(path string, body io.Reader) (*Response, error) {
Expand All @@ -405,7 +409,7 @@ func (c *Client) PostJSON(path string, body io.Reader) (*Response, error) {
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) PostRequest(path string, body io.Reader) (*http.Request, error) {
Expand All @@ -418,7 +422,7 @@ func (c *Client) Patch(path string, body io.Reader) (*Response, error) {
if err != nil {
return nil, err
}
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) PatchJSON(path string, body io.Reader) (*Response, error) {
Expand All @@ -429,7 +433,7 @@ func (c *Client) PatchJSON(path string, body io.Reader) (*Response, error) {
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) PatchRequest(path string, body io.Reader) (*http.Request, error) {
Expand All @@ -442,7 +446,7 @@ func (c *Client) Delete(path string, body io.Reader) (*Response, error) {
if err != nil {
return nil, err
}
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) DeleteJSON(path string, body io.Reader) (*Response, error) {
Expand All @@ -453,7 +457,7 @@ func (c *Client) DeleteJSON(path string, body io.Reader) (*Response, error) {
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
return c.Do(req)
return do(c.webc, req)
}

func (c *Client) DeleteRequest(path string, body io.Reader) (*http.Request, error) {
Expand Down
24 changes: 22 additions & 2 deletions runtime/transpiler/transpiler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package transpiler

import (
"errors"
"io/fs"
"path"
"strings"
Expand Down Expand Up @@ -29,14 +30,17 @@ func splitRoot(fpath string) (rootDir, remainingPath string) {
return parts[0], parts[1]
}

// Aliasing allows us to target the transpiler filesystem directly
type FS = fs.FS

// TranspileFile transpiles a file from one extension to another. It assumes
// the transpiler generator is hooked up and serving from the transpiler
// directory.
func TranspileFile(fsys fs.FS, inputPath, toExt string) ([]byte, error) {
func TranspileFile(fsys FS, inputPath, toExt string) ([]byte, error) {
return fs.ReadFile(fsys, path.Join(transpilerDir, toExt, inputPath))
}

func Serve(tr transpiler.Interface, fsys fs.FS, file *genfs.File) error {
func Serve(tr transpiler.Interface, fsys FS, file *genfs.File) error {
toExt, inputPath := splitRoot(file.Relative())
input, err := fs.ReadFile(fsys, inputPath)
if err != nil {
Expand All @@ -49,3 +53,19 @@ func Serve(tr transpiler.Interface, fsys fs.FS, file *genfs.File) error {
file.Data = output
return nil
}

// Proxy a filesystem through the transpiler
type Proxy struct {
FS FS
}

func (p *Proxy) Open(name string) (fs.File, error) {
file, err := p.FS.Open(path.Join(transpilerDir, path.Ext(name), name))
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, err
}
return p.FS.Open(name)
}
return file, nil
}

0 comments on commit 061c0f1

Please sign in to comment.