@@ -1045,12 +1045,6 @@ fn minify_template_literal<'a>(lit: &mut TemplateLiteral<'a>, ast: AstBuilder<'a
10451045 }
10461046 // Skip line comments, but be careful not to break URLs like `https://...`
10471047 b'/' if paren_depth == 0 && ( i == 0 || bytes[ i - 1 ] != b':' ) => {
1048- // Remove trailing space before comment.
1049- // Comment will end with a line break, so space will be added again if required.
1050- if output. last ( ) . is_some_and ( |& last| last == b' ' ) {
1051- output. pop ( ) ;
1052- }
1053-
10541048 let end_index =
10551049 bytes[ i + 2 ..] . iter ( ) . position ( |& b| matches ! ( b, b'\n' | b'\r' ) ) ;
10561050 if let Some ( end_index) = end_index {
@@ -1067,6 +1061,17 @@ fn minify_template_literal<'a>(lit: &mut TemplateLiteral<'a>, ast: AstBuilder<'a
10671061 }
10681062 }
10691063 // Skip and compress whitespace.
1064+ //
1065+ // CSS allows removing spaces around certain delimiters without changing meaning:
1066+ // - `color: red` -> `color:red` (spaces after colons)
1067+ // - `.a { }` -> `.a{}` (spaces around braces)
1068+ // - `margin: 1px , 2px` -> `margin:1px,2px` (spaces around commas)
1069+ //
1070+ // But spaces are significant in other contexts:
1071+ // - `.a .b` - space between selectors preserved
1072+ // - `padding: 0 ${VALUE}px` - space before interpolation preserved
1073+ // - `${A} ${B}` - space between interpolations preserved
1074+ // - `.class :hover` - space before pseudo-selector preserved
10701075 _ if cur_byte. is_ascii_whitespace ( ) => {
10711076 // Consume this whitespace and any further whitespace characters
10721077 let mut next_byte;
@@ -1078,56 +1083,23 @@ fn minify_template_literal<'a>(lit: &mut TemplateLiteral<'a>, ast: AstBuilder<'a
10781083 }
10791084 }
10801085
1081- // Decide whether to preserve this whitespace character.
1082- // CSS allows removing spaces around certain delimiters without changing meaning:
1083- // - `color: red` -> `color:red` (spaces around colons)
1084- // - `.a { }` -> `.a{}` (spaces around braces)
1085- // - `margin: 1px , 2px` -> `margin:1px,2px` (spaces around commas)
1086- // But spaces are significant in other contexts like selectors: `.a .b` != `.a.b`.
1087-
1088- // Check what comes before this whitespace
1089- if let Some ( & last) = output. last ( ) {
1090- if matches ! ( last, b' ' | b':' | b'{' | b'}' | b',' | b';' ) {
1091- // Always safe to remove whitespace after these characters
1092- continue ;
1093- }
1094- } else if quasi_index == 0 {
1095- // We're at the start of the first quasi, so can trim leading whitespace
1096- continue ;
1097- } else {
1098- // We're at start of a later quasi.
1099- // Preserve space to avoid joining with previous interpolation.
1100- }
1101-
1102- // Check what comes after this whitespace.
1103- // - If we're at the end of the quasi (`next_byte == None`), and it's not the last,
1104- // preserve the space to avoid joining with the next interpolation.
1105- // - Remove whitespace before `{`, `}`, `,`, and `;`.
1106- // Note: We intentionally DON'T include ':' here because spaces before colons
1107- // are significant in CSS. ` :hover` (descendant pseudo-selector) is different
1108- // from `:hover` (direct pseudo-selector). Example: `.parent :hover` selects any
1109- // hovered descendant, while `.parent:hover` selects the parent when hovered.
1110- if let Some ( & next) = next_byte {
1111- if matches ! ( next, b'{' | b'}' | b',' | b';' ) {
1112- // Always safe to remove whitespace before these characters
1113- continue ;
1114- }
1115- } else if quasi_index == quasis. len ( ) - 1 {
1116- // We're at the end of the last quasi, so can trim trailing whitespace
1117- continue ;
1118- } else {
1119- // We're at end of a quasi which isn't the last one.
1120- // Preserve space to avoid joining with next interpolation.
1121- }
1122-
1123- // Preserve this space character.
1124- // Examples:
1125- // - `padding: 0 ${VALUE}px` - space before interpolation preserved
1126- // - `${A} ${B}` - space between interpolations preserved
1127- // - `.class :hover` - space before pseudo-selector preserved
1128- output. push ( b' ' ) ;
1086+ // Preserve this space if it's not following a character which doesn't require one.
1087+ // Note: If a space is inserted, it may be removed again if it's followed
1088+ // by `{`, `}`, `,`, or `;`.
1089+ insert_space_if_required ( & mut output, quasi_index) ;
11291090 continue ;
11301091 }
1092+ // Remove whitespace before `{`, `}`, `,`, and `;`.
1093+ //
1094+ // Note: We intentionally DON'T include ':' here because spaces before colons
1095+ // are significant in CSS. ` :hover` (descendant pseudo-selector) is different
1096+ // from `:hover` (direct pseudo-selector). Example: `.parent :hover` selects any
1097+ // hovered descendant, while `.parent:hover` selects the parent when hovered.
1098+ b'{' | b'}' | b',' | b';' => {
1099+ if output. last ( ) == Some ( & b' ' ) {
1100+ output. pop ( ) ;
1101+ }
1102+ }
11311103 _ => { }
11321104 }
11331105
@@ -1136,6 +1108,11 @@ fn minify_template_literal<'a>(lit: &mut TemplateLiteral<'a>, ast: AstBuilder<'a
11361108 }
11371109 }
11381110
1111+ // Remove trailing space from last quasi
1112+ if output. last ( ) == Some ( & b' ' ) {
1113+ output. pop ( ) ;
1114+ }
1115+
11391116 // Update last quasi.
11401117 // SAFETY: Output is all picked from the original `raw` values and is guaranteed to be valid UTF-8.
11411118 let output_str = unsafe { std:: str:: from_utf8_unchecked ( & output) } ;
@@ -1163,6 +1140,26 @@ fn minify_template_literal<'a>(lit: &mut TemplateLiteral<'a>, ast: AstBuilder<'a
11631140 }
11641141}
11651142
1143+ /// Insert a space, unless preceding character makes it possible to skip.
1144+ ///
1145+ /// See comments above about whitespace removal.
1146+ fn insert_space_if_required ( output : & mut Vec < u8 > , quasi_index : usize ) {
1147+ if let Some ( & last) = output. last ( ) {
1148+ // Always safe to remove whitespace after these characters
1149+ if matches ! ( last, b' ' | b':' | b'{' | b'}' | b',' | b';' ) {
1150+ return ;
1151+ }
1152+ } else {
1153+ // We're at start of a quasi. If it's the first, trim leading space.
1154+ // Otherwise, preserve space to avoid joining with previous interpolation.
1155+ if quasi_index == 0 {
1156+ return ;
1157+ }
1158+ }
1159+
1160+ output. push ( b' ' ) ;
1161+ }
1162+
11661163#[ cfg( test) ]
11671164mod tests {
11681165 use super :: * ;
0 commit comments