Comparing version 2.1.1 to 2.2.0
@@ -0,1 +1,10 @@ | ||
## 2.2.0 (June 23, 2016) | ||
- Implement AST cloning by adding `clone()` [function](https://github.com/css/csso#cloneast) and `clone` [option](https://github.com/css/csso#compressast-options) for `compress()` function (#296) | ||
- Fix parse and translate attribute selector with flags but w/o operator (i.e. `[attrName i]`) | ||
- Don't merge rules with flagged attribute selectors with others (#291) | ||
- Take in account functions when merge TRBL-properties (#297, thanks to @ArturAralin) | ||
- Improve partial merge (#304) | ||
- Tweak scanner, reduce code deoptimizations and other small improvements | ||
## 2.1.1 (May 11, 2016) | ||
@@ -2,0 +11,0 @@ |
var List = require('../utils/list'); | ||
var clone = require('../utils/clone'); | ||
var usageUtils = require('./usage'); | ||
@@ -8,3 +9,3 @@ var clean = require('./clean'); | ||
function readBlock(stylesheet, specialComments) { | ||
function readRulesChunk(rules, specialComments) { | ||
var buffer = new List(); | ||
@@ -14,3 +15,3 @@ var nonSpaceTokenInBuffer = false; | ||
stylesheet.rules.nextUntil(stylesheet.rules.head, function(node, item, list) { | ||
rules.nextUntil(rules.head, function(node, item, list) { | ||
if (node.type === 'Comment') { | ||
@@ -42,2 +43,3 @@ if (!specialComments || node.value.charAt(0) !== '!') { | ||
type: 'StyleSheet', | ||
info: null, | ||
rules: buffer | ||
@@ -48,9 +50,9 @@ } | ||
function compressBlock(ast, usageData, num, logger) { | ||
function compressChunk(ast, firstAtrulesAllowed, usageData, num, logger) { | ||
logger('Compress block #' + num, null, true); | ||
var seed = 1; | ||
ast.firstAtrulesAllowed = ast.firstAtrulesAllowed; | ||
walkRules(ast, function() { | ||
if (!this.stylesheet.id) { | ||
walkRules(ast, function markStylesheets() { | ||
if ('id' in this.stylesheet === false) { | ||
this.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed; | ||
this.stylesheet.id = seed++; | ||
@@ -90,5 +92,22 @@ } | ||
function wrapBlock(block) { | ||
return new List([{ | ||
type: 'Ruleset', | ||
selector: { | ||
type: 'Selector', | ||
selectors: new List([{ | ||
type: 'SimpleSelector', | ||
sequence: new List([{ | ||
type: 'Identifier', | ||
name: 'x' | ||
}]) | ||
}]) | ||
}, | ||
block: block | ||
}]); | ||
} | ||
module.exports = function compress(ast, options) { | ||
ast = ast || { type: 'StyleSheet', info: null, rules: new List() }; | ||
options = options || {}; | ||
ast = ast || { type: 'StyleSheet', rules: new List() }; | ||
@@ -98,32 +117,21 @@ var logger = typeof options.logger === 'function' ? options.logger : Function(); | ||
var restructuring = getRestructureOption(options); | ||
var result = new List(); | ||
var block; | ||
var firstAtrulesAllowed = true; | ||
var blockNum = 1; | ||
var blockRules; | ||
var blockMode = false; | ||
var usageData = false; | ||
var info = ast.info || null; | ||
var inputRules; | ||
var outputRules = new List(); | ||
var chunk; | ||
var chunkNum = 1; | ||
var chunkRules; | ||
if (ast.type !== 'StyleSheet') { | ||
blockMode = true; | ||
ast = { | ||
type: 'StyleSheet', | ||
rules: new List([{ | ||
type: 'Ruleset', | ||
selector: { | ||
type: 'Selector', | ||
selectors: new List([{ | ||
type: 'SimpleSelector', | ||
sequence: new List([{ | ||
type: 'Identifier', | ||
name: 'x' | ||
}]) | ||
}]) | ||
}, | ||
block: ast | ||
}]) | ||
}; | ||
if (options.clone) { | ||
ast = clone(ast); | ||
} | ||
if (ast.type === 'StyleSheet') { | ||
inputRules = ast.rules; | ||
ast.rules = outputRules; | ||
} else { | ||
inputRules = wrapBlock(ast); | ||
} | ||
if (options.usage) { | ||
@@ -134,17 +142,17 @@ usageData = usageUtils.buildIndex(options.usage); | ||
do { | ||
block = readBlock(ast, Boolean(specialComments)); | ||
block.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed; | ||
block.stylesheet = compressBlock(block.stylesheet, usageData, blockNum++, logger); | ||
chunk = readRulesChunk(inputRules, Boolean(specialComments)); | ||
compressChunk(chunk.stylesheet, firstAtrulesAllowed, usageData, chunkNum++, logger); | ||
// structure optimisations | ||
if (restructuring) { | ||
restructureBlock(block.stylesheet, usageData, logger); | ||
restructureBlock(chunk.stylesheet, usageData, logger); | ||
} | ||
blockRules = block.stylesheet.rules; | ||
chunkRules = chunk.stylesheet.rules; | ||
if (block.comment) { | ||
// add \n before comment if there is another content in result | ||
if (!result.isEmpty()) { | ||
result.insert(List.createItem({ | ||
if (chunk.comment) { | ||
// add \n before comment if there is another content in outputRules | ||
if (!outputRules.isEmpty()) { | ||
outputRules.insert(List.createItem({ | ||
type: 'Raw', | ||
@@ -155,7 +163,7 @@ value: '\n' | ||
result.insert(List.createItem(block.comment)); | ||
outputRules.insert(List.createItem(chunk.comment)); | ||
// add \n after comment if block is not empty | ||
if (!blockRules.isEmpty()) { | ||
result.insert(List.createItem({ | ||
// add \n after comment if chunk is not empty | ||
if (!chunkRules.isEmpty()) { | ||
outputRules.insert(List.createItem({ | ||
type: 'Raw', | ||
@@ -167,4 +175,4 @@ value: '\n' | ||
if (firstAtrulesAllowed && !blockRules.isEmpty()) { | ||
var lastRule = blockRules.last(); | ||
if (firstAtrulesAllowed && !chunkRules.isEmpty()) { | ||
var lastRule = chunkRules.last(); | ||
@@ -181,22 +189,8 @@ if (lastRule.type !== 'Atrule' || | ||
result.appendList(blockRules); | ||
} while (!ast.rules.isEmpty()); | ||
outputRules.appendList(chunkRules); | ||
} while (!inputRules.isEmpty()); | ||
if (blockMode) { | ||
result = !result.isEmpty() ? result.first().block : { | ||
type: 'Block', | ||
info: info, | ||
declarations: new List() | ||
}; | ||
} else { | ||
result = { | ||
type: 'StyleSheet', | ||
info: info, | ||
rules: result | ||
}; | ||
} | ||
return { | ||
ast: result | ||
ast: ast | ||
}; | ||
}; |
@@ -121,2 +121,6 @@ var List = require('../../utils/list.js'); | ||
case 'Function': | ||
special = child.name; | ||
break; | ||
case 'Space': | ||
@@ -123,0 +127,0 @@ return false; // ignore space |
@@ -28,6 +28,2 @@ var List = require('../../utils/list.js'); | ||
function inList(selector) { | ||
return selector.compareMarker in this; | ||
} | ||
function processRuleset(node, item, list) { | ||
@@ -37,3 +33,5 @@ var avoidRulesMerge = this.stylesheet.avoidRulesMerge; | ||
var block = node.block; | ||
var skippedCompareMarkers = Object.create(null); | ||
var disallowDownMarkers = Object.create(null); | ||
var allowMergeUp = true; | ||
var allowMergeDown = true; | ||
@@ -53,4 +51,8 @@ list.prevUntil(item.prev, function(prev, prevItem) { | ||
allowMergeDown = !prevSelectors.some(function(selector) { | ||
return selector.compareMarker in disallowDownMarkers; | ||
}); | ||
// try prev ruleset if simpleselectors has no equal specifity and element selector | ||
if (prevSelectors.some(inList, skippedCompareMarkers)) { | ||
if (!allowMergeDown && !allowMergeUp) { | ||
return true; | ||
@@ -60,3 +62,3 @@ } | ||
// try to join by selectors | ||
if (utils.isEqualLists(prevSelectors, selectors)) { | ||
if (allowMergeUp && utils.isEqualLists(prevSelectors, selectors)) { | ||
prevBlock.declarations.appendList(block.declarations); | ||
@@ -75,4 +77,7 @@ list.remove(item); | ||
// equal blocks | ||
utils.addSelectors(selectors, prevSelectors); | ||
list.remove(prevItem); | ||
if (allowMergeDown) { | ||
utils.addSelectors(selectors, prevSelectors); | ||
list.remove(prevItem); | ||
} | ||
return true; | ||
@@ -87,3 +92,3 @@ } else if (!avoidRulesMerge) { /* probably we don't need to prevent those merges for @keyframes | ||
if (selectorLength < blockLength) { | ||
if (allowMergeUp && selectorLength < blockLength) { | ||
utils.addSelectors(prevSelectors, selectors); | ||
@@ -97,3 +102,3 @@ block.declarations = new List(diff.ne1); | ||
if (selectorLength < blockLength) { | ||
if (allowMergeDown && selectorLength < blockLength) { | ||
utils.addSelectors(selectors, prevSelectors); | ||
@@ -115,3 +120,3 @@ prevBlock.declarations = new List(diff.ne2); | ||
// ruleset description overhead | ||
if (blockLength >= newBlockLength) { | ||
if (allowMergeDown && blockLength >= newBlockLength) { | ||
var newRuleset = { | ||
@@ -138,4 +143,14 @@ type: 'Ruleset', | ||
if (allowMergeUp) { | ||
// TODO: disallow up merge only if any property interception only (i.e. diff.ne2overrided.length > 0); | ||
// await property families to find property interception correctly | ||
allowMergeUp = !prevSelectors.some(function(prevSelector) { | ||
return selectors.some(function(selector) { | ||
return selector.compareMarker === prevSelector.compareMarker; | ||
}); | ||
}); | ||
} | ||
prevSelectors.each(function(data) { | ||
skippedCompareMarkers[data.compareMarker] = true; | ||
disallowDownMarkers[data.compareMarker] = true; | ||
}); | ||
@@ -142,0 +157,0 @@ }); |
@@ -71,2 +71,9 @@ var translate = require('../../../utils/translate.js'); | ||
case 'Attribute': | ||
if (node.flags) { | ||
pseudos['[' + node.flags + ']'] = true; | ||
hasPseudo = true; | ||
} | ||
break; | ||
case 'Combinator': | ||
@@ -73,0 +80,0 @@ tagName = '*'; |
@@ -6,2 +6,3 @@ var parse = require('./parser'); | ||
var walkers = require('./utils/walk'); | ||
var clone = require('./utils/clone'); | ||
var List = require('./utils/list'); | ||
@@ -128,3 +129,6 @@ | ||
walkRules: walkers.rules, | ||
walkRulesRight: walkers.rulesRight | ||
walkRulesRight: walkers.rulesRight, | ||
// utils | ||
clone: clone | ||
}; |
@@ -766,14 +766,17 @@ 'use strict'; | ||
if (scanner.token !== null && scanner.token.type !== TokenType.RightSquareBracket) { | ||
node.operator = readAttrselector(); | ||
// avoid case `[name i]` | ||
if (scanner.token.type !== TokenType.Identifier) { | ||
node.operator = readAttrselector(); | ||
readSC(); | ||
readSC(); | ||
if (scanner.token !== null && scanner.token.type === TokenType.String) { | ||
node.value = getString(); | ||
} else { | ||
node.value = getIdentifier(false); | ||
if (scanner.token !== null && scanner.token.type === TokenType.String) { | ||
node.value = getString(); | ||
} else { | ||
node.value = getIdentifier(false); | ||
} | ||
readSC(); | ||
} | ||
readSC(); | ||
// attribute flags | ||
@@ -780,0 +783,0 @@ if (scanner.token !== null && scanner.token.type === TokenType.Identifier) { |
@@ -23,4 +23,3 @@ 'use strict'; | ||
var DIGIT = 3; | ||
var STRING_SQ = 4; | ||
var STRING_DQ = 5; | ||
var STRING = 4; | ||
@@ -86,4 +85,4 @@ var PUNCTUATION = { | ||
SYMBOL_CATEGORY[QUOTE] = STRING_SQ; | ||
SYMBOL_CATEGORY[DOUBLE_QUOTE] = STRING_DQ; | ||
SYMBOL_CATEGORY[QUOTE] = STRING; | ||
SYMBOL_CATEGORY[DOUBLE_QUOTE] = STRING; | ||
@@ -129,13 +128,14 @@ // | ||
next: function() { | ||
this.prevToken = this.token; | ||
var newToken = null; | ||
if (this.buffer.length !== 0) { | ||
this.token = this.buffer.shift(); | ||
newToken = this.buffer.shift(); | ||
} else if (!this.eof) { | ||
this.token = this.getToken(); | ||
} else { | ||
this.token = null; | ||
newToken = this.getToken(); | ||
} | ||
return this.token; | ||
this.prevToken = this.token; | ||
this.token = newToken; | ||
return newToken; | ||
}, | ||
@@ -168,4 +168,3 @@ | ||
case STRING_SQ: | ||
case STRING_DQ: | ||
case STRING: | ||
type = TokenType.String; | ||
@@ -182,3 +181,3 @@ value = this.readString(code); | ||
if (code === SLASH) { | ||
next = this.source.charCodeAt(this.pos + 1); | ||
next = this.pos + 1 < this.source.length ? this.source.charCodeAt(this.pos + 1) : 0; | ||
@@ -382,2 +381,6 @@ if (next === STAR) { // /* | ||
// warm up tokenizer to elimitate code branches that never execute | ||
// fix soft deoptimizations (insufficient type feedback) | ||
new Scanner('\n\r\r\n\f//""\'\'/**/1a;.{url(a)}').lookup(1e3); | ||
module.exports = Scanner; |
@@ -79,2 +79,3 @@ function each(list) { | ||
var result = translate(node.name); | ||
var flagsPrefix = ' '; | ||
@@ -87,4 +88,5 @@ if (node.operator !== null) { | ||
if (node.flags !== null) { | ||
result += (node.value.type !== 'String' ? ' ' : '') + node.flags; | ||
// space between string and flags is not required | ||
if (node.value.type === 'String') { | ||
flagsPrefix = ''; | ||
} | ||
@@ -94,2 +96,6 @@ } | ||
if (node.flags !== null) { | ||
result += flagsPrefix + node.flags; | ||
} | ||
return '[' + result + ']'; | ||
@@ -96,0 +102,0 @@ |
@@ -188,2 +188,3 @@ var SourceMapGenerator = require('source-map').SourceMapGenerator; | ||
var result = translate(node.name); | ||
var flagsPrefix = ' '; | ||
@@ -196,4 +197,5 @@ if (node.operator !== null) { | ||
if (node.flags !== null) { | ||
result += (node.value.type !== 'String' ? ' ' : '') + node.flags; | ||
// space between string and flags is not required | ||
if (node.value.type === 'String') { | ||
flagsPrefix = ''; | ||
} | ||
@@ -203,2 +205,6 @@ } | ||
if (node.flags !== null) { | ||
result += flagsPrefix + node.flags; | ||
} | ||
return '[' + result + ']'; | ||
@@ -205,0 +211,0 @@ |
{ | ||
"name": "csso", | ||
"version": "2.1.1", | ||
"version": "2.2.0", | ||
"description": "CSSO (CSS Optimizer) is a CSS minifier with structural optimisations", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -289,7 +289,10 @@ [![NPM version](https://img.shields.io/npm/v/csso.svg)](https://www.npmjs.com/package/csso) | ||
Do the main task – compress AST. | ||
Does the main task – compress AST. | ||
> NOTE: `compress` performs AST compression by transforming input AST by default (since AST cloning is expensive and needed in rare cases). Use `clone` option with truthy value in case you want to keep input AST untouched. | ||
Options: | ||
- restructure `Boolean` – do the structure optimisations or not (`true` by default) | ||
- clone `Boolean` - transform a copy of input AST if `true`, useful in case of AST reuse (`false` by default) | ||
- comments `String` or `Boolean` – specify what comments to left | ||
@@ -302,2 +305,22 @@ - `'exclamation'` or `true` (default) – left all exclamation comments (i.e. `/*! .. */`) | ||
#### clone(ast) | ||
Make an AST node deep copy. | ||
```js | ||
var orig = csso.parse('.test { color: red }'); | ||
var copy = csso.clone(orig); | ||
csso.walk(copy, function(node) { | ||
if (node.type === 'Class') { | ||
node.name = 'replaced'; | ||
} | ||
}); | ||
console.log(csso.translate(orig)); | ||
// .test{color:red} | ||
console.log(csso.translate(copy)); | ||
// .replaced{color:red} | ||
``` | ||
#### translate(ast) | ||
@@ -304,0 +327,0 @@ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
300853
52
5608
406
1
4
2