* Adds `process` method for compatibility with optimize-css-assets-webpack-plugin.
* Fixed issue [#861](https://github.com/jakubpawlowicz/clean-css/issues/861) - new `transition` property optimizer.
+* Fixed issue [#895](https://github.com/jakubpawlowicz/clean-css/issues/895) - ignoring specific styles.
[4.1.4 / 2017-06-14](https://github.com/jakubpawlowicz/clean-css/compare/v4.1.3...v4.1.4)
==================
* [How to process remote `@import`s correctly?](#how-to-process-remote-imports-correctly)
* [How to apply arbitrary transformations to CSS properties?](#how-to-apply-arbitrary-transformations-to-css-properties)
* [How to specify a custom rounding precision?](#how-to-specify-a-custom-rounding-precision)
+ * [How to keep a CSS fragment intact?](#how-to-keep-a-css-fragment-intact)
* [How to preserve a comment block?](#how-to-preserve-a-comment-block)
* [How to rebase relative image URLs?](#how-to-rebase-relative-image-urls)
* [How to work with source maps?](#how-to-work-with-source-maps)
* Adds `process` method for compatibility with optimize-css-assets-webpack-plugin;
* new `transition` property optimizer;
+* preserves any CSS content between `/* clean-css ignore:start */` and `/* clean-css ignore:end */` comments;
## Constructor options
which sets all units rounding precision to 3 digits except `px` unit precision of 5 digits.
+## How to keep a CSS fragment intact?
+
+Wrap the CSS fragment in special comments which instruct clean-css to preserve it, e.g.
+
+```css
+.block-1 {
+ color: red
+}
+/* clean-css ignore:start */
+.block-special {
+ color: transparent
+}
+/* clean-css ignore:end */
+.block-2 {
+ margin: 0
+}
+```
+
+Optimizing this CSS will result in the following output:
+
+```css
+.block-1{color:red}
+.block-special {
+ color: transparent
+}
+.block-2{margin:0}
+```
+
## How to preserve a comment block?
Use the `/*!` notation instead of the standard one `/*`:
PROPERTY_BLOCK: 'property-block', // e.g. `--var:{color:red}`
PROPERTY_NAME: 'property-name', // e.g. `color`
PROPERTY_VALUE: 'property-value', // e.g. `red`
+ RAW: 'raw', // e.g. anything between /* clean-css ignore:start */ and /* clean-css ignore:end */ comments
RULE: 'rule', // e.g `div > a{...}`
RULE_SCOPE: 'rule-scope' // e.g `div > a`
};
'@supports'
];
+var IGNORE_END_COMMENT_PATTERN = /\/\* clean\-css ignore:end \*\/$/;
+var IGNORE_START_COMMENT_PATTERN = /^\/\* clean\-css ignore:start \*\//;
var REPEAT_PATTERN = /^\[\s*\d+\s*\]$/;
var RULE_WORD_SEPARATOR_PATTERN = /[\s\(]/;
var TAIL_BROKEN_VALUE_PATTERN = /[\s|\}]*$/;
var buffer = [];
var buffers = [];
var serializedBuffer;
+ var serializedBufferPart;
var roundBracketLevel = 0;
var isQuoted;
var isSpace;
var wasCommentEnd = false;
var isEscaped;
var wasEscaped = false;
+ var isRaw = false;
var seekingValue = false;
var seekingPropertyBlockClosing = false;
var position = internalContext.position;
+ var lastCommentStartAt;
for (; position.index < source.length; position.index++) {
var character = source[position.index];
buffer.push(character);
} else if (!isCommentEnd && level == Level.COMMENT) {
buffer.push(character);
+ } else if (!isCommentStart && !isCommentEnd && isRaw) {
+ buffer.push(character);
} else if (isCommentStart && (level == Level.BLOCK || level == Level.RULE) && buffer.length > 1) {
// comment start within block preceded by some content, e.g. div/*<--
metadatas.push(metadata);
levels.push(level);
level = Level.COMMENT;
buffer.push(character);
+ } else if (isCommentEnd && isIgnoreStartComment(buffer)) {
+ // ignore:start comment end, e.g. /* clean-css ignore:start */<--
+ serializedBuffer = buffer.join('').trim() + character;
+ lastToken = [Token.COMMENT, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]];
+ newTokens.push(lastToken);
+
+ isRaw = true;
+ metadata = metadatas.pop() || null;
+ buffer = buffers.pop() || [];
+ } else if (isCommentEnd && isIgnoreEndComment(buffer)) {
+ // ignore:start comment end, e.g. /* clean-css ignore:end */<--
+ serializedBuffer = buffer.join('') + character;
+ lastCommentStartAt = serializedBuffer.lastIndexOf(Marker.FORWARD_SLASH + Marker.ASTERISK);
+
+ serializedBufferPart = serializedBuffer.substring(0, lastCommentStartAt);
+ lastToken = [Token.RAW, serializedBufferPart, [originalMetadata(metadata, serializedBufferPart, externalContext)]];
+ newTokens.push(lastToken);
+
+ serializedBufferPart = serializedBuffer.substring(lastCommentStartAt);
+ metadata = [position.line, position.column - serializedBufferPart.length + 1, position.source];
+ lastToken = [Token.COMMENT, serializedBufferPart, [originalMetadata(metadata, serializedBufferPart, externalContext)]];
+ newTokens.push(lastToken);
+
+ isRaw = false;
+ level = levels.pop();
+ metadata = metadatas.pop() || null;
+ buffer = buffers.pop() || [];
} else if (isCommentEnd) {
// comment end, e.g. /* comment */<--
serializedBuffer = buffer.join('').trim() + character;
return allTokens;
}
+function isIgnoreStartComment(buffer) {
+ return IGNORE_START_COMMENT_PATTERN.test(buffer.join('') + Marker.FORWARD_SLASH);
+}
+
+function isIgnoreEndComment(buffer) {
+ return IGNORE_END_COMMENT_PATTERN.test(buffer.join('') + Marker.FORWARD_SLASH);
+}
+
function originalMetadata(metadata, value, externalContext, selectorFallbacks) {
var source = metadata[2];
store(context, colon(context));
value(context, token);
store(context, needsSemicolon ? semicolon(context, Breaks.AfterProperty, isLast) : emptyCharacter);
+ break;
+ case Token.RAW:
+ store(context, token);
}
}
store(context, token);
store(context, allowsBreak(context, Breaks.AfterComment) ? lineBreak : emptyCharacter);
break;
+ case Token.RAW:
+ store(context, token);
+ break;
case Token.RULE:
rules(context, token[1]);
store(context, openBrace(context, Breaks.AfterRuleBegins, true));
'two comments, general selector right after first, and quotes': [
'/*! comment */*{box-sizing:border-box}div:before{content:" "}/*! @comment */div{display:inline-block}',
'/*! comment */*{box-sizing:border-box}div:before{content:" "}/*! @comment */div{display:inline-block}'
+ ],
+ 'clean-css ignore comments on top level': [
+ '/* clean-css ignore:start */\n .block { color:transparent } \n/* clean-css ignore:end */',
+ '\n .block { color:transparent } \n'
+ ],
+ 'clean-css ignore comments on nested block level': [
+ '@media print { /* clean-css ignore:start */\n .block { color:transparent } \n/* clean-css ignore:end */ }',
+ '@media print{\n .block { color:transparent } \n}'
+ ],
+ 'clean-css ignore comments on rule level': [
+ '.block { /* clean-css ignore:start */ *!color:transparent /* clean-css ignore:end */ }',
+ '.block{ *!color:transparent }'
+ ],
+ 'clean-css ignore comments with nested block': [
+ '/* clean-css ignore:start */ @media print { a { *!color:transparent } } /* clean-css ignore:end */',
+ ' @media print { a { *!color:transparent } } '
]
})
)
]
]
],
+ 'rule wrapped between ignore comments': [
+ '.block-1 { color: red }\n/* clean-css ignore:start */\n .block-2 { color: transparent } \n/* clean-css ignore:end */\n.block-3 { color: red }',
+ [
+ [
+ 'rule',
+ [
+ [
+ 'rule-scope',
+ '.block-1',
+ [
+ [1, 0, undefined]
+ ]
+ ]
+ ],
+ [
+ [
+ 'property',
+ [
+ 'property-name',
+ 'color',
+ [
+ [1, 11, undefined]
+ ]
+ ],
+ [
+ 'property-value',
+ 'red',
+ [
+ [1, 18, undefined]
+ ]
+ ]
+ ]
+ ]
+ ],
+ [
+ 'comment',
+ '/* clean-css ignore:start */',
+ [
+ [2, 0, undefined]
+ ]
+ ],
+ [
+ 'raw',
+ '\n .block-2 { color: transparent } \n',
+ [
+ [2, 28, undefined]
+ ]
+ ],
+ [
+ 'comment',
+ '/* clean-css ignore:end */',
+ [
+ [4, 0, undefined]
+ ]
+ ],
+ [
+ 'rule',
+ [
+ [
+ 'rule-scope',
+ '.block-3',
+ [
+ [5, 0, undefined]
+ ]
+ ]
+ ],
+ [
+ [
+ 'property',
+ [
+ 'property-name',
+ 'color',
+ [
+ [5, 11, undefined]
+ ]
+ ],
+ [
+ 'property-value',
+ 'red',
+ [
+ [5, 18, undefined]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ ],
'two properties wrapped between comments': [
'div{/* comment 1 */color:red/* comment 2 */}',
[