198 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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
 | 
						|
    };
 | 
						|
};
 |