Skip to content

Commit

Permalink
feat: storybook, reduced need for rebuild.
Browse files Browse the repository at this point in the history
  • Loading branch information
a-h committed Oct 20, 2021
1 parent 1bf8784 commit e92bcc2
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 44 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
go.uber.org/atomic v1.8.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.17.0
golang.org/x/mod v0.5.1 // indirect
)

//replace github.com/a-h/lexical => /Users/adrian/github.com/a-h/lexical
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,28 @@ go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
125 changes: 81 additions & 44 deletions storybook/storybook.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import (
"path"
"reflect"
"strconv"
"strings"
"sync"

"golang.org/x/mod/sumdb/dirhash"

"github.com/a-h/pathvars"
"github.com/a-h/templ"
"github.com/rs/cors"
Expand Down Expand Up @@ -45,7 +48,7 @@ func New() *Storybook {
Log: logger,
}
sh.Server = http.Server{
Handler: sh,
Handler: http.NotFoundHandler(),
Addr: "localhost:60606",
}
return sh
Expand All @@ -62,32 +65,10 @@ func (sh *Storybook) AddComponent(name string, componentConstructor interface{},

var storybookPreviewMatcher = pathvars.NewExtractor("/storybook_preview/{name}")

func (sh *Storybook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
values, ok := storybookPreviewMatcher.Extract(r.URL)
if !ok {
sh.Log.Info("URL not matched", zap.String("url", r.URL.String()))
http.NotFound(w, r)
return
}
name, ok := values["name"]
if !ok {
sh.Log.Info("URL does not contain component name", zap.String("url", r.URL.String()))
http.NotFound(w, r)
return
}
h, found := sh.Handlers[name]
if !found {
sh.Log.Info("Component name not found", zap.String("name", name), zap.String("url", r.URL.String()))
http.NotFound(w, r)
return
}
cors.Default().Handler(h).ServeHTTP(w, r)
}

func (sh *Storybook) ListenAndServeWithContext(ctx context.Context) (err error) {
defer sh.Log.Sync()
// Download Storybook to the directory required.
sh.Log.Info("Installing storybook")
sh.Log.Info("Installing storybook.")
err = sh.installStorybook()
if err != nil {
return
Expand All @@ -97,25 +78,39 @@ func (sh *Storybook) ListenAndServeWithContext(ctx context.Context) (err error)
}

// Copy the config to Storybook.
sh.Log.Info("Configuring storybook")
err = sh.configureStorybook()
sh.Log.Info("Configuring storybook.")
configHasChanged, err := sh.configureStorybook()
if err != nil {
return
}
if ctx.Err() != nil {
return
}

// Run storybook.
go func() {
sh.Log.Info("Starting Storybook on http://localhost:6006/")
sh.runStorybook(ctx)
}()
// Execute a static build of storybook if the config has changed.
if configHasChanged {
sh.Log.Info("Config not present, or has changed, rebuilding storybook.")
sh.buildStorybook()
} else {
sh.Log.Info("Storybook is up-to-date, skipping build step.")
}
if ctx.Err() != nil {
return
}

// Combine static and dynamic routes and start the server.
staticHandler := http.FileServer(http.Dir("./storybook-server/storybook-static"))
sbh := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/storybook_preview/") {
sh.previewHandler(w, r)
return
}
staticHandler.ServeHTTP(w, r)
})
sh.Server.Handler = cors.Default().Handler(sbh)

// Start the Go server.
sh.Server.Handler = sh
go func() {
sh.Log.Info("Starting Go server", zap.String("address", sh.Server.Addr), zap.String("serverUrl", sh.ServerURL))
sh.Log.Info("Starting Go server", zap.String("address", sh.Server.Addr), zap.String("url", fmt.Sprintf("http://%s", sh.Server.Addr)), zap.String("previewUrl", sh.ServerURL))
err = sh.Server.ListenAndServe()
}()
<-ctx.Done()
Expand All @@ -124,10 +119,32 @@ func (sh *Storybook) ListenAndServeWithContext(ctx context.Context) (err error)
return err
}

