var List = require('../utils/list'); var clone = require('../utils/clone'); var usageUtils = require('./usage'); var clean = require('./clean'); var compress = require('./compress'); var restructureBlock = require('./restructure'); var walkRules = require('../utils/walk').rules; function readRulesChunk(rules, specialComments) { var buffer = new List(); var nonSpaceTokenInBuffer = false; var protectedComment; rules.nextUntil(rules.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 !== 'Space') { nonSpaceTokenInBuffer = true; } buffer.insert(list.remove(item)); }); return { comment: protectedComment, stylesheet: { type: 'StyleSheet', info: null, rules: buffer } }; } function compressChunk(ast, firstAtrulesAllowed, usageData, num, logger) { logger('Compress block #' + num, null, true); var seed = 1; walkRules(ast, function markStylesheets() { if ('id' in this.stylesheet === false) { this.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed; this.stylesheet.id = seed++; } }); logger('init', ast); // remove redundant clean(ast, usageData); logger('clean', ast); // compress nodes compress(ast, usageData); logger('compress', ast); 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([{ 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 || {}; var logger = typeof options.logger === 'function' ? options.logger : Function(); var specialComments = getCommentsOption(options); var restructuring = getRestructureOption(options); var firstAtrulesAllowed = true; var usageData = false; var inputRules; var outputRules = new List(); var chunk; var chunkNum = 1; var chunkRules; if (options.clone) { ast = clone(ast); } if (ast.type === 'StyleSheet') { inputRules = ast.rules; ast.rules = outputRules; } else { inputRules = wrapBlock(ast); } if (options.usage) { usageData = usageUtils.buildIndex(options.usage); } do { chunk = readRulesChunk(inputRules, Boolean(specialComments)); compressChunk(chunk.stylesheet, firstAtrulesAllowed, usageData, chunkNum++, logger); // structure optimisations if (restructuring) { restructureBlock(chunk.stylesheet, usageData, logger); } chunkRules = chunk.stylesheet.rules; if (chunk.comment) { // add \n before comment if there is another content in outputRules if (!outputRules.isEmpty()) { outputRules.insert(List.createItem({ type: 'Raw', value: '\n' })); } outputRules.insert(List.createItem(chunk.comment)); // add \n after comment if chunk is not empty if (!chunkRules.isEmpty()) { outputRules.insert(List.createItem({ type: 'Raw', value: '\n' })); } } if (firstAtrulesAllowed && !chunkRules.isEmpty()) { var lastRule = chunkRules.last(); if (lastRule.type !== 'Atrule' || (lastRule.name !== 'import' && lastRule.name !== 'charset')) { firstAtrulesAllowed = false; } } if (specialComments !== 'exclamation') { specialComments = false; } outputRules.appendList(chunkRules); } while (!inputRules.isEmpty()); return { ast: ast }; };