Code coverage report for lib/px2rem.js

Statements: 100% (93 / 93)      Branches: 97.73% (43 / 44)      Functions: 100% (9 / 9)      Lines: 100% (93 / 93)      Ignored: none     

All files » lib/ » px2rem.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160    1 1   1               1   1 2 2       1 6 6 6 6   1 12 84 84 6 6 78 18     60 60 252   252 168 168 96 12 12 84 60     156           6   6       1 2 2 2   1 4 28 28 2 2 26 6       20 20 60 60 60 72   60 60     20 20 84   84 56 56 32   20 4 4 4     16 48 48 48 48   16 16 12 4   8     24           20 4 4       20 12 12         2   2       1 236 236   1 284 284     236 284       1  
'use strict';
 
var css = require('css');
var extend = require('extend');
 
var defaultConfig = {
    baseDpr: 2,             // base device pixel ratio (default: 2)
    remUnit: 75,            // rem unit value (default: 75)
    remPrecision: 6,        // rem value precision (default: 6)
    forcePxComment: 'px',   // force px comment (default: `px`)
    keepComment: 'no'       // no transform value comment (default: `no`)
};
 
var pxRegExp = /\b(\d+(\.\d+)?)px\b/;
 
function Px2rem(options) {
    this.config = {};
    extend(this.config, defaultConfig, options);
}
 
// generate @1x, @2x and @3x version stylesheet
Px2rem.prototype.generateThree = function(cssText, dpr) {
    dpr = dpr || 2;
    var self = this;
    var config = self.config;
    var astObj = css.parse(cssText);
 
    function processRules(rules) {
        for (var i = 0; i < rules.length; i++) {
            var rule = rules[i];
            if (rule.type === 'media') {
                processRules(rule.rules); // recursive invocation while dealing with media queries
                continue;
            } else if (rule.type !== 'rule') {
                continue;
            }
 
            var declarations = rule.declarations;
            for (var j = 0; j < declarations.length; j++) {
                var declaration = declarations[j];
                // need transform: declaration && has 'px'
                if (declaration.type === 'declaration' && pxRegExp.test(declaration.value)) {
                    var nextDeclaration = rule.declarations[j + 1];
                    if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
                        if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
                            declarations.splice(j + 1, 1); // delete corresponding comment
                            continue;
                        } else if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
                            declarations.splice(j + 1, 1); // delete corresponding comment
                        }
                    }
                    declaration.value = self._getCalcValue('px', declaration.value, dpr); // common transform
                }
            }
        }
    }
 
    processRules(astObj.stylesheet.rules);
 
    return css.stringify(astObj);
};
 
// generate rem version stylesheet
Px2rem.prototype.generateRem = function(cssText) {
    var self = this;
    var config = self.config;
    var astObj = css.parse(cssText);
 
    function processRules(rules) {
        for (var i = 0; i < rules.length; i++) {
            var rule = rules[i];
            if (rule.type === 'media') {
                processRules(rule.rules); // recursive invocation while dealing with media queries
                continue;
            } else if (rule.type !== 'rule') {
                continue;
            }
 
            // generate 3 new rules which has [data-dpr]
            var newRules = [];
            for (var dpr = 1; dpr <= 3; dpr++) {
                var newRule = {};
                newRule.type = rule.type;
                newRule.selectors = rule.selectors.map(function(sel) {
                    return '[data-dpr="' + dpr + '"] ' + sel;
                });
                newRule.declarations = [];
                newRules.push(newRule);
            }
 
            var declarations = rule.declarations;
            for (var j = 0; j < declarations.length; j++) {
                var declaration = declarations[j];
                // need transform: declaration && has 'px'
                if (declaration.type === 'declaration' && pxRegExp.test(declaration.value)) {
                    var nextDeclaration = rule.declarations[j + 1];
                    if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
                        if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
                            // do not transform `0px`
                            if (declaration.value === '0px') {
                                declaration.value = '0';
                                declarations.splice(j + 1, 1); // delete corresponding comment
                                continue;
                            }
                            // generate 3 new declarations and put them in the new rules which has [data-dpr]
                            for (var dpr = 1; dpr <= 3; dpr++) {
                                var newDeclaration = {};
                                extend(true, newDeclaration, declaration);
                                newDeclaration.value = self._getCalcValue('px', newDeclaration.value, dpr);
                                newRules[dpr - 1].declarations.push(newDeclaration);
                            }
                            declarations.splice(j, 2); // delete this rule and corresponding comment
                            j--;
                        } else if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
                            declarations.splice(j + 1, 1); // delete corresponding comment
                        } else {
                            declaration.value = self._getCalcValue('rem', declaration.value); // common transform
                        }
                    } else {
                        declaration.value = self._getCalcValue('rem', declaration.value); // common transform
                    }
                }
            }
 
            // if the origin rule has no declarations, delete it
            if (!rules[i].declarations.length) {
                rules.splice(i, 1);
                i--;
            }
 
            // add the new rules which contain declarations that are forced to use px
            if (newRules[0].declarations.length) {
                rules.splice(i + 1, 0, newRules[0], newRules[1], newRules[2]);
                i += 3; // skip the added new rules
            }
        }
    }
 
    processRules(astObj.stylesheet.rules);
 
    return css.stringify(astObj);
};
 
// get calculated value of px or rem
Px2rem.prototype._getCalcValue = function(type, value, dpr) {
    var config = this.config;
    var pxGlobalRegExp = new RegExp(pxRegExp.source, 'g');
 
    function getValue(val) {
        val = parseFloat(val.toFixed(config.remPrecision)); // control decimal precision of the calculated value
        return val == 0 ? val : val + type;
    }
 
    return value.replace(pxGlobalRegExp, function($0, $1) {
        return type === 'px' ? getValue($1 * dpr / config.baseDpr) : getValue($1 / config.remUnit);
    });
};
 
module.exports = Px2rem;