Skip to content

Conversation

@TrivediKavit
Copy link

@TrivediKavit TrivediKavit commented Nov 10, 2025

Problem

The CSS minifier works correctly for flat CSS structures but fails when modern CSS nesting syntax is present. In nested structures, the parser couldn't properly handle the CSS, leading to incorrect space removal around mathematical operators (+, -, *, /) in functions like calc(), clamp(), min(), max(), etc. resulting in invalid CSS. For flat CSS, expressions in property values were safely tokenized and protected.

Root Cause

Flat CSS Succeeds: In processRuleBodies(), property values like calc(100% - 50px) are tokenized (replaced with placeholders like _CSSMIN_RBT_%d_) before processAtRulesAndSelectors() runs. This protects expressions from space-stripping regexes that remove spaces around + in selectors/at-rules.

Nested CSS Fails: The parser (which is ancient and hasn't been updated since 2018-01-15) most likely doesn't support CSS nesting as it is quite a modern feature.

For nested CSS having mathematical expressions, it fails to tokenize rule bodies correctly, leaving expressions exposed to space removal in processAtRulesAndSelectors() via the following code:

// Remove spaces before the things that should not have spaces before them.
$css = preg_replace('/ ([@{};>+)\]~=,\/\n])/S', '$1', $css);

// Remove the spaces after the things that should not have spaces after them.
$css = preg_replace('/([{}:;>+(\[~=,\/\n]) /S', '$1', $css);

This corrupts expressions in malformed nested CSS for the outer blocks.

Example of the Problem

Example Source CSS (Nested):

.header {
    padding: clamp(1rem, calc(0.8239rem + 0.7512vw), 1.5rem);
    gap: clamp(1rem, 0.8239rem + 0.7512vw, 1.5rem);
    h2 {
        font-size: clamp(1.5rem, calc(1.1479rem + 1.5023vw), 2.5rem);
        span {
            margin-top: calc(10px + 2%);
        }
    }
}

Broken Output:

.header{padding:clamp(1rem,calc(0.8239rem+0.7512vw),1.5rem);gap:clamp(1rem,0.8239rem+0.7512vw,1.5rem);h2{font-size:clamp(1.5rem,calc(1.1479rem+1.5023vw),2.5rem);span{margin-top:calc(10px + 2%)}}}

Expected Output:

.header{padding:clamp(1rem,calc(0.8239rem + 0.7512vw),1.5rem);gap:clamp(1rem,0.8239rem + 0.7512vw,1.5rem);h2{font-size:clamp(1.5rem,calc(1.1479rem + 1.5023vw),2.5rem);span{margin-top:calc(10px + 2%)}}}

Potential Solution

Added early tokenization of mathematical expressions before any space processing occurs. This protects expressions regardless of CSS structure.

Changes Proposed

  1. Mathematical Expression Tokenization in minify() (after string processing):
// Process mathematical expressions so their operators don't get accidentally minified
$css = preg_replace_callback(
    '/\b\d*\.?\d+[a-z%]*\s*[\+\-\*\/]\s*\d*\.?\d+[a-z%]*\b/Si',
    array($this, 'processMathematicalExpressionCallback'),
    $css
);
  1. Callback Method:
/**
 * Preserves mathematical expressions found
 * @param array $matches
 * @return string
 */
private function processMathematicalExpressionCallback($matches)
{
    return $this->registerPreservedToken($matches[0]);
}

Why This Works

  • Early Protection: Tokenizes expressions like 0.8239rem + 0.7512vw before parsing, replacing them with unique tokens that are restored later with original spacing.
  • Structure-Agnostic: Works for flat and nested CSS by protecting expressions upfront, avoiding parser-dependent failures.
  • Comprehensive: Covers operators and units in calc(), clamp(), etc.

Limitations

  • Nesting: Parser doesn't support CSS nesting; structure can be mangled.
  • Multi-Operator Mathematical Expressions: Regex handles basic patterns such as Operand Operator Operand; may need expansion for chained or multi-operator mathematical expressions. For example, calc(1px + 2px) works, but the minifier might miss more complex ones like calc(1px + 2px * 3px) or chained operations.

Suggestion

There is another minifier which is actively maintained: matthiasmullie/minify which can be looked into. In my local testing, it preserves the spaces around mathematical expressions by default, even inside nested CSS. It also handles multi-operator mathematical expressions correctly, such as calc(1px + 2px * 3px).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants