Socket
Socket
Sign inDemoInstall

csso

Package Overview
Dependencies
Maintainers
3
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

csso - npm Package Compare versions

Comparing version 1.4.4 to 1.5.0

lib/compressor/ast/gonzalesToInternal.js

31

HISTORY.md

@@ -0,1 +1,32 @@

## 1.5.0 (January 14, 2016)
### Parser
- attach minus to number
### Compressor
- split code base into small modules and related refactoring
- introduce internal AST format for compressor (`gonzales`→`internal` and `internal`→`gonzales` convertors, walkers, translator)
- various optimizations: no snapshots, using caches and indexes
- sort selectors, merge selectors in alphabet order
- compute selector's specificity
- better ruleset restructuring, improve compression of partially equal blocks
- better ruleset merge – not only closest but also disjoined by other rulesets when safe
- join `@media` with same query
- `outputAst` – new option to specify output AST format (`gonzales` by default for backward compatibility)
- remove quotes surrounding attribute values in attribute selectors when possible (issue #73)
- replace `from`→`0%` and `100%`→`to` at `@keyframes` (#205)
- prevent partial merge of rulesets at `@keyframes` (#80, #197)
- fix issue with wrong space removals (#258)
### API
- walker for `gonzales` AST was implemented
### CLI
- new option `--stat` (output stat in `stderr`)
- new optional parameter `level` for `--debug` option
## 1.4.4 (December 10, 2015)

@@ -2,0 +33,0 @@

36

lib/cli.js

@@ -18,2 +18,20 @@ var fs = require('fs');

function stat(filename, source, result, time, mem) {
function fmt(size) {
return String(size).replace(/\B\d{3}$/, ' $&');
}
console.error('File: ', filename);
console.error('Original: ', fmt(source), 'bytes');
console.error('Compressed:', fmt(result), 'bytes', '(' + (100 * result / source).toFixed(2) + '%)');
console.error('Saving: ', fmt(source - result), 'bytes', '(' + (100 * (source - result) / source).toFixed(2) + '%)');
console.error('Time: ', time, 'ms');
console.error('Memory: ', (mem / (1024 * 1024)).toFixed(3), 'MB');
}
function debugLevel(level) {
// level is undefined when no param -> 1
return isNaN(level) ? 1 : Math.max(Number(level), 0);
}
var command = cli.create('csso', '[input] [output]')

@@ -24,3 +42,4 @@ .version(require('../package.json').version)

.option('--restructure-off', 'Turns structure minimization off')
.option('--debug', 'Output intermediate state of CSS during compression')
.option('--stat', 'Output statistics instead of result')
.option('--debug [level]', 'Output intermediate state of CSS during compression', debugLevel, 0)
.action(function(args) {

@@ -38,4 +57,8 @@ var inputFile = this.values.input || args[0];

var input = inputFile ? fs.createReadStream(inputFile) : process.stdin;
var statistics = this.values.stat;
readFromStream(input, function(source) {
var time = process.hrtime();
var mem = process.memoryUsage().heapUsed;
var result = csso.minify(source, {

@@ -46,2 +69,13 @@ restructuring: !structureOptimisationOff,

if (statistics) {
var timeDiff = process.hrtime(time);
stat(
inputFile || '<stdin>',
source.length,
result.length,
parseInt(timeDiff[0] * 1e3 + timeDiff[1] / 1e6),
process.memoryUsage().heapUsed - mem
);
}
if (outputFile) {

@@ -48,0 +82,0 @@ fs.writeFileSync(outputFile, result, 'utf-8');

1356

lib/compressor/index.js

@@ -1,12 +0,37 @@

var translate = require('../utils/translate');
var constants = require('./const');
var rules = require('./rules');
var TRBL = require('./trbl');
var color = require('./color');
var packNumber = require('./utils').packNumber;
var convertToInternal = require('./ast/gonzalesToInternal.js');
var convertToGonzales = require('./ast/internalToGonzales.js');
var internalTranslate = require('./ast/translate.js');
var internalWalkAll = require('./ast/walk.js').all;
var cleanFn = require('./clean');
var compressFn = require('./compress');
var restructureAst = require('./restructure');
function CSSOCompressor() {
}
function createLogger(level) {
var lastDebug;
CSSOCompressor.prototype.injectInfo = function(token) {
if (!level) {
// no output
return function() {};
}
return function debugOutput(name, token, reset) {
var line = (!reset ? '(' + ((Date.now() - lastDebug) / 1000).toFixed(3) + 'ms) ' : '') + name;
if (level > 1 && token) {
var css = internalTranslate(token, true).trim();
// when level 2, limit css to 256 symbols
if (level === 2 && css.length > 256) {
css = css.substr(0, 256) + '...';
}
line += '\n ' + css + '\n';
}
console.error(line);
lastDebug = Date.now();
};
};
function injectInfo(token) {
for (var i = token.length - 1; i > -1; i--) {

@@ -16,7 +41,7 @@ var child = token[i];

if (Array.isArray(child)) {
this.injectInfo(child);
injectInfo(child);
child.unshift({});
}
}
};
}

@@ -55,1297 +80,92 @@ function readBlock(stylesheet, offset) {

CSSOCompressor.prototype.process = function(rules, token, container, idx, path, stack) {
var type = token[1];
var rule = rules[type];
var result;
function compressBlock(ast, restructuring, num, debug) {
function walk(name, fn) {
internalWalkAll(internalAst, fn);
if (rule) {
result = token;
for (var i = 0; i < rule.length; i++) {
var tmp = this[rule[i]](result, type, container, idx, path, stack);
if (tmp === null) {
return null;
}
if (tmp !== undefined) {
result = tmp;
}
}
debug(name, internalAst);
}
return result;
};
debug('Compress block #' + num, null, true);
CSSOCompressor.prototype.walk = function(rules, token, path, name, stack) {
if (!stack) {
stack = [token];
}
var internalAst = convertToInternal(ast);
debug('convertToInternal', internalAst);
for (var i = token.length - 1; i >= 2; i--) {
var child = token[i];
internalAst.firstAtrulesAllowed = ast.firstAtrulesAllowed;
walk('clean', cleanFn);
walk('compress', compressFn);
if (Array.isArray(child)) {
stack.push(child);
child = this.walk(rules, child, path + '/' + i, null, stack); // go inside
stack.pop();
if (child === null) {
token.splice(i, 1);
} else {
child = this.process(rules, child, token, i, path, stack);
if (child) {
// compressed not null
token[i] = child;
} else if (child === null) {
// null is the mark to delete token
token.splice(i, 1);
}
}
}
}
if (this.debug && name) {
console.log(name + '\n ' + translate(token, true).trim());
console.log('');
}
return token.length ? token : null;
};
function compressBlock(ast, restructuring) {
this.props = {};
this.shorts = {};
this.shorts2 = {};
this.shortGroupID = 0;
this.lastShortGroupID = 0;
this.lastShortSelector = 0;
// compression without restructure
ast = this.walk(rules.cleanComments, ast, '/0', 'cleanComments');
ast = this.walk(rules.compress, ast, '/0', 'compress');
ast = this.walk(rules.prepare, ast, '/0', 'prepare');
ast = this.walk(rules.freezeRuleset, ast, '/0', 'freezeRuleset');
// structure optimisations
if (restructuring) {
var initAst = this.copyArray(ast);
var initLength = translate(initAst, true).length;
ast = this.walk(rules.rejoinRuleset, ast, '/0', 'rejoinRuleset');
this.disjoin(ast);
ast = this.walk(rules.markShorthand, ast, '/0', 'markShorthand');
ast = this.walk(rules.cleanShortcut, ast, '/0', 'cleanShortcut');
ast = this.walk(rules.restructureBlock, ast, '/0', 'restructureBlock');
var curLength = Infinity;
var minLength;
var astSnapshot;
do {
minLength = curLength;
astSnapshot = this.copyArray(ast);
ast = this.walk(rules.rejoinRuleset, ast, '/0', 'rejoinRuleset');
ast = this.walk(rules.restructureRuleset, ast, '/0', 'restructureRuleset');
curLength = translate(ast, true).length;
} while (minLength > curLength);
if (initLength < minLength && initLength < curLength) {
ast = initAst;
} else if (minLength < curLength) {
ast = astSnapshot;
}
restructureAst(internalAst, debug);
}
ast = this.walk(rules.finalize, ast, '/0');
return ast;
return internalAst;
}
CSSOCompressor.prototype.compress = function(ast, options) {
this.debug = Boolean(options.debug);
module.exports = function compress(ast, options) {
ast = ast || [{}, 'stylesheet'];
options = options || {};
var debug = createLogger(options.debug);
var restructuring = options.restructuring || options.restructuring === undefined;
var result = [];
var block = { offset: 2 };
var firstAtrulesAllowed = true;
var blockNum = 1;
if (typeof ast[0] === 'string') {
this.injectInfo([ast]);
injectInfo([ast]);
}
var result = [{}, 'stylesheet'];
var block = { offset: 2 };
var restructuring = options.restructuring || options.restructuring === undefined;
var firstAtrulesAllowed = true;
do {
block = readBlock(ast, block.offset);
block.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed;
block.stylesheet = compressBlock.call(this, block.stylesheet, restructuring);
block.stylesheet = compressBlock(block.stylesheet, restructuring, blockNum++, debug);
if (block.comment) {
// add \n before comment if there is another content in result
if (result.length > 2) {
result.push([{}, 's', '\n']);
if (result.length) {
result.push({
type: 'Raw',
value: '\n'
});
}
result.push(block.comment);
result.push({
type: 'Comment',
value: block.comment[2]
});
// add \n after comment if block is not empty
if (block.stylesheet.length > 2) {
result.push([{}, 's', '\n']);
if (block.stylesheet.rules.length) {
result.push({
type: 'Raw',
value: '\n'
});
}
}
result.push.apply(result, block.stylesheet.slice(2));
result.push.apply(result, block.stylesheet.rules);
if (firstAtrulesAllowed && result.length > 2) {
firstAtrulesAllowed = this.cleanImport(
null, null, block.stylesheet, block.stylesheet.length
) !== null;
}
} while (block.offset < ast.length);
if (firstAtrulesAllowed && result.length) {
var lastRule = result[result.length - 1];
return result;
};
CSSOCompressor.prototype.disjoin = function(token) {
for (var i = token.length - 1; i >= 2; i--) {
var child = token[i];
if (!Array.isArray(child)) {
continue;
}
if (child[1] === 'ruleset') {
var selector = child[2];
child[0].shortGroupID = this.shortGroupID++;
// there are more than 1 simple selector split for rulesets
if (selector.length > 3) {
// generate new rule sets:
// .a, .b { color: red; }
// ->
// .a { color: red; }
// .b { color: red; }
for (var j = selector.length - 1; j >= 2; j--) {
var selectorInfo = this.copyObject(selector[0]);
var newRuleset = [
this.copyObject(child[0]),
'ruleset',
[selectorInfo, 'selector', selector[j]],
this.copyArray(child[3])
];
selectorInfo.s = selector[j][0].s;
token.splice(i + 1, 0, newRuleset);
}
// delete old ruleset
token.splice(i, 1);
if (lastRule.type !== 'Atrule' ||
(lastRule.name !== 'import' && lastRule.name !== 'charset')) {
firstAtrulesAllowed = false;
}
} else {
// try disjoin nested stylesheets, i.e. @media, @support etc.
this.disjoin(child);
}
}
} while (block.offset < ast.length);
if (this.debug) {
console.log('disjoin\n ' + translate(token, true).trim());
console.log('');
if (!options.outputAst || options.outputAst === 'gonzales') {
return convertToGonzales({
type: 'StyleSheet',
rules: result
});
}
};
CSSOCompressor.prototype.freezeRulesets = function(token) {
var info = token[0];
var selector = token[2];
info.freeze = this.freezeNeeded(selector);
info.freezeID = this.selectorSignature(selector);
info.pseudoID = this.composePseudoID(selector);
info.pseudoSignature = this.pseudoSelectorSignature(selector, constants.allowedPClasses, true);
this.markSimplePseudo(selector);
return token;
};
CSSOCompressor.prototype.markSimplePseudo = function(selector) {
var hash = {};
for (var i = 2; i < selector.length; i++) {
var simpleSelector = selector[i];
simpleSelector[0].pseudo = this.containsPseudo(simpleSelector);
simpleSelector[0].sg = hash;
hash[simpleSelector[0].s] = 1;
}
};
CSSOCompressor.prototype.composePseudoID = function(selector) {
var pseudos = [];
for (var i = 2; i < selector.length; i++) {
var simpleSelector = selector[i];
if (this.containsPseudo(simpleSelector)) {
pseudos.push(simpleSelector[0].s);
}
}
return pseudos.sort().join(',');
};
CSSOCompressor.prototype.containsPseudo = function(sselector) {
for (var j = 2; j < sselector.length; j++) {
switch (sselector[j][1]) {
case 'pseudoc':
case 'pseudoe':
case 'nthselector':
if (sselector[j][2][2] in constants.notFPClasses === false) {
return true;
}
}
}
};
CSSOCompressor.prototype.selectorSignature = function(selector) {
var parts = [];
for (var i = 2; i < selector.length; i++) {
parts.push(translate(selector[i], true));
}
return parts.sort().join(',');
};
CSSOCompressor.prototype.pseudoSelectorSignature = function(selector, exclude, dontAppendExcludeMark) {
var pseudos = {};
var wasExclude = false;
exclude = exclude || {};
for (var i = 2; i < selector.length; i++) {
var simpleSelector = selector[i];
for (var j = 2; j < simpleSelector.length; j++) {
switch (simpleSelector[j][1]) {
case 'pseudoc':
case 'pseudoe':
case 'nthselector':
if (!exclude.hasOwnProperty(simpleSelector[j][2][2])) {
pseudos[simpleSelector[j][2][2]] = 1;
} else {
wasExclude = true;
}
break;
}
}
}
return Object.keys(pseudos).sort().join(',') + (dontAppendExcludeMark ? '' : wasExclude);
};
CSSOCompressor.prototype.freezeNeeded = function(selector) {
for (var i = 2; i < selector.length; i++) {
var simpleSelector = selector[i];
for (var j = 2; j < simpleSelector.length; j++) {
switch (simpleSelector[j][1]) {
case 'pseudoc':
if (!(simpleSelector[j][2][2] in constants.notFPClasses)) {
return true;
}
break;
case 'pseudoe':
if (!(simpleSelector[j][2][2] in constants.notFPElements)) {
return true;
}
break;
case 'nthselector':
return true;
}
}
}
return false;
};
CSSOCompressor.prototype.cleanCharset = function(token, rule, parent, i) {
if (token[2][2][2] === 'charset') {
for (i = i - 1; i > 1; i--) {
if (parent[i][1] !== 's' && parent[i][1] !== 'comment') {
return null;
}
}
}
};
CSSOCompressor.prototype.cleanImport = function(token, rule, parent, i) {
if (!parent.firstAtrulesAllowed) {
return null;
}
for (i = i - 1; i > 1; i--) {
var type = parent[i][1];
if (type !== 's' && type !== 'comment') {
if (type === 'atrules') {
var atrule = parent[i][2][2][2];
if (atrule !== 'import' && atrule !== 'charset') {
return null;
}
} else {
return null;
}
}
}
};
CSSOCompressor.prototype.cleanComment = function(token, rule, parent, i) {
return null;
};
CSSOCompressor.prototype.cleanWhitespace = function(token, rule, parent, i) {
var parentType = parent[1];
var prevType = (parentType === 'braces' && i === 4) ||
(parentType !== 'braces' && i === 2) ? null : parent[i - 1][1];
var nextType = i === parent.length - 1 ? null : parent[i + 1][1];
if (nextType === 'unknown') {
token[2] = '\n';
} else {
if (parentType === 'simpleselector') {
if (!prevType || prevType === 'combinator' ||
!nextType || nextType === 'combinator') {
return null;
}
} else if ((parentType !== 'atrulerq' || prevType) &&
!this.issue16(prevType, nextType) &&
!this.issue165(parent, prevType, nextType) &&
!this.issue134(prevType, nextType) &&
!this.issue228(prevType, nextType)) {
if (nextType !== null && prevType !== null) {
if ((prevType === 'ident' && parent[i - 1][2] === '*') ||
(nextType === 'ident' && parent[i + 1][2] === '*')) {
return null;
}
if (this._cleanWhitespace(nextType, false) ||
this._cleanWhitespace(prevType, true)) {
return null;
}
} else {
return null;
}
}
token[2] = ' ';
}
return token;
};
// See https://github.com/afelix/csso/issues/16
CSSOCompressor.prototype.issue16 = function(prevType, nextType) {
return nextType && prevType === 'uri';
};
// See https://github.com/css/csso/issues/165
CSSOCompressor.prototype.issue165 = function(parent, prevType, nextType) {
return prevType === 'braces' && nextType === 'ident';
};
// See https://github.com/css/csso/issues/134
CSSOCompressor.prototype.issue134 = function(prevType, nextType) {
return prevType === 'funktion' && (nextType === 'funktion' || nextType === 'vhash');
};
CSSOCompressor.prototype.issue228 = function(prevType, nextType) {
return prevType === 'braces' && nextType === 'unary';
};
CSSOCompressor.prototype._cleanWhitespace = function(type, left) {
switch (type) {
case 's':
case 'operator':
case 'attrselector':
case 'block':
case 'decldelim':
case 'ruleset':
case 'declaration':
case 'atruleb':
case 'atrules':
case 'atruler':
case 'important':
case 'nth':
case 'combinator':
return true;
}
if (left) {
switch (type) {
case 'funktion':
case 'braces':
case 'uri':
return true;
}
}
};
CSSOCompressor.prototype.cleanDecldelim = function(token) {
for (var i = token.length - 1; i > 1; i--) {
var type = token[i][1];
var nextType = token[i + 1][1];
if (type === 'decldelim' && nextType !== 'declaration') {
token.splice(i, 1);
}
}
if (token[2][1] === 'decldelim') {
token.splice(2, 1);
}
return token;
};
CSSOCompressor.prototype.compressNumber = function(token) {
var value = packNumber(token[2]);
token[2] = value;
token[0].s = value;
return token;
};
CSSOCompressor.prototype.cleanUnary = function(token, rule, parent, i) {
var next = parent[i + 1];
if (next && next[1] === 'number' && next[2] === '0') {
return null;
}
return token;
};
CSSOCompressor.prototype.compressColor = function(token, rule, parent, i) {
switch (rule) {
case 'vhash':
return color.compressHex(token);
case 'funktion':
return color.compressFunction(token, rule, parent, i);
case 'ident':
return color.compressIdent(token, rule, parent, i);
}
};
CSSOCompressor.prototype.compressDimension = function(token, rule, parent, i, path, stack) {
var value = token[2][2];
var unit = token[3][2];
if (value === '0' && !constants.nonLengthUnits[unit]) {
// issue #200: don't remove units in flex property as it could change value meaning
if (parent[1] === 'value' && stack[stack.length - 2][2][2][2] === 'flex') {
return;
}
// issue #222: don't remove units inside calc
var i = stack.length - 1;
while (i > 0 && (stack[i][1] === 'braces' || stack[i][1] === 'functionBody')) {
i--;
if (stack[i][1] === 'funktion' && stack[i][2][2] === 'calc') {
return;
}
}
return token[2];
}
};
CSSOCompressor.prototype.compressString = function(token) {
// remove escaped \n, i.e.
// .a { content: "foo\
// bar"}
// ->
// .a { content: "foobar" }
token[2] = token[2].replace(/\\\n/g, '');
};
CSSOCompressor.prototype.compressFontWeight = function(token) {
var property = token[2];
var value = token[3];
if (/font-weight$/.test(property[2][2]) && value[2][1] === 'ident') {
switch (value[2][2]) {
case 'normal':
value[2] = [{}, 'number', '400'];
break;
case 'bold':
value[2] = [{}, 'number', '700'];
break;
}
}
};
CSSOCompressor.prototype.compressFont = function(token) {
var property = token[2];
var value = token[3];
if (/font$/.test(property[2][2]) && value.length) {
value.splice(2, 0, [{}, 's', '']);
for (var i = value.length - 1; i > 2; i--) {
if (value[i][1] === 'ident') {
var ident = value[i][2];
if (ident === 'bold') {
value[i] = [{}, 'number', '700'];
} else if (ident === 'normal') {
var t = value[i - 1];
if (t[1] === 'operator' && t[2] === '/') {
value.splice(--i, 2);
} else {
value.splice(i, 1);
}
if (value[i - 1][1] === 's') {
value.splice(--i, 1);
}
} else if (ident === 'medium' && value[i + 1] && value[i + 1][2] !== '/') {
value.splice(i, 1);
if (value[i - 1][1] === 's') {
value.splice(--i, 1);
}
}
}
}
if (value.length > 2 && value[2][1] === 's') {
value.splice(2, 1);
}
if (value.length === 2) {
value.push([{}, 'ident', 'normal']);
}
return token;
}
};
CSSOCompressor.prototype.compressBackground = function(token) {
function lastType() {
if (sequence.length) {
return sequence[sequence.length - 1][1];
}
}
function flush() {
if (lastType() === 's') {
sequence.pop();
}
if (!sequence.length ||
(sequence.length === 1 && sequence[0][1] === 'important')) {
value.push(
[{}, 'number', '0'],
[{}, 's', ' '],
[{}, 'number', '0']
);
}
value.push.apply(value, sequence);
sequence = [];
}
var property = token[2];
var value = token[3];
if (/background$/.test(property[2][2]) && value.length) {
var current = value.splice(2);
var sequence = [];
for (var i = 0; i < current.length; i++) {
var node = current[i];
var type = node[1];
var val = node[2];
// flush collected sequence
if (type === 'operator' && val === ',') {
flush();
value.push(node);
continue;
}
// remove defaults
if (type === 'ident') {
if (val === 'transparent' ||
val === 'none' ||
val === 'repeat' ||
val === 'scroll') {
continue;
}
}
// don't add redundant spaces
if (type === 's' && (!sequence.length || lastType() === 's')) {
continue;
}
sequence.push(node);
}
flush();
return token;
}
};
CSSOCompressor.prototype.cleanEmpty = function(token, rule) {
switch (rule) {
case 'ruleset':
if (!token[3] || token[3].length === 2) {
return null;
}
break;
case 'atruleb':
if (token[token.length - 1].length < 3) {
return null;
}
break;
case 'atruler':
if (token[4].length < 3) {
return null;
}
break;
}
};
CSSOCompressor.prototype.destroyDelims = function() {
return null;
};
CSSOCompressor.prototype.preTranslate = function(token) {
token[0].s = translate(token, true);
return token;
};
CSSOCompressor.prototype.markShorthands = function(token, rule, parent, j, path) {
var selector = '';
var freeze = false;
var freezeID = 'fake';
var shortGroupID = parent[0].shortGroupID;
var pre;
var sh;
if (parent[1] === 'ruleset') {
selector = parent[2][2][0].s,
freeze = parent[0].freeze,
freezeID = parent[0].freezeID;
}
pre = this.pathUp(path) + '/' + (freeze ? '&' + freezeID + '&' : '') + selector + '/';
for (var i = token.length - 1; i > -1; i--) {
var createNew = true;
var child = token[i];
if (child[1] === 'declaration') {
var childInfo = child[0];
var property = child[2][0].s;
var value = child[3];
var important = value[value.length - 1][1] === 'important';
childInfo.id = path + '/' + i;
if (property in TRBL.props) {
var key = pre + TRBL.extractMain(property);
var shorts = this.shorts2[key] || [];
if (!this.lastShortSelector ||
selector === this.lastShortSelector ||
shortGroupID === this.lastShortGroupID) {
if (shorts.length) {
sh = shorts[shorts.length - 1];
createNew = false;
}
}
if (createNew) {
sh = new TRBL(property, important);
shorts.push(sh);
childInfo.replaceByShort = true;
} else {
childInfo.removeByShort = true;
}
childInfo.shorthandKey = { key: key, i: shorts.length - 1 };
sh.add(property, value[0].s, value.slice(2), important);
this.shorts2[key] = shorts;
this.lastShortSelector = selector;
this.lastShortGroupID = shortGroupID;
}
}
}
return token;
};
CSSOCompressor.prototype.cleanShorthands = function(token) {
var info = token[0];
if (info.removeByShort || info.replaceByShort) {
var sKey = info.shorthandKey;
var shorthand = this.shorts2[sKey.key][sKey.i];
if (shorthand.isOkToMinimize()) {
if (info.replaceByShort) {
var shorterToken = [{}, 'declaration', shorthand.getProperty(), shorthand.getValue()];
shorterToken[0].s = translate(shorterToken, true);
return shorterToken;
} else {
return null;
}
}
}
};
CSSOCompressor.prototype.restructureBlock = function(token, rule, parent, j, path) {
var props = {};
var isPseudo = false;
var selector = '';
var freeze = false;
var freezeID = 'fake';
var pseudoID = 'fake';
var sg = {};
if (parent[1] === 'ruleset') {
var parentInfo = parent[0];
var parentSelectorInfo = parent[2][2][0];
props = this.props;
isPseudo = parentSelectorInfo.pseudo;
selector = parentSelectorInfo.s;
freeze = parentInfo.freeze;
freezeID = parentInfo.freezeID;
pseudoID = parentInfo.pseudoID;
sg = parentSelectorInfo.sg;
}
for (var i = token.length - 1; i > -1; i--) {
var child = token[i];
if (child[1] === 'declaration') {
var value = child[3];
var important = value[value.length - 1][1] === 'important';
var property = child[2][0].s;
var pre = this.pathUp(path) + '/' + selector + '/';
var ppre = this.buildPPre(pre, property, value, child, freeze);
var ppreProps = props[ppre];
var id = path + '/' + i;
child[0].id = id;
if (!constants.dontRestructure[property] && ppreProps) {
if ((isPseudo && freezeID === ppreProps.freezeID) || // pseudo from equal selectors group
(!isPseudo && pseudoID === ppreProps.pseudoID) || // not pseudo from equal pseudo signature group
(isPseudo && pseudoID === ppreProps.pseudoID && this.hashInHash(sg, ppreProps.sg))) { // pseudo from covered selectors group
if (important && !ppreProps.important) {
props[ppre] = {
block: token,
important: important,
id: id,
sg: sg,
freeze: freeze,
path: path,
freezeID: freezeID,
pseudoID: pseudoID
};
this.deleteProperty(ppreProps.block, ppreProps.id);
} else {
token.splice(i, 1);
}
}
} else if (this.needless(property, props, pre, important, value, child, freeze)) {
token.splice(i, 1);
} else {
props[ppre] = {
block: token,
important: important,
id: id,
sg: sg,
freeze: freeze,
path: path,
freezeID: freezeID,
pseudoID: pseudoID
};
}
}
}
return token;
};
CSSOCompressor.prototype.buildPPre = function(pre, property, value, d, freeze) {
var fp = freeze ? 'ft:' : 'ff:';
if (property.indexOf('background') !== -1) {
return fp + pre + d[0].s;
}
var vendorId = '';
var hack9 = 0;
var functions = {};
var units = {};
for (var i = 2; i < value.length; i++) {
if (!vendorId) {
vendorId = this.getVendorIDFromToken(value[i]);
}
switch (value[i][1]) {
case 'ident':
if (value[i][2] === '\\9') {
hack9 = 1;
}
break;
case 'funktion':
var name = value[i][2][2];
if (name === 'rect') {
// there are 2 forms of rect:
// rect(<top>, <right>, <bottom>, <left>) - standart
// rect(<top> <right> <bottom> <left>) – backwards compatible syntax
// only the same form values can be merged
if (value[i][3].slice(2).some(function(token) {
return token[1] === 'operator' && token[2] === ',';
})) {
name = 'rect-backward';
}
}
functions[name] = true;
break;
case 'dimension':
var unit = value[i][3][2];
switch (unit) {
// is not supported until IE11
case 'rem':
// v* units is too buggy across browsers and better
// don't merge values with those units
case 'vw':
case 'vh':
case 'vmin':
case 'vmax':
case 'vm': // IE9 supporting "vm" instead of "vmin".
units[unit] = true;
break;
}
break;
}
}
return (
fp + pre + property +
'[' + Object.keys(functions) + ']' +
Object.keys(units) +
hack9 + vendorId
);
};
CSSOCompressor.prototype.getVendorIDFromToken = function(token) {
var vendorId;
switch (token[1]) {
case 'ident':
vendorId = this.getVendorFromString(token[2]);
break;
case 'funktion':
vendorId = this.getVendorFromString(token[2][2]);
break;
}
if (vendorId) {
return constants.vendorID[vendorId] || '';
}
return '';
};
CSSOCompressor.prototype.getVendorFromString = function(string) {
if (string[0] === '-') {
var secondDashIndex = string.indexOf('-', 2);
if (secondDashIndex !== -1) {
return string.substr(0, secondDashIndex + 1);
}
}
return '';
};
CSSOCompressor.prototype.deleteProperty = function(block, id) {
for (var i = block.length - 1; i > 1; i--) {
var child = block[i];
if (Array.isArray(child) &&
child[1] === 'declaration' &&
child[0].id === id) {
block.splice(i, 1);
return;
}
}
};
CSSOCompressor.prototype.needless = function(name, props, pre, important, v, d, freeze) {
var hack = name[0];
if (hack === '*' || hack === '_' || hack === '$') {
name = name.substr(1);
} else if (hack === '/' && name[1] === '/') {
hack = '//';
name = name.substr(2);
} else {
hack = '';
}
var vendor = this.getVendorFromString(name);
var table = constants.nlTable[name.substr(vendor.length)];
if (table) {
for (var i = 0; i < table.length; i++) {
var ppre = this.buildPPre(pre, hack + vendor + table[i], v, d, freeze);
var property = props[ppre];
if (property) {
return (!important || property.important);
}
}
}
};
CSSOCompressor.prototype.rejoinRuleset = function(token, rule, container, i) {
var prev = i === 2 || container[i - 1][1] === 'unknown' ? null : container[i - 1];
var prevSelector = prev ? prev[2] : [];
var prevBlock = prev ? prev[3] : [];
var selector = token[2];
var block = token[3];
if (block.length === 2) {
return null;
}
if (prevSelector.length > 2 &&
prevBlock.length > 2 &&
token[0].pseudoSignature == prev[0].pseudoSignature) {
if (token[1] !== prev[1]) {
return;
}
// try to join by selectors
var prevHash = this.getHash(prevSelector);
var hash = this.getHash(selector);
if (this.equalHash(hash, prevHash)) {
prev[3] = prev[3].concat(token[3].splice(2));
return null;
}
if (this.okToJoinByProperties(token, prev)) {
// try to join by properties
var r = this.analyze(token, prev);
if (!r.ne1.length && !r.ne2.length) {
prev[2] = this.cleanSelector(prev[2].concat(token[2].splice(2)));
prev[2][0].s = translate(prev[2], true);
return null;
}
}
}
};
CSSOCompressor.prototype.okToJoinByProperties = function(token1, token2) {
var info1 = token1[0];
var info2 = token2[0];
// same frozen ruleset
if (info1.freezeID === info2.freezeID) {
return true;
}
// same pseudo-classes in selectors
if (info1.pseudoID === info2.pseudoID) {
return true;
}
// different frozen rulesets
if (info1.freeze && info2.freeze) {
var signature1 = this.pseudoSelectorSignature(token1[2], constants.allowedPClasses);
var signature2 = this.pseudoSelectorSignature(token2[2], constants.allowedPClasses);
return signature1 === signature2;
}
// is it frozen at all?
return !info1.freeze && !info2.freeze;
};
CSSOCompressor.prototype.containsOnlyAllowedPClasses = function(selector) {
for (var i = 2; i < selector.length; i++) {
var simpleSelector = selector[i];
for (var j = 2; j < simpleSelector.length; j++) {
if (simpleSelector[j][1] == 'pseudoc' ||
simpleSelector[j][1] == 'pseudoe') {
if (!constants.allowedPClasses[simpleSelector[j][2][2]]) {
return false;
}
}
}
}
return true;
};
CSSOCompressor.prototype.restructureRuleset = function(token, rule, parent, i) {
var prevToken = (i === 2 || parent[i - 1][1] === 'unknown') ? null : parent[i - 1];
var prevSelector = prevToken ? prevToken[2] : [];
var prevBlock = prevToken ? prevToken[3] : [];
var selector = token[2];
var block = token[3];
if (block.length < 3) {
return null;
}
if (prevSelector.length > 2 &&
prevBlock.length > 2 &&
token[0].pseudoSignature == prevToken[0].pseudoSignature) {
if (token[1] !== prevToken[1]) {
return;
}
// try to join by properties
var analyzeInfo = this.analyze(token, prevToken);
if (analyzeInfo.eq.length && (analyzeInfo.ne1.length || analyzeInfo.ne2.length)) {
if (analyzeInfo.ne1.length && !analyzeInfo.ne2.length) {
// prevToken in token
var simpleSelectorCount = selector.length - 2; // - type and info
var selectorStr = translate(selector, true);
var selectorLength = selectorStr.length +
simpleSelectorCount - 1; // delims count
var blockLength = this.calcLength(analyzeInfo.eq) + // declarations length
analyzeInfo.eq.length - 1; // decldelims length
if (selectorLength < blockLength) {
prevToken[2] = this.cleanSelector(prevSelector.concat(selector.slice(2)));
token[3] = [block[0], block[1]].concat(analyzeInfo.ne1);
return token;
}
} else if (analyzeInfo.ne2.length && !analyzeInfo.ne1.length) {
// token in prevToken
var simpleSelectorCount = prevSelector.length - 2; // - type and info
var selectorStr = translate(prevSelector, true);
// selectorLength = selector str - delims count
var selectorLength = selectorStr.length + simpleSelectorCount - 1;
var blockLength = this.calcLength(analyzeInfo.eq) + // declarations length
analyzeInfo.eq.length - 1; // decldelims length
if (selectorLength < blockLength) {
token[2] = this.cleanSelector(prevSelector.concat(selector.slice(2)));
prevToken[3] = [prevBlock[0], prevBlock[1]].concat(analyzeInfo.ne2);
return token;
}
} else {
// extract equal block?
var newSelector = this.cleanSelector(prevSelector.concat(selector.slice(2)));
var newSelectorStr = translate(newSelector, true);
var newSelectorLength = newSelectorStr.length + // selector length
newSelector.length - 1 + // delims length
2; // braces length
var blockLength = this.calcLength(analyzeInfo.eq) + // declarations length
analyzeInfo.eq.length - 1; // decldelims length
// ok, it's good enough to extract
if (blockLength >= newSelectorLength) {
var newRuleset = [
{},
'ruleset',
newSelector,
[{}, 'block'].concat(analyzeInfo.eq)
];
newSelector[0].s = newSelectorStr;
token[3] = [block[0], block[1]].concat(analyzeInfo.ne1);
prevToken[3] = [prevBlock[0], prevBlock[1]].concat(analyzeInfo.ne2);
parent.splice(i, 0, newRuleset);
return newRuleset;
}
}
}
}
};
CSSOCompressor.prototype.calcLength = function(tokens) {
var length = 0;
for (var i = 0; i < tokens.length; i++) {
length += tokens[i][0].s.length;
return {
type: 'StyleSheet',
rules: result
};
return length;
};
CSSOCompressor.prototype.cleanSelector = function(token) {
if (token.length === 2) {
return null;
}
var saw = {};
for (var i = 2; i < token.length; i++) {
var selector = token[i][0].s;
if (saw.hasOwnProperty(selector)) {
token.splice(i, 1);
i--;
} else {
saw[selector] = true;
}
}
return token;
};
CSSOCompressor.prototype.analyze = function(token1, token2) {
var result = {
eq: [],
ne1: [],
ne2: []
};
if (token1[1] !== token2[1]) {
return result;
}
var items1 = token1[3];
var items2 = token2[3];
var hash1 = this.getHash(items1);
var hash2 = this.getHash(items2);
for (var i = 2; i < items1.length; i++) {
var item = items1[i];
if (item[0].s in hash2) {
result.eq.push(item);
} else {
result.ne1.push(item);
}
}
for (var i = 2; i < items2.length; i++) {
var item = items2[i];
if (item[0].s in hash1 === false) {
result.ne2.push(item);
}
}
return result;
};
CSSOCompressor.prototype.equalHash = function(h0, h1) {
for (var key in h0) {
if (key in h1 === false) {
return false;
}
}
for (var key in h1) {
if (key in h0 === false) {
return false;
}
}
return true;
};
CSSOCompressor.prototype.getHash = function(tokens) {
var hash = {};
for (var i = 2; i < tokens.length; i++) {
hash[tokens[i][0].s] = true;
}
return hash;
};
CSSOCompressor.prototype.hashInHash = function(hash1, hash2) {
for (var key in hash1) {
if (key in hash2 === false) {
return false;
}
}
return true;
};
CSSOCompressor.prototype.delimSelectors = function(token) {
for (var i = token.length - 1; i > 2; i--) {
token.splice(i, 0, [{}, 'delim']);
}
};
CSSOCompressor.prototype.delimBlocks = function(token) {
for (var i = token.length - 1; i > 2; i--) {
token.splice(i, 0, [{}, 'decldelim']);
}
};
CSSOCompressor.prototype.copyObject = function(obj) {
var result = {};
for (var key in obj) {
result[key] = obj[key];
}
return result;
};
CSSOCompressor.prototype.copyArray = function(token) {
var result = token.slice();
var oldInfo = token[0];
var newInfo = {};
for (var key in oldInfo) {
newInfo[key] = oldInfo[key];
}
result[0] = newInfo;
for (var i = 2; i < token.length; i++) {
if (Array.isArray(token[i])) {
result[i] = this.copyArray(token[i]);
}
}
return result;
};
CSSOCompressor.prototype.pathUp = function(path) {
return path.substr(0, path.lastIndexOf('/'));
};
module.exports = function(tree, options) {
return new CSSOCompressor().compress(tree, options || {});
};
var parse = require('./parser');
var compress = require('./compressor');
var traslateInternal = require('./compressor/ast/translate');
var walk = require('./utils/walker');
var translate = require('./utils/translate');

@@ -14,12 +16,34 @@ var stringify = require('./utils/stringify');

});
return translate(compressed, true);
return traslateInternal(compressed);
};
var minify = function(src, options) {
var minifyOptions = {
outputAst: 'internal'
};
if (options) {
for (var key in options) {
minifyOptions[key] = options[key];
}
}
var t = Date.now();
var ast = parse(src, 'stylesheet', true);
var compressed = compress(ast, options);
return translate(compressed, true);
if (minifyOptions.debug) {
console.error('## parse', Date.now() - t);
}
var t = Date.now();
var compressed = compress(ast, minifyOptions);
if (minifyOptions.debug) {
console.error('## compress', Date.now() - t);
}
return traslateInternal(compressed);
};
module.exports = {
version: require('../package.json').version,
// main method

@@ -33,2 +57,3 @@ minify: minify,

walk: walk,
stringify: stringify,

@@ -35,0 +60,0 @@ cleanInfo: cleanInfo,

@@ -1347,5 +1347,15 @@ var TokenType = require('./const.js');

// node: Number
function checkNumber(_i) {
function checkNumber(_i, sign) {
if (_i < tokens.length && tokens[_i].number_l) return tokens[_i].number_l;
if (!sign && _i < tokens.length && tokens[_i].type === TokenType.HyphenMinus) {
var x = checkNumber(_i + 1, true);
if (x) {
tokens[_i].number_l = x + 1;
return tokens[_i].number_l;
} else {
fail(tokens[_i])
}
}
if (_i < tokens.length && tokens[_i].type === TokenType.DecimalNumber &&

@@ -1722,2 +1732,6 @@ (!tokens[_i + 1] ||

if (!t) {
throwError();
}
if ((needInfo && typeof t[1] === 'string') || typeof t[0] === 'string') ss.push(t);

@@ -2097,4 +2111,9 @@ else ss = ss.concat(t);

var ast = CSSPRules[rule]();
if (!ast && rule === 'stylesheet') {
return needInfo ? [{}, rule] : [rule];
}
//console.log(require('../utils/stringify.js')(require('../utils/cleanInfo.js')(ast), true));
return ast;
};
{
"name": "csso",
"description": "CSSO — CSS optimizer",
"version": "1.4.4",
"version": "1.5.0",
"homepage": "https://github.com/css/csso",
"author": "Sergey Kryzhanovsky <skryzhanovsky@ya.ru> (https://github.com/afelix)",
"maintainers": [
{
"name": "Roman Dvornov",
"email": "rdvornov@gmail.com",
"github-username": "lahmatiy"
}
],
"license": "MIT",

@@ -26,3 +33,3 @@ "repository": "css/csso",

"devDependencies": {
"browserify": "^12.0.1",
"browserify": "^13.0.0",
"jscs": "^2.6.0",

@@ -29,0 +36,0 @@ "mocha": "~2.3.3",

@@ -60,2 +60,8 @@ [![NPM version](https://img.shields.io/npm/v/csso.svg)](https://www.npmjs.com/package/csso)

// .test{color:red}
// There are two options you can pass
var compressedWithOptions = csso.minify('.test { color: #ff0000; }', {
restructuring: false, // don't combine same selectors
debug: true // show additional debug information
})
```

@@ -62,0 +68,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc