cleaning up logging, speed up live reload using tailwind watch

This commit is contained in:
maddalax 2024-09-18 22:32:09 -05:00
parent f0a38379c3
commit b610aefa36
20 changed files with 158 additions and 95 deletions

View file

@ -3,12 +3,13 @@ package main
import (
"log/slog"
"os"
"strings"
)
func getLogLevel() slog.Level {
// Get the log level from the environment variable
logLevel := os.Getenv("LOG_LEVEL")
switch logLevel {
switch strings.ToUpper(logLevel) {
case "DEBUG":
return slog.LevelDebug
case "INFO":

View file

@ -51,18 +51,21 @@ func main() {
taskName := os.Args[1]
slog.Debug("Running task: %s", taskName)
slog.Debug("working dir %s", process.GetWorkingDir())
slog.Debug("Running task:", slog.String("task", taskName))
slog.Debug("working dir:", slog.String("dir", process.GetWorkingDir()))
if taskName == "watch" {
os.Setenv("ENV", "development")
os.Setenv("WATCH_MODE", "true")
copyassets.CopyAssets()
astgen.GenAst(true)
css.GenerateCss(true)
astgen.GenAst(process.ExitOnError)
css.GenerateCss(process.ExitOnError, process.Silent)
run.EntGenerate()
go func() {
_ = run.Server(true)
css.GenerateCssWatch(process.ExitOnError)
}()
go func() {
_ = run.Server(process.ExitOnError)
}()
startWatcher(reloader.OnFileChange)
} else {
@ -75,18 +78,18 @@ func main() {
}
if taskName == "generate" {
run.EntGenerate()
astgen.GenAst(true)
astgen.GenAst(process.ExitOnError)
}
if taskName == "setup" {
run.Setup()
} else if taskName == "css" {
_ = css.GenerateCss(true)
_ = css.GenerateCss(process.ExitOnError)
} else if taskName == "ast" {
_ = astgen.GenAst(true)
_ = astgen.GenAst(process.ExitOnError)
} else if taskName == "run" {
_ = astgen.GenAst(true)
_ = css.GenerateCss(true)
_ = run.Server(true)
_ = astgen.GenAst(process.ExitOnError)
_ = css.GenerateCss(process.ExitOnError)
_ = run.Server(process.ExitOnError)
} else if taskName == "template" {
reader := bufio.NewReader(os.Stdin)
fmt.Print("What would you like to call your new app?: ")

View file

@ -9,6 +9,7 @@ import (
"golang.org/x/mod/modfile"
"os"
"path/filepath"
"slices"
"strings"
)
@ -354,9 +355,9 @@ func GetModuleName() string {
return modName
}
func GenAst(exitOnError bool) error {
func GenAst(flags ...process.RunFlag) error {
if GetModuleName() == "" {
if exitOnError {
if slices.Contains(flags, process.ExitOnError) {
os.Exit(1)
}
return fmt.Errorf("error getting module name")

View file

@ -7,6 +7,7 @@ import (
"golang.org/x/mod/modfile"
"io"
"log"
"log/slog"
"os"
"path/filepath"
"strings"
@ -122,5 +123,5 @@ func CopyAssets() {
log.Fatalf("Error: %v", err)
}
fmt.Printf("successfully copied assets to %s\n", destDir)
slog.Debug("successfully copied assets to %s\n", destDir)
}

View file

@ -2,9 +2,15 @@ package css
import "github.com/maddalax/htmgo/cli/tasks/process"
func GenerateCss(exitOnError bool) error {
func GenerateCss(flags ...process.RunFlag) error {
return process.RunMany([]string{
"chmod +x ./assets/css/tailwindcss",
"./assets/css/tailwindcss -i ./assets/css/input.css -o ./assets/dist/main.css -c ./assets/css/tailwind.config.js",
}, exitOnError)
}, append(flags, process.Silent)...)
}
func GenerateCssWatch(flags ...process.RunFlag) error {
return process.RunMany([]string{
"./assets/css/tailwindcss -i ./assets/css/input.css -o ./assets/dist/main.css -c ./assets/css/tailwind.config.js --watch=always",
}, append(flags, process.DontTrack, process.Silent)...)
}

View file

@ -3,11 +3,11 @@ package process
import (
"errors"
"fmt"
"log"
"log/slog"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"syscall"
"time"
@ -17,7 +17,7 @@ var workingDir string
var commands = make([]*exec.Cmd, 0)
func AppendRunning(cmd *exec.Cmd) {
slog.Info("running", slog.String("command", strings.Join(cmd.Args, " ")))
slog.Debug("running", slog.String("command", strings.Join(cmd.Args, " ")))
commands = append(commands, cmd)
}
@ -49,7 +49,7 @@ func KillAll() {
if tries > 50 {
args := strings.Join(cmd.Args, " ")
log.Printf("process %v is not running after 50 tries, breaking.\n", args)
slog.Debug("process %v is not running after 50 tries, breaking.\n", args)
allFinished = true
break
} else {
@ -90,13 +90,13 @@ func KillAll() {
if finished {
break
} else {
fmt.Printf("waiting for all processes to exit\n")
slog.Debug("waiting for all processes to exit\n")
time.Sleep(time.Millisecond * 5)
}
}
commands = make([]*exec.Cmd, 0)
fmt.Printf("all processes killed\n")
slog.Debug("all processes killed\n")
}
func PidExists(pid int32) (bool, error) {
@ -129,14 +129,22 @@ func PidExists(pid int32) (bool, error) {
}
func RunOrExit(command string) {
_ = Run(command, true)
_ = Run(command, ExitOnError)
}
func RunMany(commands []string, exitOnError bool) error {
type RunFlag int
const (
ExitOnError RunFlag = iota
Silent
DontTrack
)
func RunMany(commands []string, flags ...RunFlag) error {
for _, command := range commands {
err := Run(command, false)
err := Run(command, flags...)
if err != nil {
if exitOnError {
if slices.Contains(flags, ExitOnError) {
os.Exit(1)
}
return err
@ -145,17 +153,26 @@ func RunMany(commands []string, exitOnError bool) error {
return nil
}
func Run(command string, exitOnError bool) error {
cmd := exec.Command("bash", "-c", command)
func Run(command string, flags ...RunFlag) error {
parts := strings.Fields(command)
cmd := exec.Command(parts[0], parts[1:]...)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if slices.Contains(flags, Silent) {
cmd.Stdout = nil
cmd.Stderr = nil
} else {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
if workingDir != "" {
cmd.Dir = workingDir
}
if !slices.Contains(flags, DontTrack) {
AppendRunning(cmd)
}
err := cmd.Run()
if err == nil {
@ -166,8 +183,10 @@ func Run(command string, exitOnError bool) error {
return nil
}
if exitOnError {
log.Println(fmt.Sprintf("error: %v", err))
if slices.Contains(flags, ExitOnError) {
slog.Error("Error running command: ",
slog.String("error", err.Error()),
slog.String("command", command))
os.Exit(1)
}

View file

@ -4,20 +4,22 @@ import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/maddalax/htmgo/cli/tasks/astgen"
"github.com/maddalax/htmgo/cli/tasks/css"
"github.com/maddalax/htmgo/cli/tasks/process"
"github.com/maddalax/htmgo/cli/tasks/run"
"github.com/maddalax/htmgo/cli/tasks/util"
"log/slog"
"strings"
"sync"
"time"
)
type Change struct {
name string
op fsnotify.Op
}
func NewChange(name string) *Change {
return &Change{name: name}
func NewChange(event *fsnotify.Event) *Change {
return &Change{name: event.Name, op: event.Op}
}
func (c *Change) Name() string {
@ -46,61 +48,71 @@ func (c *Change) IsGenerated() bool {
return c.HasAnySuffix("generated.go")
}
func (c *Change) IsWrite() bool {
return c.op == fsnotify.Write
}
func (c *Change) IsGo() bool {
return c.HasAnySuffix(".go")
}
type Tasks struct {
AstGen bool
Css bool
Run bool
Ent bool
}
func OnFileChange(events []*fsnotify.Event) {
now := time.Now()
tasks := Tasks{}
for _, event := range events {
c := NewChange(event.Name)
slog.Debug("file changed", slog.String("file", c.Name()))
c := NewChange(event)
if c.IsGenerated() {
continue
}
slog.Debug("file changed", slog.String("file", c.Name()))
if c.IsGo() && c.HasAnyPrefix("pages/", "partials/") {
tasks.AstGen = true
}
if c.IsGo() {
tasks.Css = true
tasks.Run = true
}
if c.HasAnySuffix("tailwind.config.js", ".css") {
tasks.Css = true
tasks.Run = true
}
if c.HasAnyPrefix("ent/schema") {
tasks.Ent = true
}
slog.Info("file changed", slog.String("file", c.Name()))
}
deps := make([]func() any, 0)
if tasks.AstGen {
deps = append(deps, func() any {
return astgen.GenAst(false)
return util.Trace("generate ast", func() any {
astgen.GenAst()
return nil
})
})
}
if tasks.Ent {
deps = append(deps, func() any {
return util.Trace("generate ent", func() any {
run.EntGenerate()
return nil
})
})
}
wg := sync.WaitGroup{}
@ -119,14 +131,16 @@ func OnFileChange(events []*fsnotify.Event) {
wg.Wait()
if tasks.Run {
util.Trace("kill all processes", func() any {
process.KillAll()
}
if tasks.Css {
go css.GenerateCss(false)
return nil
})
}
if tasks.Run {
_ = run.Server(false)
go run.Server()
}
slog.Info("reloaded in", slog.Duration("duration", time.Since(now)))
}

View file

@ -7,8 +7,8 @@ import (
)
func Build() {
astgen.GenAst(true)
css.GenerateCss(true)
astgen.GenAst(process.ExitOnError)
css.GenerateCss(process.ExitOnError)
process.RunOrExit("rm -rf ./dist")
process.RunOrExit("mkdir -p ./dist/assets/dist")
process.RunOrExit("cp -r ./assets/dist/* ./dist/assets/dist/")

View file

@ -3,9 +3,9 @@ package run
import "github.com/maddalax/htmgo/cli/tasks/process"
func EntNewSchema(name string) {
process.RunOrExit("GOWORK=off go run -mod=mod entgo.io/ent/cmd/ent new " + name)
process.RunOrExit("bash -c GOWORK=off go run -mod=mod entgo.io/ent/cmd/ent new " + name)
}
func EntGenerate() {
process.RunOrExit("GOWORK=off go generate ./ent")
process.RunOrExit("bash -c GOWORK=off go generate ./ent")
}

View file

@ -2,6 +2,6 @@ package run
import "github.com/maddalax/htmgo/cli/tasks/process"
func Server(exitOnError bool) error {
return process.Run("go run .", exitOnError)
func Server(flags ...process.RunFlag) error {
return process.Run("go run .", flags...)
}

View file

@ -11,7 +11,7 @@ func Setup() {
process.RunOrExit("go mod download")
process.RunOrExit("go mod tidy")
copyassets.CopyAssets()
_ = astgen.GenAst(true)
_ = css.GenerateCss(true)
_ = astgen.GenAst(process.ExitOnError)
_ = css.GenerateCss(process.ExitOnError)
EntGenerate()
}

13
cli/tasks/util/trace.go Normal file
View file

@ -0,0 +1,13 @@
package util
import (
"log/slog"
"time"
)
func Trace(name string, cb func() any) any {
now := time.Now()
result := cb()
slog.Debug("trace", slog.String("name", name), slog.Duration("duration", time.Since(now)))
return result
}

View file

@ -1,23 +1,23 @@
package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"log"
"log/slog"
"os"
"path/filepath"
"time"
)
var ignoredDirs = []string{".git", ".idea", "node_modules", "vendor"}
func startWatcher(cb func(file []*fsnotify.Event)) {
events := make([]*fsnotify.Event, 0)
debouncer := NewDebouncer(100 * time.Millisecond)
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from fatal error:", r)
// You can log the error here or take other corrective actions
slog.Debug("Recovered from fatal error:", slog.String("error", r.(error).Error()))
}
}()
// Create new watcher.
@ -36,8 +36,10 @@ func startWatcher(cb func(file []*fsnotify.Event)) {
}
if event.Has(fsnotify.Write) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
events = append(events, &event)
go cb(events)
debouncer.Do(func() {
cb(events)
events = make([]*fsnotify.Event, 0)
})
}
case err, ok := <-watcher.Errors:
if !ok {
@ -64,9 +66,9 @@ func startWatcher(cb func(file []*fsnotify.Event)) {
if info.IsDir() {
err = watcher.Add(path)
if err != nil {
log.Println("Error adding directory to watcher:", err)
slog.Error("Error adding directory to watcher:", err)
} else {
log.Println("Watching directory:", path)
slog.Debug("Watching directory:", slog.String("path", path))
}
}
return nil

View file

@ -11,7 +11,7 @@ type InputProps struct {
Type string
DefaultValue string
ValidationPath string
Childen []h.Renderable
Children []h.Renderable
}
func Input(props InputProps) h.Renderable {
@ -27,7 +27,7 @@ func Input(props InputProps) h.Renderable {
h.Class("border p-2 rounded"),
h.If(props.Id != "", h.Id(props.Id)),
h.If(props.Name != "", h.Name(props.Name)),
h.If(props.Childen != nil, h.Children(props.Childen...)),
h.If(props.Children != nil, h.Children(props.Children...)),
h.If(props.DefaultValue != "", h.Attribute("value", props.DefaultValue)),
validation,
)

File diff suppressed because one or more lines are too long

View file

@ -33,14 +33,15 @@ htmx.defineExtension("livereload", {
});
function reload() {
fetch(window.location.href).then(response => {
return response.text();
}).then(html => {
document.open();
document.write(html);
document.close();
}).catch(err => {
console.log('failed to fetch live reload', err)
setTimeout(reload, 100)
})
window.location.reload()
// fetch(window.location.href).then(response => {
// return response.text();
// }).then(html => {
// document.open();
// document.write(html);
// document.close();
// }).catch(err => {
// console.log('failed to fetch live reload', err)
// setTimeout(reload, 100)
// })
}

View file

@ -10,16 +10,8 @@ type WsOpts = {
export function createWebSocketClient(opts: WsOpts) {
let socket: WebSocket | null = null;
const connect = (tries: number) => {
if(tries > 50) {
console.error('failed to connect to websocket after 50 tries, please reload the page');
return;
}
console.log('connecting to ws', opts.url, 'attempt', tries)
socket = new WebSocket(opts.url);
// Handle connection open
socket.onopen = () => {
};
// Handle incoming messages
socket.onmessage = (event) => {
opts.onMessage(event.data)
@ -31,12 +23,14 @@ export function createWebSocketClient(opts: WsOpts) {
} catch(ex) {
// noop
}
let interval = tries * (opts.reconnectInterval || 100);
socket = null
let interval = tries * (opts.reconnectInterval || 1000);
setTimeout(() => connect(tries + 1), interval);
};
// Handle connection close and attempt reconnection
socket.onclose = () => {
let interval = tries * (opts.reconnectInterval || 100);
socket = null;
let interval = tries * (opts.reconnectInterval || 1000);
setTimeout(() => connect(tries + 1), interval);
};
};

View file

@ -318,6 +318,10 @@ func NewHeaders(headers ...string) *Headers {
return &m
}
func Checkbox(children ...Renderable) Renderable {
return Input("checkbox", children...)
}
func Input(inputType string, children ...Renderable) Renderable {
return &Node{
tag: "input",

View file

@ -6,6 +6,7 @@ import "github.com/maddalax/htmgo/framework/h"
func RegisterPages(f *echo.Echo) {
f.GET("/", func(ctx echo.Context) error {
return h.HtmlView(ctx, IndexPage(ctx))
cc := ctx.(*h.RequestContext)
return h.HtmlView(ctx, IndexPage(cc))
})
}

View file

@ -6,15 +6,18 @@ import "github.com/labstack/echo/v4"
import "starter-template/partials"
func GetPartialFromContext(ctx echo.Context) *h.Partial {
path := ctx.Path()
path := ctx.Request().URL.Path
if path == "SamplePartial" || path == "/starter-template/partials.SamplePartial" {
return partials.SamplePartial(ctx)
cc := ctx.(*h.RequestContext)
return partials.SamplePartial(cc)
}
if path == "NewPartial" || path == "/starter-template/partials.NewPartial" {
return partials.NewPartial(ctx)
cc := ctx.(*h.RequestContext)
return partials.NewPartial(cc)
}
if path == "NewPartial2" || path == "/starter-template/partials.NewPartial2" {
return partials.NewPartial2(ctx)
cc := ctx.(*h.RequestContext)
return partials.NewPartial2(cc)
}
return nil
}