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.
		
		
		
		
		
			
		
			
				
					
					
						
							160 lines
						
					
					
						
							4.0 KiB
						
					
					
				
			
		
		
	
	
							160 lines
						
					
					
						
							4.0 KiB
						
					
					
				'use strict'; | 
						|
 | 
						|
exports.type = 'full'; | 
						|
 | 
						|
exports.active = true; | 
						|
 | 
						|
exports.description = 'minifies styles and removes unused styles based on usage data'; | 
						|
 | 
						|
exports.params = { | 
						|
    // ... CSSO options goes here | 
						|
 | 
						|
    // additional  | 
						|
    usage: { | 
						|
        force: false,  // force to use usage data even if it unsafe (document contains <script> or on* attributes) | 
						|
        ids: true, | 
						|
        classes: true, | 
						|
        tags: true | 
						|
    } | 
						|
}; | 
						|
 | 
						|
var csso = require('csso'); | 
						|
 | 
						|
/** | 
						|
 * Minifies styles (<style> element + style attribute) using CSSO | 
						|
 * | 
						|
 * @author strarsis <strarsis@gmail.com> | 
						|
 */ | 
						|
exports.fn = function(ast, options) { | 
						|
    options = options || {}; | 
						|
 | 
						|
    var minifyOptionsForStylesheet = cloneObject(options); | 
						|
    var minifyOptionsForAttribute = cloneObject(options); | 
						|
    var elems = findStyleElems(ast); | 
						|
 | 
						|
    minifyOptionsForStylesheet.usage = collectUsageData(ast, options); | 
						|
    minifyOptionsForAttribute.usage = null; | 
						|
 | 
						|
    elems.forEach(function(elem) { | 
						|
        if (elem.isElem('style')) { | 
						|
            // <style> element | 
						|
            var styleCss = elem.content[0].text || elem.content[0].cdata || []; | 
						|
            var DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text'; | 
						|
 | 
						|
            elem.content[0][DATA] = csso.minify(styleCss, minifyOptionsForStylesheet).css; | 
						|
        } else { | 
						|
            // style attribute | 
						|
            var elemStyle = elem.attr('style').value; | 
						|
 | 
						|
            elem.attr('style').value = csso.minifyBlock(elemStyle, minifyOptionsForAttribute).css; | 
						|
        } | 
						|
    }); | 
						|
 | 
						|
    return ast; | 
						|
}; | 
						|
 | 
						|
function cloneObject(obj) { | 
						|
    var result = {}; | 
						|
 | 
						|
    for (var key in obj) { | 
						|
        result[key] = obj[key]; | 
						|
    } | 
						|
 | 
						|
    return result; | 
						|
} | 
						|
 | 
						|
function findStyleElems(ast) { | 
						|
 | 
						|
    function walk(items, styles) { | 
						|
        for (var i = 0; i < items.content.length; i++) { | 
						|
            var item = items.content[i]; | 
						|
 | 
						|
            // go deeper | 
						|
            if (item.content) { | 
						|
                walk(item, styles); | 
						|
            } | 
						|
 | 
						|
            if (item.isElem('style') && !item.isEmpty()) { | 
						|
                styles.push(item); | 
						|
            } else if (item.isElem() && item.hasAttr('style')) { | 
						|
                styles.push(item); | 
						|
            } | 
						|
        } | 
						|
 | 
						|
        return styles; | 
						|
    } | 
						|
 | 
						|
    return walk(ast, []); | 
						|
} | 
						|
 | 
						|
function shouldFilter(options, name) { | 
						|
    if ('usage' in options === false) { | 
						|
        return true; | 
						|
    } | 
						|
 | 
						|
    if (options.usage && name in options.usage === false) { | 
						|
        return true; | 
						|
    } | 
						|
 | 
						|
    return Boolean(options.usage && options.usage[name]); | 
						|
} | 
						|
 | 
						|
function collectUsageData(ast, options) { | 
						|
 | 
						|
    function walk(items, usageData) { | 
						|
        for (var i = 0; i < items.content.length; i++) { | 
						|
            var item = items.content[i]; | 
						|
 | 
						|
            // go deeper | 
						|
            if (item.content) { | 
						|
                walk(item, usageData); | 
						|
            } | 
						|
 | 
						|
            if (item.isElem('script')) { | 
						|
                safe = false; | 
						|
            } | 
						|
 | 
						|
            if (item.isElem()) { | 
						|
                usageData.tags[item.elem] = true; | 
						|
 | 
						|
                if (item.hasAttr('id')) { | 
						|
                    usageData.ids[item.attr('id').value] = true; | 
						|
                } | 
						|
 | 
						|
                if (item.hasAttr('class')) { | 
						|
                    item.attr('class').value.replace(/^\s+|\s+$/g, '').split(/\s+/).forEach(function(className) { | 
						|
                        usageData.classes[className] = true; | 
						|
                    }); | 
						|
                } | 
						|
 | 
						|
                if (item.attrs && Object.keys(item.attrs).some(function(name) { return /^on/i.test(name); })) { | 
						|
                    safe = false; | 
						|
                } | 
						|
            } | 
						|
        } | 
						|
 | 
						|
        return usageData; | 
						|
    } | 
						|
 | 
						|
    var safe = true; | 
						|
    var usageData = {}; | 
						|
    var hasData = false; | 
						|
    var rawData = walk(ast, { | 
						|
        ids: Object.create(null), | 
						|
        classes: Object.create(null), | 
						|
        tags: Object.create(null) | 
						|
    }); | 
						|
 | 
						|
    if (!safe && options.usage && options.usage.force) { | 
						|
        safe = true; | 
						|
    } | 
						|
 | 
						|
    for (var key in rawData) { | 
						|
        if (shouldFilter(options, key)) { | 
						|
            usageData[key] = Object.keys(rawData[key]); | 
						|
            hasData = true; | 
						|
        } | 
						|
    } | 
						|
 | 
						|
    return safe && hasData ? usageData : null; | 
						|
}
 | 
						|
 |