From 753472abb3174a9b68f9a9806a028c7011d8b93d Mon Sep 17 00:00:00 2001 From: connerohnesorge Date: Thu, 3 Jul 2025 04:20:49 -0500 Subject: [PATCH 1/2] fix: make code generation deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix non-deterministic map iteration in generateGo function that caused generated Go code to have different key ordering on each run. Changed from direct map iteration to using the existing sortMap function to ensure consistent ordering of ClassMapStr entries. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- tw.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tw.go b/tw.go index 45c3e12..16979ba 100644 --- a/tw.go +++ b/tw.go @@ -171,13 +171,14 @@ func generateGo( "github.com/conneroisu/twerge", "CacheValue", ).Values(jen.DictFunc(func(d jen.Dict) { - for k := range g.Cache() { + keys, values := sortMap(g.Cache()) + for i, k := range keys { d[jen.Lit(k)] = jen.Qual( "github.com/conneroisu/twerge", "CacheValue", ).Values(jen.Dict{ - jen.Id("Generated"): jen.Lit(g.Cache()[k].Generated), - jen.Id("Merged"): jen.Lit(g.Cache()[k].Merged), + jen.Id("Generated"): jen.Lit(values[i].Generated), + jen.Id("Merged"): jen.Lit(values[i].Merged), }) } })) From 0a5d7938fe216f95bf28d6ba158de008bab68a23 Mon Sep 17 00:00:00 2001 From: connerohnesorge Date: Thu, 3 Jul 2025 04:23:03 -0500 Subject: [PATCH 2/2] Replace gen.go with build.go in examples and update documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename gen.go to build.go in dashboard and simple examples - Update all documentation references from gen.go to build.go - Update command examples in README files and documentation - Maintain consistent naming convention across examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 76 +++- doc/src/examples/basic.md | 8 +- doc/src/examples/complex-webapp.md | 6 +- doc/src/examples/tailwind-build.md | 24 +- doc/src/faq.md | 2 +- examples/README.md | 4 +- examples/dashboard/README.md | 2 +- examples/dashboard/{gen.go => build.go} | 2 +- examples/simple/{gen.go => build.go} | 0 examples/simple/views/view_templ.go | 526 ++++++++++++++++++++++++ 10 files changed, 621 insertions(+), 29 deletions(-) rename examples/dashboard/{gen.go => build.go} (99%) rename examples/simple/{gen.go => build.go} (100%) create mode 100644 examples/simple/views/view_templ.go diff --git a/README.md b/README.md index 89e8cd7..fdd2d0b 100644 --- a/README.md +++ b/README.md @@ -251,9 +251,15 @@ A typical development workflow with twerge: - [func CodeGen\(g \*Generator, goPath string, cssPath string, htmlPath string, comps ...templ.Component\) error](<#CodeGen>) - [func If\(ok bool, trueClass string, falseClass string\) string](<#If>) +- [func IsDev\(\) bool](<#IsDev>) - [func It\(raw string\) string](<#It>) - [func SetDefault\(g \*Generator\)](<#SetDefault>) - [type CacheValue](<#CacheValue>) +- [type DebugHandler](<#DebugHandler>) + - [func NewDebugHandler\(\) \*DebugHandler](<#NewDebugHandler>) + - [func \(d \*DebugHandler\) Cache\(\) map\[string\]CacheValue](<#DebugHandler.Cache>) + - [func \(d \*DebugHandler\) It\(s string\) string](<#DebugHandler.It>) + - [func \(d \*DebugHandler\) SetCache\(newC map\[string\]CacheValue\)](<#DebugHandler.SetCache>) - [type Generator](<#Generator>) - [func Default\(\) \*Generator](<#Default>) - [func New\(h Handler\) \*Generator](<#New>) @@ -280,6 +286,15 @@ func If(ok bool, trueClass string, falseClass string) string If returns a short unique CSS class name from the merged classes taking an additional boolean parameter. + +## func [IsDev]() + +```go +func IsDev() bool +``` + +IsDev returns true if the default generator is using a debug handler. + ## func [It]() @@ -320,8 +335,59 @@ type CacheValue struct { } ``` + +## type [DebugHandler]() + +DebugHandler is a [Handler](<#Handler>) that can be used to debug tailwind classes. + +It is not meant to be used in production. + +It will return the same class name for the same input. + +```go +type DebugHandler struct { + // contains filtered or unexported fields +} +``` + + +### func [NewDebugHandler]() + +```go +func NewDebugHandler() *DebugHandler +``` + +NewDebugHandler creates a new DebugHandler. + + +### func \(\*DebugHandler\) [Cache]() + +```go +func (d *DebugHandler) Cache() map[string]CacheValue +``` + +Cache returns the cache of the [Generator](<#Generator>). + + +### func \(\*DebugHandler\) [It]() + +```go +func (d *DebugHandler) It(s string) string +``` + +It returns a short unique CSS class name from the merged classes. + + +### func \(\*DebugHandler\) [SetCache]() + +```go +func (d *DebugHandler) SetCache(newC map[string]CacheValue) +``` + +SetCache sets the cache of the [Generator](<#Generator>). + -## type [Generator]() +## type [Generator]() Generator generates all the code needed to use Twerge statically. @@ -341,7 +407,7 @@ func Default() *Generator Default returns the default [Generator](<#Generator>). -### func [New]() +### func [New]() ```go func New(h Handler) *Generator @@ -350,7 +416,7 @@ func New(h Handler) *Generator New creates a new Generator with the given non\-nil Handler. -### func \(Generator\) [Cache]() +### func \(Generator\) [Cache]() ```go func (Generator) Cache() map[string]CacheValue @@ -359,7 +425,7 @@ func (Generator) Cache() map[string]CacheValue Cache returns the cache of the [Generator](<#Generator>). -### func \(\*Generator\) [It]() +### func \(\*Generator\) [It]() ```go func (g *Generator) It(classes string) string @@ -372,7 +438,7 @@ If the class name already exists, it will return the existing class name. If the class name does not exist, it will generate a new class name and return it. -## type [Handler]() +## type [Handler]() Handler is the interface that needs to be implemented to customize the behavior of the [Generator](<#Generator>). diff --git a/doc/src/examples/basic.md b/doc/src/examples/basic.md index 2447b60..71b411b 100644 --- a/doc/src/examples/basic.md +++ b/doc/src/examples/basic.md @@ -15,7 +15,7 @@ simple/ ├── classes/ │ ├── classes.go # Generated Go code with class mappings │ └── classes.html # HTML output of class definitions -├── gen.go # Code generation script +├── build.go # Code generation script ├── go.mod # Go module file ├── input.css # TailwindCSS input file ├── main.go # Web server @@ -27,9 +27,9 @@ simple/ ### Code Generation -The `gen.go` file handles Twerge code generation and TailwindCSS processing: +The `build.go` file handles Twerge code generation and TailwindCSS processing: -```go title="gen.go" +```go title="build.go" //go:build ignore // +build ignore @@ -149,7 +149,7 @@ templ generate ./views 3. Run the code generation: ```sh -go run gen.go +go run build.go ``` 4. Run the server: diff --git a/doc/src/examples/complex-webapp.md b/doc/src/examples/complex-webapp.md index 0a479d3..0a53962 100644 --- a/doc/src/examples/complex-webapp.md +++ b/doc/src/examples/complex-webapp.md @@ -21,7 +21,7 @@ dashboard/ ├── classes/ │ ├── classes.go # Generated Go code with class mappings │ └── classes.html # HTML output of class definitions -├── gen.go # Code generation script +├── build.go # Code generation script ├── go.mod # Go module file ├── input.css # TailwindCSS input file ├── main.go # Web server implementation @@ -37,7 +37,7 @@ dashboard/ The dashboard example uses multiple components, all processed by Twerge: -```go title="gen.go" +```go title="build.go" //go:build ignore // +build ignore @@ -532,7 +532,7 @@ templ generate ./views 3. Run the code generation: ```sh -go run gen.go +go run build.go ``` 4. Run the server: diff --git a/doc/src/examples/tailwind-build.md b/doc/src/examples/tailwind-build.md index e50dcfd..d314271 100644 --- a/doc/src/examples/tailwind-build.md +++ b/doc/src/examples/tailwind-build.md @@ -15,7 +15,7 @@ A typical Twerge-Tailwind integration includes these steps: Here's a simple build script that handles code generation and Tailwind processing: -```go title="gen.go" +```go title="build.go" //go:build ignore // +build ignore @@ -212,7 +212,7 @@ func runBuild() { For production builds, you'll want to minify your CSS and use Tailwind's purge feature to remove unused styles: -```go title="gen_prod.go" +```go title="build_prod.go" //go:build ignore // +build ignore @@ -292,10 +292,10 @@ func runTailwind(prod bool) { Usage: ```sh # Development build -go run gen_prod.go +go run build_prod.go # Production build (minified) -go run gen_prod.go -prod +go run build_prod.go -prod ``` ## Makefile Integration @@ -307,19 +307,19 @@ You can create a Makefile to simplify common build tasks: dev: templ generate ./views - go run gen.go + go run build.go watch: go run watch.go build: templ generate ./views - go run gen.go + go run build.go go build -o app ./main.go prod: templ generate ./views - go run gen_prod.go -prod + go run build_prod.go -prod go build -o app -ldflags="-s -w" ./main.go clean: @@ -361,7 +361,7 @@ jobs: run: templ generate ./views - name: Build production CSS - run: go run gen_prod.go -prod + run: go run build_prod.go -prod - name: Build application run: go build -o app -ldflags="-s -w" ./main.go @@ -405,7 +405,7 @@ COPY . . RUN templ generate ./views # Build with Twerge -RUN go run gen_prod.go -prod +RUN go run build_prod.go -prod # Build the Go application RUN CGO_ENABLED=0 GOOS=linux go build -o app -ldflags="-s -w" ./main.go @@ -430,7 +430,7 @@ CMD ["./app"] You can extend your build script to generate multiple theme variants: -```go title="gen_themes.go" +```go title="build_themes.go" //go:build ignore // +build ignore @@ -528,10 +528,10 @@ func runTailwind(input, output string) { Usage: ```sh # Build default theme -go run gen_themes.go +go run build_themes.go # Build dark theme -go run gen_themes.go -theme dark +go run build_themes.go -theme dark ``` This example demonstrates how to integrate Twerge into your Tailwind CSS build process for both development and production environments. \ No newline at end of file diff --git a/doc/src/faq.md b/doc/src/faq.md index e4c035b..6b000a5 100644 --- a/doc/src/faq.md +++ b/doc/src/faq.md @@ -135,7 +135,7 @@ jobs: - name: Generate templ code run: templ generate ./views - name: Run Twerge code generation - run: go run ./gen.go + run: go run ./build.go - name: Build application run: go build -o app ``` diff --git a/examples/README.md b/examples/README.md index 6a290a3..60d64ba 100644 --- a/examples/README.md +++ b/examples/README.md @@ -34,7 +34,7 @@ templ generate ./views 3. Run the code generation: ```sh -go run gen.go +go run build.go ``` 4. Run the server: @@ -47,6 +47,6 @@ go run main.go ## Notes - Each example follows the same structure with `views/`, `classes/`, and `_static/` directories -- The `gen.go` file handles twerge code generation and TailwindCSS processing +- The `build.go` file handles twerge code generation and TailwindCSS processing - `classes/classes.go` contains the generated class mappings - `input.css` and `tailwind.config.js` manage TailwindCSS configuration \ No newline at end of file diff --git a/examples/dashboard/README.md b/examples/dashboard/README.md index 6626c6c..0800d58 100644 --- a/examples/dashboard/README.md +++ b/examples/dashboard/README.md @@ -17,7 +17,7 @@ templ generate 2. Run the code generation: ```sh -go run gen.go +go run build.go ``` 3. Run the server: diff --git a/examples/dashboard/gen.go b/examples/dashboard/build.go similarity index 99% rename from examples/dashboard/gen.go rename to examples/dashboard/build.go index 186d1e8..bdf8947 100644 --- a/examples/dashboard/gen.go +++ b/examples/dashboard/build.go @@ -62,4 +62,4 @@ func runTailwind() { if err := cmd.Run(); err != nil { panic(err) } -} +} \ No newline at end of file diff --git a/examples/simple/gen.go b/examples/simple/build.go similarity index 100% rename from examples/simple/gen.go rename to examples/simple/build.go diff --git a/examples/simple/views/view_templ.go b/examples/simple/views/view_templ.go new file mode 100644 index 0000000..9a9b3f6 --- /dev/null +++ b/examples/simple/views/view_templ.go @@ -0,0 +1,526 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.865 +package views + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "github.com/conneroisu/twerge" + +func View() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "stellar") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 = []any{twerge.It("bg-gray-50 text-gray-900 flex flex-col min-h-screen")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 = []any{twerge.It("bg-indigo-600 text-white shadow-md")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 = []any{twerge.It("container mx-auto px-4 py-4 flex justify-between items-center")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 = []any{twerge.It("flex items-center space-x-2")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 = []any{twerge.It("h-8 w-8")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 = []any{twerge.It("text-2xl font-bold")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

stellar

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 = []any{twerge.It("container mx-auto px-4 py-6 flex-grow")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
Content
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 = []any{twerge.It("bg-gray-800 text-white py-6")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var24...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 = []any{twerge.It("container mx-auto px-4")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var26...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 = []any{twerge.It("flex flex-col md:flex-row justify-between items-center")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var28...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 = []any{twerge.It("mb-4 md:mb-0")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var30...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 = []any{twerge.It("text-xl font-semibold")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var32...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

stellar

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var34 = []any{twerge.It("text-gray-400")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var34...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

Stellar Contracting Services

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var36 = []any{twerge.It("flex space-x-4")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var36...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var38 = []any{twerge.It("text-gray-400 hover:text-white transition-colors")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var38...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "GitHub
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var40 = []any{twerge.It("text-gray-400 hover:text-white transition-colors")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var40...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "Documentation
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var42 = []any{twerge.It("text-gray-400 hover:text-white transition-colors")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var42...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "API
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var44 = []any{twerge.It("mt-4 text-center text-gray-400 text-sm")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var44...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
© 1999 stellar. All rights reserved.
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate