| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 | '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;}
 |