Skip to content

Commit

Permalink
fix: increase hash size for autogenerated IDs to reduce collisions - f…
Browse files Browse the repository at this point in the history
…ixes #978

Co-authored-by: Adrian Hesketh <[email protected]>
  • Loading branch information
GustavoSept and a-h authored Nov 12, 2024
1 parent 4f2ce16 commit 9c8ad4d
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.793
0.2.796
4 changes: 2 additions & 2 deletions generator/test-css-expression/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
)

var expected = templ.ComponentCSSClass{
ID: "className_34fc",
Class: templ.SafeCSS(`.className_34fc{background-color:#ffffff;max-height:calc(100vh - 170px);color:#ff0000;}`),
ID: "className_34fc0328",
Class: templ.SafeCSS(`.className_34fc0328{background-color:#ffffff;max-height:calc(100vh - 170px);color:#ff0000;}`),
}

func TestCSSExpression(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion generator/test-css-middleware/expected.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div class="red_050e">
<div class="red_050e5e03">
Red text
</div>
2 changes: 1 addition & 1 deletion generator/test-css-middleware/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
//go:embed expected.html
var expected string

var expectedCSS = `.red_050e{color:red;}
var expectedCSS = `.red_050e5e03{color:red;}
`

func Test(t *testing.T) {
Expand Down
24 changes: 12 additions & 12 deletions generator/test-css-usage/expected.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,33 @@
}
</style>
<div class="test">Style tags are supported</div>
<style type="text/css">.cssComponentGreen_58d2{color:#00ff00;}</style>
<div class="cssComponentGreen_58d2">CSS components are supported</div>
<div class="cssComponentGreen_58d2 classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
<div class="cssComponentGreen_58d2 classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
<style type="text/css">.cssComponentGreen_58d2872e{color:#00ff00;}</style>
<div class="cssComponentGreen_58d2872e">CSS components are supported</div>
<div class="cssComponentGreen_58d2872e classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
<div class="cssComponentGreen_58d2872e classA &amp;&amp;&amp;classB classC d e" type="button">Both CSS components and constants are supported</div>
<div class="a c">Maps can be used to determine if a class should be added or not.</div>
<style type="text/css">.e_739d{font-size:14pt;}</style>
<div class="a c e_739d">KV can be used to conditionally set classes.</div>
<style type="text/css">.e_739d4573{font-size:14pt;}</style>
<div class="a c e_739d4573">KV can be used to conditionally set classes.</div>
<div class="bg-violet-500 hover:bg-red-600 hover:bg-sky-700 text-[#50d71e] w-[calc(100%-4rem)">Psuedo attributes and complex class names are supported.</div>
<div class="a&#34; onClick=&#34;alert(&#39;hello&#39;)&#34;">
Class names are HTML escaped.
</div>
<style type="text/css">
.loading_a3cc{width:50%;}
.loading_a3cc3f08{width:50%;}
</style>
<div class="loading_a3cc">
<div class="loading_a3cc3f08">
CSS components can be used with arguments.
</div>
<style type="text/css">
.loading_9ccc{width:100%;}
.loading_9ccc4ca9{width:100%;}
</style>
<div class="loading_9ccc">
<div class="loading_9ccc4ca9">
CSS components can be used with arguments.
</div>
<style type="text/css">
.windVaneRotation_b68b{transform:rotate(45deg);}
.windVaneRotation_b68b990e{transform:rotate(45deg);}
</style>
<div class="windVaneRotation_b68b">
<div class="windVaneRotation_b68b990e">
Rotate
</div>

8 changes: 4 additions & 4 deletions generator/test-element-attributes/expected.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<style type="text/css">
.important_2ed1{width:100;}
.important_2ed176fc{width:100;}
</style>
<div style="width: 100;">
Important
</div>
<style type="text/css">
.unimportant_900a{width:50;}
.unimportant_900aeb18{width:50;}
</style>
<div style="width: 100;" class="unimportant_900a">
<div style="width: 100;" class="unimportant_900aeb18">
Unimportant
</div>
<div style="width: 100;" class="unimportant_900a">
<div style="width: 100;" class="unimportant_900aeb18">
Else
</div>
<div data-script="on click
Expand Down
4 changes: 2 additions & 2 deletions runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,11 @@ func (css ComponentCSSClass) ClassName() string {
// CSSID calculates an ID.
func CSSID(name string, css string) string {
sum := sha256.Sum256([]byte(css))
hp := hex.EncodeToString(sum[:])[0:4]
hs := hex.EncodeToString(sum[:])[0:8] // NOTE: See issue #978. Minimum recommended hs length is 6.
// Benchmarking showed this was fastest, and with fewest allocations (1).
// Using strings.Builder (2 allocs).
// Using fmt.Sprintf (3 allocs).
return name + "_" + hp
return name + "_" + hs
}

// NewCSSMiddleware creates HTTP middleware that renders a global stylesheet of ComponentCSSClass
Expand Down
24 changes: 24 additions & 0 deletions runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ import (
"github.com/google/go-cmp/cmp"
)

func TestCSSID(t *testing.T) {
t.Run("minimum hash suffix length is 8", func(t *testing.T) {
// See issue #978.
name := "classA"
css := "background-color:white;"
actual := len(templ.CSSID(name, css))
expected := len(name) + 1 + 8
if expected != actual {
t.Errorf("expected length %d, got %d", expected, actual)
}
})
t.Run("known hash collisions are avoided", func(t *testing.T) {
name := "classA"
// Note that the first 4 characters of the hash are the same.
css1 := "grid-column:1;grid-row:1;" // After hash: f781266f
css2 := "grid-column:13;grid-row:6;" // After hash: f781f18b
id1 := templ.CSSID(name, css1)
id2 := templ.CSSID(name, css2)
if id1 == id2 {
t.Errorf("hash collision: %s == %s", id1, id2)
}
})
}

func TestCSSHandler(t *testing.T) {
tests := []struct {
name string
Expand Down

0 comments on commit 9c8ad4d

Please sign in to comment.