Code coverage report for lib/px2rem.js

Statements: 100% (85 / 85)      Branches: 97.62% (41 / 42)      Functions: 100% (16 / 16)      Lines: 100% (85 / 85)      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    1 1     1             1     1 2 2       1 6 6 6 6   1 12 72 6 66 18     48   300 156 156 84 12 12 72 48     144         12     6   6       1 2 2 2   1 4 24 2 22 6       16 16 48 48 48 48 60   48 48     16   100 52 52 28   16 48 48 48 48   16 16 12 4   8     24           16 12       4     2   2       1 16 132 100 448             1 224 224   1 272 272     224 272       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 arrayPush = Array.prototype.push;
 
 
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) {
        rules.forEach(function(rule) {
            if (rule.type === 'media') {
                return processRules(rule.rules); // recursive invocation while dealing with media queries
            } else if (rule.type !== 'rule') {
                return;
            }
 
            rule.declarations.forEach(function(declaration, i) {
                // need transform: declaration && commented 'px'
                if (declaration.type === 'declaration' && /px/.test(declaration.value)) {
                    var nextDeclaration = rule.declarations[i + 1];
                    if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
                        if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
                            nextDeclaration.toDelete = true;
                            return;
                        } else if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
                            nextDeclaration.toDelete = true;
                        }
                    }
                    declaration.value = self._getCalcValue('px', declaration.value, dpr); // common transform
                }
            });
        });
 
        self._deleteNouseRules(rules);
    }
 
    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) {
        rules.forEach(function(rule) {
            if (rule.type === 'media') {
                return processRules(rule.rules); // recursive invocation while dealing with media queries
            } else if (rule.type !== 'rule') {
                return;
            }
 
            // 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.forEach(function(sel) {
                    newRule.selectors.push('[data-dpr="' + dpr + '"] ' + sel);
                });
                newRule.declarations = [];
                newRules.push(newRule);
            }
 
            rule.declarations.forEach(function(declaration, i) {
                // need transform: declaration && commented 'px'
                if (declaration.type === 'declaration' && /px/.test(declaration.value)) {
                    var nextDeclaration = rule.declarations[i + 1];
                    if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
                        if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
                            // 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);
                            }
                            declaration.toDelete = true;
                            nextDeclaration.toDelete = true;
                        } else if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
                            nextDeclaration.toDelete = true;
                        } else {
                            declaration.value = self._getCalcValue('rem', declaration.value); // common transform
                        }
                    } else {
                        declaration.value = self._getCalcValue('rem', declaration.value); // common transform
                    }
                }
            });
 
            // append the declarations which are forced to use px in the end of origin stylesheet
            if (newRules[0].declarations.length) {
                arrayPush.apply(rules, newRules);
            }
        });
 
        self._deleteNouseRules(rules);
    }
 
    processRules(astObj.stylesheet.rules);
 
    return css.stringify(astObj);
};
 
// delete no use info in rules in ast tree
Px2rem.prototype._deleteNouseRules = function(rules) {
    rules.forEach(function(rule, i) {
        if (rule.type === 'rule') {
            rule.declarations = rule.declarations.filter(function(declaration) {
                return !declaration.toDelete;
            });
        }
    });
};
 
// get calculated value of px or rem
Px2rem.prototype._getCalcValue = function(type, value, dpr) {
    var config = this.config;
    var REG = /\b(\d+(\.\d+)?)px\b/gi;
 
    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(REG, function($0, $1) {
        return type === 'px' ? getValue($1 * dpr / config.baseDpr) : getValue($1 / config.remUnit);
    });
};
 
module.exports = Px2rem;