diff --git a/cli/htmgo/tasks/astgen/entry.go b/cli/htmgo/tasks/astgen/entry.go index 6c45551..bc385a3 100644 --- a/cli/htmgo/tasks/astgen/entry.go +++ b/cli/htmgo/tasks/astgen/entry.go @@ -9,10 +9,12 @@ import ( "go/parser" "go/token" "golang.org/x/mod/modfile" + "io/fs" "os" "path/filepath" "slices" "strings" + "unicode" ) type Page struct { @@ -37,6 +39,32 @@ const ModuleName = "github.com/maddalax/htmgo/framework/h" var PackageName = fmt.Sprintf("package %s", GeneratedDirName) var GeneratedFileLine = fmt.Sprintf("// Package %s THIS FILE IS GENERATED. DO NOT EDIT.", GeneratedDirName) +func toPascaleCase(input string) string { + words := strings.Split(input, "_") + for i := range words { + words[i] = strings.Title(strings.ToLower(words[i])) + } + return strings.Join(words, "") +} + +func isValidGoVariableName(name string) bool { + // Variable name must not be empty + if name == "" { + return false + } + // First character must be a letter or underscore + if !unicode.IsLetter(rune(name[0])) && name[0] != '_' { + return false + } + // Remaining characters must be letters, digits, or underscores + for _, char := range name[1:] { + if !unicode.IsLetter(char) && !unicode.IsDigit(char) && char != '_' { + return false + } + } + return true +} + func normalizePath(path string) string { return strings.ReplaceAll(path, `\`, "/") } @@ -390,6 +418,63 @@ func writePagesFile() { }) } +func writeAssetsFile() { + cwd := process.GetWorkingDir() + config := dirutil.GetConfig() + distAssets := filepath.Join(cwd, "assets", "dist") + hasAssets := false + + builder := strings.Builder{} + + builder.WriteString(`package assets`) + builder.WriteString("\n") + + filepath.WalkDir(distAssets, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + if strings.HasPrefix(d.Name(), ".") { + return nil + } + + path = strings.ReplaceAll(path, distAssets, "") + httpUrl := fmt.Sprintf("%s%s", config.PublicAssetPath, path) + + path = normalizePath(path) + path = strings.ReplaceAll(path, "/", "_") + path = strings.ReplaceAll(path, "//", "_") + + name := strings.ReplaceAll(path, ".", "_") + name = strings.ReplaceAll(name, "-", "_") + + name = toPascaleCase(name) + + if isValidGoVariableName(name) { + builder.WriteString(fmt.Sprintf(`const %s = "%s"`, name, httpUrl)) + builder.WriteString("\n") + hasAssets = true + } + + return nil + }) + + builder.WriteString("\n") + + str := builder.String() + + if hasAssets { + WriteFile(filepath.Join(GeneratedDirName, "assets", "assets-generated.go"), func(content *ast.File) string { + return str + }) + } + +} + func GetModuleName() string { wd := process.GetWorkingDir() modPath := filepath.Join(wd, "go.mod") @@ -411,6 +496,7 @@ func GenAst(flags ...process.RunFlag) error { } writePartialsFile() writePagesFile() + writeAssetsFile() WriteFile("__htmgo/setup-generated.go", func(content *ast.File) string { diff --git a/framework/config/project.go b/framework/config/project.go index 33f6cfc..33b3d07 100644 --- a/framework/config/project.go +++ b/framework/config/project.go @@ -1,6 +1,7 @@ package config import ( + "github.com/maddalax/htmgo/cli/htmgo/tasks/process" "gopkg.in/yaml.v3" "log/slog" "os" @@ -14,6 +15,7 @@ type ProjectConfig struct { WatchFiles []string `yaml:"watch_files"` AutomaticPageRoutingIgnore []string `yaml:"automatic_page_routing_ignore"` AutomaticPartialRoutingIgnore []string `yaml:"automatic_partial_routing_ignore"` + PublicAssetPath string `yaml:"public_asset_path"` } func DefaultProjectConfig() *ProjectConfig { @@ -25,6 +27,7 @@ func DefaultProjectConfig() *ProjectConfig { WatchFiles: []string{ "**/*.go", "**/*.html", "**/*.css", "**/*.js", "**/*.json", "**/*.yaml", "**/*.yml", "**/*.md", }, + PublicAssetPath: "/public", } } @@ -57,9 +60,19 @@ func (cfg *ProjectConfig) Enhance() *ProjectConfig { } } + if cfg.PublicAssetPath == "" { + cfg.PublicAssetPath = "/public" + } + return cfg } +func Get() *ProjectConfig { + cwd := process.GetWorkingDir() + config := FromConfigFile(cwd) + return config +} + func FromConfigFile(workingDir string) *ProjectConfig { defaultCfg := DefaultProjectConfig() names := []string{"htmgo.yaml", "htmgo.yml", "_htmgo.yaml", "_htmgo.yml"} diff --git a/framework/config/project_test.go b/framework/config/project_test.go index bb492a0..197f4b9 100644 --- a/framework/config/project_test.go +++ b/framework/config/project_test.go @@ -73,6 +73,15 @@ func TestShouldPrefixAutomaticPartialRoutingIgnore_1(t *testing.T) { assert.Equal(t, []string{"partials/somefile/*"}, cfg.AutomaticPartialRoutingIgnore) } +func TestPublicAssetPath(t *testing.T) { + t.Parallel() + cfg := DefaultProjectConfig() + assert.Equal(t, "/public", cfg.PublicAssetPath) + + cfg.PublicAssetPath = "/assets" + assert.Equal(t, "/assets", cfg.PublicAssetPath) +} + func writeConfigFile(t *testing.T, content string) string { temp := os.TempDir() os.Mkdir(temp, 0755) diff --git a/htmgo-site/pages/base/root.go b/htmgo-site/pages/base/root.go index 84e2c93..03e546f 100644 --- a/htmgo-site/pages/base/root.go +++ b/htmgo-site/pages/base/root.go @@ -3,6 +3,7 @@ package base import ( "github.com/google/uuid" "github.com/maddalax/htmgo/framework/h" + "htmgo-site/__htmgo/assets" "htmgo-site/partials" ) @@ -43,8 +44,8 @@ func ConfigurableRootPage(ctx *h.RequestContext, props RootPageProps) *h.Page { h.Title( h.Text(title), ), - h.Link("/public/favicon.ico", "icon"), - h.Link("/public/apple-touch-icon.png", "apple-touch-icon"), + h.Link(assets.FaviconIco, "icon"), + h.Link(assets.AppleTouchIconPng, "apple-touch-icon"), h.Meta("charset", "utf-8"), h.Meta("author", "htmgo"), h.Meta("description", description), @@ -53,8 +54,8 @@ func ConfigurableRootPage(ctx *h.RequestContext, props RootPageProps) *h.Page { h.Link("canonical", canonical), h.Link("https://cdn.jsdelivr.net/npm/@docsearch/css@3", "stylesheet"), h.Meta("og:description", description), - h.LinkWithVersion("/public/main.css", "stylesheet", Version), - h.ScriptWithVersion("/public/htmgo.js", Version), + h.LinkWithVersion(assets.MainCss, "stylesheet", Version), + h.ScriptWithVersion(assets.HtmgoJs, Version), h.Style(` html { scroll-behavior: smooth; diff --git a/htmgo-site/pages/docs/htmx-extensions/overview.go b/htmgo-site/pages/docs/htmx-extensions/overview.go index 56c59d2..46c83b7 100644 --- a/htmgo-site/pages/docs/htmx-extensions/overview.go +++ b/htmgo-site/pages/docs/htmx-extensions/overview.go @@ -61,20 +61,3 @@ h.JoinExtensions( h.HxExtension("my-extension"), ) ` - -const htmxExtensions = ` -h.HxOnLoad -h.HxOnAfterSwap -h.OnClick -h.OnSubmit -h.HxBeforeSseMessage -h.HxAfterSseMessage -h.OnClick -h.OnSubmit -h.HxOnSseError -h.HxOnSseClose -h.HxOnSseConnecting -h.HxOnSseOpen -h.HxAfterRequest -h.HxOnMutationError -` diff --git a/templates/starter/htmgo.yml b/templates/starter/htmgo.yml index 7647094..3074bbe 100644 --- a/templates/starter/htmgo.yml +++ b/templates/starter/htmgo.yml @@ -16,3 +16,6 @@ automatic_page_routing_ignore: ["root.go"] # files or directories to ignore when automatically registering routes for partials # supports glob patterns through https://github.com/bmatcuk/doublestar automatic_partial_routing_ignore: [] + +# url path of where the public assets are located +public_asset_path: "/public" diff --git a/templates/starter/main.go b/templates/starter/main.go index bab347c..a33919b 100644 --- a/templates/starter/main.go +++ b/templates/starter/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "github.com/maddalax/htmgo/framework/config" "github.com/maddalax/htmgo/framework/h" "github.com/maddalax/htmgo/framework/service" "io/fs" @@ -10,6 +12,7 @@ import ( func main() { locator := service.NewLocator() + cfg := config.Get() h.Start(h.AppOpts{ ServiceLocator: locator, @@ -23,7 +26,10 @@ func main() { http.FileServerFS(sub) - app.Router.Handle("/public/*", http.StripPrefix("/public", http.FileServerFS(sub))) + // change this in htmgo.yml (public_asset_path) + app.Router.Handle(fmt.Sprintf("%s/*", cfg.PublicAssetPath), + http.StripPrefix(cfg.PublicAssetPath, http.FileServerFS(sub))) + __htmgo.Register(app.Router) }, }) diff --git a/templates/starter/pages/root.go b/templates/starter/pages/root.go index c228349..e6e5a0f 100644 --- a/templates/starter/pages/root.go +++ b/templates/starter/pages/root.go @@ -2,6 +2,7 @@ package pages import ( "github.com/maddalax/htmgo/framework/h" + "starter-template/__htmgo/assets" ) func RootPage(children ...h.Ren) *h.Page { @@ -20,8 +21,8 @@ func RootPage(children ...h.Ren) *h.Page { h.Text(title), ), h.Meta("viewport", "width=device-width, initial-scale=1"), - h.Link("/public/favicon.ico", "icon"), - h.Link("/public/apple-touch-icon.png", "apple-touch-icon"), + h.Link(assets.FaviconIco, "icon"), + h.Link(assets.AppleTouchIconPng, "apple-touch-icon"), h.Meta("title", title), h.Meta("charset", "utf-8"), h.Meta("author", author), @@ -30,8 +31,8 @@ func RootPage(children ...h.Ren) *h.Page { h.Meta("og:url", url), h.Link("canonical", url), h.Meta("og:description", description), - h.Link("/public/main.css", "stylesheet"), - h.Script("/public/htmgo.js"), + h.Link(assets.MainCss, "stylesheet"), + h.Script(assets.HtmgoJs), ), h.Body( h.Div(