resources: Resume chained resource transformations

This is a follow up the bug fix in #15189. With the example given in that issue, the `css.Build` would be performed twice, which was unfortunate.

This commit fixes that by resuming the transformation from the last executed transformation.

See #15189
This commit is contained in:
Bjørn Erik Pedersen
2026-08-11 12:07:24 +02:00
parent f88f0a9f59
commit 995a2159e5
2 changed files with 49 additions and 6 deletions
+10
View File
@@ -106,6 +106,16 @@ func MD5FromStringHexEncoded(f string) string {
return hex.EncodeToString(h.Sum(nil))
}
// MD5FromReaderHexEncoded returns the MD5 hash of the given reader.
func MD5FromReaderHexEncoded(r io.Reader) string {
h := md5.New()
_, err := io.Copy(h, r)
if err != nil {
return ""
}
return hex.EncodeToString(h.Sum(nil))
}
// HashString returns a hash from the given elements.
// It will panic if the hash cannot be calculated.
// Note that this hash should be used primarily for identity, not for change detection as
+39 -6
View File
@@ -455,11 +455,26 @@ func (r *resourceAdapter) publish() {
}
func (r *resourceAdapter) TransformationKey() string {
var key string
for _, tr := range r.transformations {
key = key + "_" + tr.Key().Value()
return r.transformationKey(r.transformations)
}
func (r *resourceAdapter) transformationKey(trs []ResourceTransformation) string {
sb := bp.GetBuffer()
defer bp.PutBuffer(sb)
for _, tr := range trs {
sb.WriteString("_")
sb.WriteString(tr.Key().Value())
}
return r.spec.ResourceCache.cleanKey(r.target.Key()) + "_" + hashing.MD5FromStringHexEncoded(key)
h := hashing.MD5FromReaderHexEncoded(sb)
sb.Reset()
sb.WriteString(r.spec.ResourceCache.cleanKey(r.target.Key()))
sb.WriteString(h)
return sb.String()
}
func (r *resourceAdapter) getOrTransform(publish, setContent bool) error {
@@ -515,6 +530,25 @@ func (r *resourceAdapter) getOrTransform(publish, setContent bool) error {
func (r *resourceAdapter) transform(key string, publish, setContent bool) (*resourceAdapterInner, error) {
cache := r.spec.ResourceCache
trs := r.transformations
writeToFileCache := false
// If a prefix of this chain has already run (e.g. .Content or .Data was
// accessed before more transformations were chained), resume from the
// longest cached prefix instead of re-running it.
for i := len(trs) - 1; i > 0; i-- {
if inner, found := cache.cacheResourceTransformation.Get(r.ctx, r.transformationKey(trs[:i])); found {
for _, tr := range trs[:i] {
if transformationsToCacheOnDisk[tr.Key().Name] {
writeToFileCache = true
}
}
r.target = inner.target
trs = trs[i:]
break
}
}
b1 := bp.GetBuffer()
b2 := bp.GetBuffer()
defer bp.PutBuffer(b1)
@@ -549,11 +583,10 @@ func (r *resourceAdapter) transform(key string, publish, setContent bool) (*reso
tctx.SourcePath = strings.TrimPrefix(tctx.InPath, "/")
counter := 0
writeToFileCache := false
var transformedContentr io.Reader
for i, tr := range r.transformations {
for i, tr := range trs {
if i != 0 {
tctx.InMediaType = tctx.OutMediaType
}