func (sh *Storybook) previewHandler(w http.ResponseWriter, r *http.Request) {
values, ok := storybookPreviewMatcher.Extract(r.URL)
if !ok {
sh.Log.Info("URL not matched", zap.String("url", r.URL.String()))
http.NotFound(w, r)
return
}
name, ok := values["name"]
if !ok {
sh.Log.Info("URL does not contain component name", zap.String("url", r.URL.String()))
http.NotFound(w, r)
return
}
h, found := sh.Handlers[name]
if !found {
sh.Log.Info("Component name not found", zap.String("name", name), zap.String("url", r.URL.String()))
http.NotFound(w, r)
return
}
h.ServeHTTP(w, r)
}

func (sh *Storybook) installStorybook() (err error) {
_, err = os.Stat(sh.Path)
if err == nil {
sh.Log.Info("Skipping installation - Storybook is already installed.")
sh.Log.Info("Storybook already installed, Skipping installation.")
return
}
if os.IsNotExist(err) {
Expand All @@ -148,38 +165,58 @@ func (sh *Storybook) installStorybook() (err error) {
return cmd.Run()
}

func (sh *Storybook) configureStorybook() error {
func (sh *Storybook) configureStorybook() (configHasChanged bool, err error) {
// Delete template/existing files in the stories directory.
storiesDir := path.Join(sh.Path, "stories")
if err := os.RemoveAll(storiesDir); err != nil {
return err
before, err := dirhash.HashDir(storiesDir, "/", dirhash.DefaultHash)
if err != nil && !os.IsNotExist(err) {
return configHasChanged, err
}
if err = os.RemoveAll(storiesDir); err != nil {
return configHasChanged, err
}
if err := os.Mkdir(storiesDir, os.ModePerm); err != nil {
return err
return configHasChanged, err
}
// Create new *.stories.json files.
for _, c := range sh.Config {
name := path.Join(sh.Path, fmt.Sprintf("stories/%s.stories.json", c.Title))
f, err := os.Create(name)
if err != nil {
return fmt.Errorf("failed to create config file to %q: %w", name, err)
return configHasChanged, fmt.Errorf("failed to create config file to %q: %w", name, err)
}
err = json.NewEncoder(f).Encode(c)
if err != nil {
return fmt.Errorf("failed to write JSON config to %q: %w", name, err)
return configHasChanged, fmt.Errorf("failed to write JSON config to %q: %w", name, err)
}
}
after, err := dirhash.HashDir(storiesDir, "/", dirhash.DefaultHash)
configHasChanged = before != after
// Configure storybook Preview URL.
previewJS := fmt.Sprintf(`export const parameters = { server: { url: %s } };`, jsonEncode(sh.ServerURL))
return os.WriteFile(path.Join(sh.Path, ".storybook/preview.js"), []byte(previewJS), os.ModePerm)
err = os.WriteFile(path.Join(sh.Path, ".storybook/preview.js"), []byte(previewJS), os.ModePerm)
return
}

func jsonEncode(s string) string {
b, _ := json.Marshal(s)
return string(b)
}

func (sh *Storybook) runStorybook(ctx context.Context) (err error) {
func (sh *Storybook) hasConfigChanged() (err error) {
var cmd exec.Cmd
cmd.Dir = sh.Path
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Path, err = exec.LookPath("npm")
if err != nil {
return fmt.Errorf("templ-storybook: cannot run storybook, cannot find npm on the path, check that Node.js is installed: %w", err)
}
cmd.Args = []string{"npm", "run", "storybook-build"}
return cmd.Run()
}

func (sh *Storybook) buildStorybook() (err error) {
var cmd exec.Cmd
cmd.Dir = sh.Path
cmd.Stdout = os.Stdout
Expand All @@ -188,7 +225,7 @@ func (sh *Storybook) runStorybook(ctx context.Context) (err error) {
if err != nil {
return fmt.Errorf("templ-storybook: cannot run storybook, cannot find npm on the path, check that Node.js is installed: %w", err)
}
cmd.Args = []string{"npm", "run", "storybook"}
cmd.Args = []string{"npm", "run", "build-storybook"}
return cmd.Run()
}

Expand Down

0 comments on commit e92bcc2

Please sign in to comment.