You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
6.5 KiB
JavaScript

2 years ago
'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 === 'keyframes') {
processRules(rule.keyframes); // recursive invocation while dealing with keyframes
continue;
} else if (rule.type !== 'rule' && rule.type !== 'keyframe') {
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, noDealPx) { // FIXME: keyframes do not support `force px` comment
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 === 'keyframes') {
processRules(rule.keyframes, true); // recursive invocation while dealing with keyframes
continue;
} else if (rule.type !== 'rule' && rule.type !== 'keyframe') {
continue;
}
if (!noDealPx) {
// 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;
}
if (!noDealPx) {
// 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 { // FIXME: keyframes do not support `force px` comment
declaration.value = self._getCalcValue('rem', declaration.value); // common transform
declarations.splice(j + 1, 1); // delete corresponding comment
}
} 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--;
}
if (!noDealPx) {
// 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;