Skip to content

Commit eab9d43

Browse files
committed
postcss-minify: preserve comments
1 parent 361cd5c commit eab9d43

6 files changed

Lines changed: 27 additions & 5 deletions

File tree

plugins/postcss-minify/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changes to PostCSS Minify
22

3+
### Unreleased (patch)
4+
5+
- Preserve comments in selectors and values. e.g. `a/* a comment */.bar {}` -> `a/**/.bar {}`
6+
37
### 2.0.4
48

59
_May 27, 2025_
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"use strict";var e=require("@csstools/css-tokenizer");const r=/license|copyright|sourcemappingurl/i,t=/\s|\/\*/,s=/^layer$/i;function minify(r,s){if(!s)return s;if(r.has(s))return r.get(s);const o=s.trim();if(""===o)return r.set(s,""),"";if(!t.test(o))return r.set(s,o),o;let n=!1,i="";const a=e.tokenizer({css:o});for(;!a.endOfFile();){const r=a.nextToken();e.isTokenWhiteSpaceOrComment(r)?(n||(i+=" "),n=!0):(n=!1,i+=r[1])}return r.set(s,i),i}function removeEmptyNodes(e){if("rule"===e.type){if(0===e.nodes?.length){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}}else if("atrule"===e.type&&0===e.nodes?.length&&!s.test(e.name)){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}return!1}function setSemicolon(e){if(!e.raws.semicolon)return;const r=e.last;"decl"===r?.type&&r.variable||(e.raws.semicolon=!1)}const creator=()=>{const e=new Map;return{postcssPlugin:"postcss-minify",OnceExit(t){t.raws.before="",t.raws.after="\n",t.walk(t=>{switch(t.type){case"atrule":if(removeEmptyNodes(t))return;return t.raws.after="",t.raws.afterName=" ",t.raws.before="",t.raws.between="",t.raws.params=void 0,setSemicolon(t),void(t.params=minify(e,t.params));case"rule":if(removeEmptyNodes(t))return;return t.raws.after="",t.raws.before="",t.raws.between="",t.raws.selector=void 0,setSemicolon(t),void(t.selector=minify(e,t.selector));case"decl":return t.prop.startsWith("--")?void(t.raws.before=""):(t.raws.before="",t.raws.between=":",t.raws.important=t.important?"!important":"",t.raws.value=void 0,void(t.value=minify(e,t.value)));case"comment":return t.text.startsWith("!")||r.test(t.text)?void(t.raws.before=""):void t.remove()}})}}};creator.postcss=!0,module.exports=creator;
1+
"use strict";var e=require("@csstools/css-tokenizer");const r=/license|copyright|sourcemappingurl/i,t=/\s|\/\*/,s=/^layer$/i;function minify(r,s){if(!s)return s;if(r.has(s))return r.get(s);const o=s.trim();if(""===o)return r.set(s,""),"";if(!t.test(o))return r.set(s,o),o;let n=!1,i=!1,a="";const c=e.tokenizer({css:o});for(;!c.endOfFile();){const r=c.nextToken();e.isTokenWhitespace(r)?(n||(a+=" "),n=!0,i=!1):e.isTokenComment(r)?(i||(a+="/**/"),n=!1,i=!0):(n=!1,i=!1,a+=r[1])}return r.set(s,a),a}function removeEmptyNodes(e){if("rule"===e.type){if(0===e.nodes?.length){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}}else if("atrule"===e.type&&0===e.nodes?.length&&!s.test(e.name)){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}return!1}function setSemicolon(e){if(!e.raws.semicolon)return;const r=e.last;"decl"===r?.type&&r.variable||(e.raws.semicolon=!1)}const creator=()=>{const e=new Map;return{postcssPlugin:"postcss-minify",OnceExit(t){t.raws.before="",t.raws.after="\n",t.walk(t=>{switch(t.type){case"atrule":if(removeEmptyNodes(t))return;return t.raws.after="",t.raws.afterName=" ",t.raws.before="",t.raws.between="",t.raws.params=void 0,setSemicolon(t),void(t.params=minify(e,t.params));case"rule":if(removeEmptyNodes(t))return;return t.raws.after="",t.raws.before="",t.raws.between="",t.raws.selector=void 0,setSemicolon(t),void(t.selector=minify(e,t.selector));case"decl":return t.prop.startsWith("--")?void(t.raws.before=""):(t.raws.before="",t.raws.between=":",t.raws.important=t.important?"!important":"",t.raws.value=void 0,void(t.value=minify(e,t.value)));case"comment":return t.text.startsWith("!")||r.test(t.text)?void(t.raws.before=""):void t.remove()}})}}};creator.postcss=!0,module.exports=creator;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
import{tokenizer as e,isTokenWhiteSpaceOrComment as r}from"@csstools/css-tokenizer";const t=/license|copyright|sourcemappingurl/i,s=/\s|\/\*/,o=/^layer$/i;function minify(t,o){if(!o)return o;if(t.has(o))return t.get(o);const n=o.trim();if(""===n)return t.set(o,""),"";if(!s.test(n))return t.set(o,n),n;let a=!1,i="";const c=e({css:n});for(;!c.endOfFile();){const e=c.nextToken();r(e)?(a||(i+=" "),a=!0):(a=!1,i+=e[1])}return t.set(o,i),i}function removeEmptyNodes(e){if("rule"===e.type){if(0===e.nodes?.length){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}}else if("atrule"===e.type&&0===e.nodes?.length&&!o.test(e.name)){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}return!1}function setSemicolon(e){if(!e.raws.semicolon)return;const r=e.last;"decl"===r?.type&&r.variable||(e.raws.semicolon=!1)}const creator=()=>{const e=new Map;return{postcssPlugin:"postcss-minify",OnceExit(r){r.raws.before="",r.raws.after="\n",r.walk(r=>{switch(r.type){case"atrule":if(removeEmptyNodes(r))return;return r.raws.after="",r.raws.afterName=" ",r.raws.before="",r.raws.between="",r.raws.params=void 0,setSemicolon(r),void(r.params=minify(e,r.params));case"rule":if(removeEmptyNodes(r))return;return r.raws.after="",r.raws.before="",r.raws.between="",r.raws.selector=void 0,setSemicolon(r),void(r.selector=minify(e,r.selector));case"decl":return r.prop.startsWith("--")?void(r.raws.before=""):(r.raws.before="",r.raws.between=":",r.raws.important=r.important?"!important":"",r.raws.value=void 0,void(r.value=minify(e,r.value)));case"comment":return r.text.startsWith("!")||t.test(r.text)?void(r.raws.before=""):void r.remove()}})}}};creator.postcss=!0;export{creator as default};
1+
import{tokenizer as e,isTokenWhitespace as r,isTokenComment as t}from"@csstools/css-tokenizer";const s=/license|copyright|sourcemappingurl/i,o=/\s|\/\*/,n=/^layer$/i;function minify(s,n){if(!n)return n;if(s.has(n))return s.get(n);const a=n.trim();if(""===a)return s.set(n,""),"";if(!o.test(a))return s.set(n,a),a;let i=!1,c=!1,m="";const f=e({css:a});for(;!f.endOfFile();){const e=f.nextToken();r(e)?(i||(m+=" "),i=!0,c=!1):t(e)?(c||(m+="/**/"),i=!1,c=!0):(i=!1,c=!1,m+=e[1])}return s.set(n,m),m}function removeEmptyNodes(e){if("rule"===e.type){if(0===e.nodes?.length){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}}else if("atrule"===e.type&&0===e.nodes?.length&&!n.test(e.name)){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}return!1}function setSemicolon(e){if(!e.raws.semicolon)return;const r=e.last;"decl"===r?.type&&r.variable||(e.raws.semicolon=!1)}const creator=()=>{const e=new Map;return{postcssPlugin:"postcss-minify",OnceExit(r){r.raws.before="",r.raws.after="\n",r.walk(r=>{switch(r.type){case"atrule":if(removeEmptyNodes(r))return;return r.raws.after="",r.raws.afterName=" ",r.raws.before="",r.raws.between="",r.raws.params=void 0,setSemicolon(r),void(r.params=minify(e,r.params));case"rule":if(removeEmptyNodes(r))return;return r.raws.after="",r.raws.before="",r.raws.between="",r.raws.selector=void 0,setSemicolon(r),void(r.selector=minify(e,r.selector));case"decl":return r.prop.startsWith("--")?void(r.raws.before=""):(r.raws.before="",r.raws.between=":",r.raws.important=r.important?"!important":"",r.raws.value=void 0,void(r.value=minify(e,r.value)));case"comment":return r.text.startsWith("!")||s.test(r.text)?void(r.raws.before=""):void r.remove()}})}}};creator.postcss=!0;export{creator as default};

