From a0e9a672341061f8c2940e87a693f5bde63f4150 Mon Sep 17 00:00:00 2001 From: hiyach28 Date: Sat, 19 Sep 2026 06:24:04 +0530 Subject: [PATCH] feat(images): encode Markdown images to WebP at build time Brings the layer5.io idea to this repo. layer5.io gets WebP from gatsby-plugin-sharp, which has no equivalent here, but Hugo extended can encode WebP itself, so the work happens in the image render hook. - Mount content/en at assets/contentimg. Hugo can only process files that are resources, and content images are not, so this makes them reachable without moving a single file. - The render hook resolves each PNG or JPEG destination against that mount, encodes a WebP derivative, and emits with the original as the fallback. Nothing in content changes, and browsers without WebP support still get the original. - The modal now reads currentSrc, so opening an image reuses the WebP the browser already fetched instead of pulling the original. Measured on a full build: - 274 of 380 Markdown images are now offered as WebP; the rest are GIF or SVG, which are left alone. - 70.05 MB of sources become 14.37 MB of WebP, a 79% saving for any browser that takes them. - Cold build goes from 14.7s to 72.9s. A warm build, with resources/_gen present, is 16s, so CI should cache that directory. Hugo's WebP encoder is lossy only, so quality is pinned at 85. At q100 the output is larger than the source PNG, and at q85 a UI screenshot differs by a mean of 1.6 per channel with text still crisp. Signed-off-by: hiyach28 --- hugo.toml | 8 ++++ layouts/_default/_markup/render-image.html | 45 ++++++++++++++++++---- layouts/partials/image-modal.html | 6 ++- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/hugo.toml b/hugo.toml index a697e6188bb3..a76a15dbe02a 100644 --- a/hugo.toml +++ b/hugo.toml @@ -348,6 +348,14 @@ enable = false [[module.imports]] path = "github.com/google/docsy/dependencies" disable = false + [[module.mounts]] + source = "assets" + target = "assets" + # Content images are mounted into assets so the image render hook can + # process them (Hugo can only process files that are resources). + [[module.mounts]] + source = "content/en" + target = "assets/contentimg" [[module.mounts]] source = "static" target = "static" diff --git a/layouts/_default/_markup/render-image.html b/layouts/_default/_markup/render-image.html index 1caa20e2d992..34af92caad01 100644 --- a/layouts/_default/_markup/render-image.html +++ b/layouts/_default/_markup/render-image.html @@ -1,13 +1,44 @@ - - - + + +{{- $dest := .Destination -}} +{{- $alt := .Text -}} +{{- $class := "md-image-responsive" -}} +{{- with .Title }}{{ $class = printf "md-image-responsive %s" . }}{{ end -}} +{{- $img := "" -}} +{{- $ext := lower (path.Ext $dest) -}} + +{{- if and (in (slice ".png" ".jpg" ".jpeg") $ext) (not (urls.Parse $dest).Scheme) (not (hasPrefix $dest "//")) -}} + {{- $rel := $dest -}} + {{- if not (hasPrefix $dest "/") -}} + {{- with .Page.File -}} + {{- $rel = path.Join (replace .Dir "\\" "/") $dest -}} + {{- end -}} + {{- end -}} + {{- $img = resources.Get (path.Join "contentimg" $rel) -}} + {{- if not $img -}}{{- $img = resources.Get $rel -}}{{- end -}} +{{- end -}} +
- {{ .Text }} -
\ No newline at end of file +{{- with $img }} + {{- $webp := .Process "webp q85" }} + + + {{ $alt }} + +{{- else }} + {{ $alt }} +{{- end }} + diff --git a/layouts/partials/image-modal.html b/layouts/partials/image-modal.html index ef3552b0a988..72650e62a27f 100644 --- a/layouts/partials/image-modal.html +++ b/layouts/partials/image-modal.html @@ -14,8 +14,10 @@ // If it's a string (image ID), get the source using the ID src = document.getElementById(imageIdOrElement).src; } else if (imageIdOrElement instanceof HTMLImageElement) { - // If , get the source directly - src = imageIdOrElement.src; + // If , get the source directly. currentSrc is what the browser actually + // chose, so inside a the modal reuses the already-fetched WebP + // rather than downloading the larger original. + src = imageIdOrElement.currentSrc || imageIdOrElement.src; } if (src && src.includes("#")) {