Skip to content

feat(rendering): minify rendered HTML behind FEATURE_FLAG_MINIFY_HTML - #36852

Open
fmontes wants to merge 1 commit into
mainfrom
issue-36851-native-html-minification
Open

feat(rendering): minify rendered HTML behind FEATURE_FLAG_MINIFY_HTML#36852
fmontes wants to merge 1 commit into
mainfrom
issue-36851-native-html-minification

Conversation

@fmontes

@fmontes fmontes commented Jul 31, 2026

Copy link

Adds an opt-in HTML minifier that strips insignificant whitespace, line
breaks and indentation from rendered pages before they are written to the
response.

Wired at the two seams that together cover every render path:
- VelocityLiveMode.writePage() for LIVE mode, before the page cache write
  so minification is paid once per cache fill rather than per request
- VelocityModeHandler.eval() for preview/edit/admin modes and the REST and
  GraphQL getPageHtml callers

The minifier is conservative by design: pre/textarea/script/style content
is copied byte-for-byte, whitespace between inline elements is collapsed
rather than removed so words are never joined, and any failure returns the
original markup.

Refs #36851

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @fmontes's task in 1m 58s —— View job


Code Review — native HTML minification

  • Gather context
  • Review HtmlMinifier.java
  • Review integration seams (VelocityLiveMode, VelocityModeHandler)
  • Review FeatureFlagName + tests
  • Post findings

Nice work — the design (two seams, cache-fill-time minification, best-effort fallback, idempotency, preserved regions) is sound and well-documented. One real correctness bug stands out.

New Issues

  • 🟠 High: dotCMS/src/main/java/com/dotcms/rendering/util/HtmlMinifier.java:80-142Whitespace inside quoted attribute values is collapsed. minify() tracks no tag/attribute context; isSignificantBefore/isSignificantAfter only look at the immediately adjacent characters. Any whitespace run whose neighbors are not >/< is treated as significant inter-element text and collapsed to a single space — including whitespace inside an attribute value.

    Trace <input value="a b">: the a b run is preceded by a (≠ >, significant) and followed by b (≠ <, significant) → emitted as a b. Output becomes <input value="a b">.

    Impact when the flag is on:

    • <input type="hidden" value="two spaces"> → submitted form value silently changes.
    • data-config='{"key": "a b"}' and other JSON/data payloads → value changes, can break client JS.
    • alt/title/aria-label with intentional spacing → altered.
    • A newline inside an attribute value (VTL can emit these) is collapsed the same way.

    This is silent data corruption, not just cosmetic. The existing test test_minify_collapses_attribute_and_document_whitespace only covers whitespace between attributes (<div class="a" id="b">), so the case is untested. A minimal fix is to track whether the cursor is inside a tag and inside a quoted value, and copy attribute-value whitespace verbatim (and add a regression test).

    Fix this →

Notes (non-blocking)

  • HtmlMinifier.java:253-266 — the while loop in findPreserveTagEnd always returns on its first iteration (the trailing return html.length() after the loop is unreachable). Not a bug, but the loop reads as if it iterates over multiple close-tag candidates when it does not. Consider replacing with a straight-line lookup for clarity.
  • VelocityModeHandler.eval():111-113 — CSP-then-minify ordering is correct, and applying the minifier to preview/edit output is harmless. Good.
  • LIVE path (VelocityLiveMode.writePage:265-280) correctly minifies before the cache add(), so the cost is paid per cache fill, and the non-minify branch still streams directly. Good.

Everything else — feature-flag gating, Config.getBooleanProperty, Logger.warnAndDebug on failure returning original markup, preserved-region handling, conditional-comment retention — follows dotCMS conventions and looks correct.

• branch issue-36851-native-html-minification

@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Jul 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a feature-flagged, dependency-free HTML minification step to dotCMS’s rendering pipeline so rendered pages can be served (and, in LIVE mode, cached) without indentation/blank lines/comments introduced by Velocity templates—opt-in via FEATURE_FLAG_MINIFY_HTML.

Changes:

  • Introduces HtmlMinifier to conservatively collapse insignificant whitespace and strip HTML comments while preserving <pre>, <textarea>, <script>, and <style> bodies.
  • Hooks minification into VelocityLiveMode.writePage() (before page cache write) and into VelocityModeHandler.eval() (post-CSP processing path).
  • Adds FEATURE_FLAG_MINIFY_HTML and a new unit test suite for the minifier.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
dotCMS/src/main/java/com/dotcms/rendering/util/HtmlMinifier.java New minifier implementation guarded by FEATURE_FLAG_MINIFY_HTML.
dotCMS/src/main/java/com/dotcms/rendering/velocity/servlet/VelocityLiveMode.java Minifies LIVE mode output before writing/storing into the page cache when enabled.
dotCMS/src/main/java/com/dotcms/rendering/velocity/servlet/VelocityModeHandler.java Minifies eval() output (after CSP application when configured).
dotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java Adds the FEATURE_FLAG_MINIFY_HTML feature flag constant + javadoc.
dotCMS/src/test/java/com/dotcms/rendering/util/HtmlMinifierTest.java Adds unit tests for whitespace significance, preserved regions, comments, and idempotence.

Comment on lines +150 to +164
private static boolean isSignificantBefore(final StringBuilder out) {

final int lastChar = out.length() - 1;
if (out.charAt(lastChar) != '>') {
// Preceded by text content.
return true;
}

final int tagStart = out.lastIndexOf("<");
if (tagStart < 0) {
return true;
}

return isInlineTag(out.substring(tagStart, out.length()));
}
Comment on lines +144 to +153
/**
* Given: null or empty input.
* Expected: it is returned untouched rather than throwing.
*/
@Test
public void test_minifyIfEnabled_handles_null_and_empty() {
assertEquals(null, HtmlMinifier.minifyIfEnabled(null));
assertEquals("", HtmlMinifier.minifyIfEnabled(""));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Native, configurable HTML minification in the core rendering engine

3 participants