plugins/postcss-minify/src/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { AtRule, Container, Document, PluginCreator, Rule } from 'postcss';
2-
import { isTokenWhiteSpaceOrComment, tokenizer } from '@csstools/css-tokenizer';
2+
import { isTokenComment, isTokenWhitespace, tokenizer } from '@csstools/css-tokenizer';
33

44
const HAS_LEGAL_KEYWORDS_OR_SOURCE_MAP_REGEX = /license|copyright|sourcemappingurl/i;
55
const HAS_WHITESPACE_OR_COMMENTS_REGEX = /\s|\/\*/;
@@ -27,21 +27,31 @@ function minify(cache: Map<string, string>, x: string): string {
2727
}
2828

2929
let lastWasWhitespace = false;
30+
let lastWasComment = false;
3031
let minified = '';
3132

3233
const t = tokenizer({ css: y });
3334

3435
while (!t.endOfFile()) {
3536
const token = t.nextToken();
3637

37-
if (isTokenWhiteSpaceOrComment(token)) {
38+
if (isTokenWhitespace(token)) {
3839
if (!lastWasWhitespace) {
3940
minified = minified + ' ';
4041
}
4142

4243
lastWasWhitespace = true;
44+
lastWasComment = false;
45+
} else if (isTokenComment(token)) {
46+
if (!lastWasComment) {
47+
minified = minified + '/**/';
48+
}
49+
50+
lastWasWhitespace = false;
51+
lastWasComment = true;
4352
} else {
4453
lastWasWhitespace = false;
54+
lastWasComment = false;
4555
minified = minified + token[1];
4656
}
4757
}

plugins/postcss-minify/test/basic.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,11 @@
4141
color: red;
4242
}
4343
}
44+
45+
div/*foo*/.foo {
46+
color:red;
47+
}
48+
49+
div/*foo*//*bar*/.foo {
50+
color:purple;
51+
}

plugins/postcss-minify/test/basic.expect.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)