| 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) { |
| return 'restructure' in options ? options.restructure : |
| '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 |
| }; |
| }; |