You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
5.1 KiB
197 lines
5.1 KiB
var List = require('css-tree').List; |
|
var clone = require('css-tree').clone; |
|
var usageUtils = require('./usage'); |
|
var clean = require('./clean'); |
|
var replace = require('./replace'); |
|
var restructure = require('./restructure'); |
|
var walk = require('css-tree').walk; |
|
|
|
function readChunk(children, specialComments) { |
|
var buffer = new List(); |
|
var nonSpaceTokenInBuffer = false; |
|
var protectedComment; |
|
|
|
children.nextUntil(children.head, function(node, item, list) { |
|
if (node.type === 'Comment') { |
|
if (!specialComments || node.value.charAt(0) !== '!') { |
|
list.remove(item); |
|
return; |
|
} |
|
|
|
if (nonSpaceTokenInBuffer || protectedComment) { |
|
return true; |
|
} |
|
|
|
list.remove(item); |
|
protectedComment = node; |
|
return; |
|
} |
|
|
|
if (node.type !== 'WhiteSpace') { |
|
nonSpaceTokenInBuffer = true; |
|
} |
|
|
|
buffer.insert(list.remove(item)); |
|
}); |
|
|
|
return { |
|
comment: protectedComment, |
|
stylesheet: { |
|
type: 'StyleSheet', |
|
loc: null, |
|
children: buffer |
|
} |
|
}; |
|
} |
|
|
|
function compressChunk(ast, firstAtrulesAllowed, num, options) { |
|
options.logger('Compress block #' + num, null, true); |
|
|
|
var seed = 1; |
|
|
|
if (ast.type === 'StyleSheet') { |
|
ast.firstAtrulesAllowed = firstAtrulesAllowed; |
|
ast.id = seed++; |
|
} |
|
|
|
walk(ast, { |
|
visit: 'Atrule', |
|
enter: function markScopes(node) { |
|
if (node.block !== null) { |
|
node.block.id = seed++; |
|
} |
|
} |
|
}); |
|
options.logger('init', ast); |
|
|
|
// remove redundant |
|
clean(ast, options); |
|
options.logger('clean', ast); |
|
|
|
// replace nodes for shortened forms |
|
replace(ast, options); |
|
options.logger('replace', ast); |
|
|
|
// structure optimisations |
|
if (options.restructuring) { |
|
restructure(ast, options); |
|
} |
|
|
|
return ast; |
|
} |
|
|
|
function getCommentsOption(options) { |
|
var comments = 'comments' in options ? options.comments : 'exclamation'; |
|
|
|
if (typeof comments === 'boolean') { |
|
comments = comments ? 'exclamation' : false; |
|
} else if (comments !== 'exclamation' && comments !== 'first-exclamation') { |
|
comments = false; |
|
} |
|
|
|
return comments; |
|
} |
|
|
|
function getRestructureOption(options) { |
|
if ('restructure' in options) { |
|
return options.restructure; |
|
} |
|
|
|
return 'restructuring' in options ? options.restructuring : true; |
|
} |
|
|
|
function wrapBlock(block) { |
|
return new List().appendData({ |
|
type: 'Rule', |
|
loc: null, |
|
prelude: { |
|
type: 'SelectorList', |
|
loc: null, |
|
children: new List().appendData({ |
|
type: 'Selector', |
|
loc: null, |
|
children: new List().appendData({ |
|
type: 'TypeSelector', |
|
loc: null, |
|
name: 'x' |
|
}) |
|
}) |
|
}, |
|
block: block |
|
}); |
|
} |
|
|
|
module.exports = function compress(ast, options) { |
|
ast = ast || { type: 'StyleSheet', loc: null, children: new List() }; |
|
options = options || {}; |
|
|
|
var compressOptions = { |
|
logger: typeof options.logger === 'function' ? options.logger : function() {}, |
|
restructuring: getRestructureOption(options), |
|
forceMediaMerge: Boolean(options.forceMediaMerge), |
|
usage: options.usage ? usageUtils.buildIndex(options.usage) : false |
|
}; |
|
var specialComments = getCommentsOption(options); |
|
var firstAtrulesAllowed = true; |
|
var input; |
|
var output = new List(); |
|
var chunk; |
|
var chunkNum = 1; |
|
var chunkChildren; |
|
|
|
if (options.clone) { |
|
ast = clone(ast); |
|
} |
|
|
|
if (ast.type === 'StyleSheet') { |
|
input = ast.children; |
|
ast.children = output; |
|
} else { |
|
input = wrapBlock(ast); |
|
} |
|
|
|
do { |
|
chunk = readChunk(input, Boolean(specialComments)); |
|
compressChunk(chunk.stylesheet, firstAtrulesAllowed, chunkNum++, compressOptions); |
|
chunkChildren = chunk.stylesheet.children; |
|
|
|
if (chunk.comment) { |
|
// add \n before comment if there is another content in output |
|
if (!output.isEmpty()) { |
|
output.insert(List.createItem({ |
|
type: 'Raw', |
|
value: '\n' |
|
})); |
|
} |
|
|
|
output.insert(List.createItem(chunk.comment)); |
|
|
|
// add \n after comment if chunk is not empty |
|
if (!chunkChildren.isEmpty()) { |
|
output.insert(List.createItem({ |
|
type: 'Raw', |
|
value: '\n' |
|
})); |
|
} |
|
} |
|
|
|
if (firstAtrulesAllowed && !chunkChildren.isEmpty()) { |
|
var lastRule = chunkChildren.last(); |
|
|
|
if (lastRule.type !== 'Atrule' || |
|
(lastRule.name !== 'import' && lastRule.name !== 'charset')) { |
|
firstAtrulesAllowed = false; |
|
} |
|
} |
|
|
|
if (specialComments !== 'exclamation') { |
|
specialComments = false; |
|
} |
|
|
|
output.appendList(chunkChildren); |
|
} while (!input.isEmpty()); |
|
|
|
return { |
|
ast: ast |
|
}; |
|
};
|
|
|