alex-cory/react-css-to-js

View on GitHub
cssToJs.js

Summary

Maintainability
A
3 hrs
Test Coverage
var fs = require('fs');
var path = require('path');
var fileToConvert = path.basename(process.argv[2]);
var file = fs.readFileSync(fileToConvert, 'utf8');
var css = require('css');
var ast = css.parse(file);
convertCssToJs(ast, fileToConvert);

// REFERENCE: http://iamdustan.com/reworkcss_ast_explorer/
// CSS LIBRARY: https://github.com/reworkcss/css
// REACT-CSS: https://www.npmjs.com/package/react-css
// REACT-STYL: https://www.npmjs.com/package/react-styl


/**
 * Converts a CSS class/id grouping to a JSX compatible grouping.
 * @param  Object rule             Contains unconverted selectors, attributes, etc.
 * @return Object convertedRule Contains an object of the converted rule.
 */
function convertRule(rule) {

  if (rule.type === 'rule') {

    var convertedRule = {
      //convert the selector (i.e. .selector#id ▶ selector id)
      selector: convertSelector(rule.selectors),
      //convert the attributes (i.e.  background-color: #ffff; ▶ backgroundColor: '#ffff',)
      attributes: convertAttributes(rule.declarations)
    };
  }

  return convertedRule;
}


/**
 * Converts the attributes into a JSX compatible format.
 * @param  Array attributes          The declaration objects and the
 *                                      values for each CSS selector.
 * @return Array convertedAttributes The converted declaration Objects.
 */
function convertAttributes(attributes) {

  var convertedAttributes = [];

    attributes.forEach(function (attribute) {

      var property = attribute.property; // ex: background-color
      var value = attribute.value;       // ex: lightgrey
      var convertedAttr = {
        property: '',
        value: ''
      };

      //if the property and the value exist  (keeps the key value pair together)
      if (attribute.property && attribute.value) {
        convertedAttr.property = stripMinusToCamel(attribute.property);
        convertedAttr.value = convertAttrValue(attribute.value);
      }

      convertedAttributes.push(convertedAttr);
    });

  //console.log(convertedAttributes);
  return convertedAttributes;
}


function convertAttrValue(value) {
  if (value) {
    if (value.indexOf('#') !== -1 || value.indexOf('%') !== -1) {
      //surround it in single quotes
      value = "'" + value + "'";

    //if has pattern /word-word/  OR      contains (
    } else if (value.match(/\w-\w/g) || value.indexOf('(') !== -1) {

      //strip the - and camel case it
      value = stripMinusToCamel(value);

      //remove the px
      value = value.replace(/\b(\d+)[p][x]\b/g, '$1');

      //rap it in single quotes
      value = "'" + value + "'";

    } else if (value.indexOf('px') !== -1) {

      //remove the px
      value = value.replace(/\b(\d+)[p][x]\b/g, '$1');

    //if string contains 3 or more consecutive letters
    } else if (value.match(/[a-z]{3,20}/gi)) {

      //rap it in single quotes
      value = "'" + value + "'";
    }
  }

  return value;
}


/**
 * Converts a property to camel casing.
 * @param  String property The CSS property to be converted.
 * @return String          The converted CSS string property.
 */
function stripMinusToCamel(attrProperty) {

  //if property contains a -
  if (attrProperty.indexOf('-') !== -1 /*&& isNotInParens(attrProperty)*/) {

    //strip the - and uppercase the letter after it  (check regex here: http://www.regexr.com/3b43v)
    return attrProperty.replace(/-([a-z])/g, function(v) { return v[1].toUpperCase(); });

  } else {

    return attrProperty;

  }
}


/**
 * Converts the css selector to a JSX compatible selector.
 * @param  Array selector [description]
 * @return {[type]}       [description]
 */
function convertSelector(selector) {

  //TODO: - in brackets, - in url()
  var selectorNodes = [];

    //if there's 1 selector (i.e.  .selector#someID )
    if (selector.length === 1) {

      //removes all -'s and converts to camel
      selector = stripMinusToCamel(selector.toString());

      //remove all .'s, #'s, and spaces
      selectorNodes = selector.toString().split(/\s\.|\s|\.|\s\#|\#/).join(' ').trim(' ').split(' ');

    //if there's multiple comma seperated selectors   (i.e.  .selector#someID1, .selector#someID2, )
    } else if (selector.length > 1) {

      selector.forEach(function (multiSelectorNode) {

        //removes all -'s and converts to camel
        selector = stripMinusToCamel(selector.toString());

        //remove the .'s, #'s, and spaces
        selectorNodes = multiSelectorNode.toString().split(/\s\.|\s|\.|\s\#|\#/).join(' ').trim(' ').split(' ');

      });
    }

  return selectorNodes;
}


/**
 * Converts a `style` or rule back into usable text.
 * @param  Object convertedRule   The converted CSS selector and
 *                                it's attributes.
 * @return String convertedString The coverted CSS string.
 */
function jsStringify(convertedRule) {

  var convertedString = '';

  convertedString += '\n' + convertedRule.selector.toString() + ': {\n';
  for (var i = 0, l = convertedRule.attributes.length; i < l; i++) {
    if (i === l - 1) {
      convertedString += '  ' + convertedRule.attributes[i].property + ': ' + convertedRule.attributes[i].value + '\n';
    } else {
      convertedString += '  ' + convertedRule.attributes[i].property + ': ' + convertedRule.attributes[i].value + ',\n';
    }
  }
  convertedString += '},';

  return convertedString;
}


/**
 * Converts the CSS file into a JS file that works with React.js.
 * @param  String obj A string with the contents of the CSS file.
 */
function convertCssToJs(obj, fileWeAreConverting) {
  var rules = obj.stylesheet.rules;
  var convertedRules = [];
  var convertedCSS = '';
  var convertedFileName = fileWeAreConverting.replace(/\.[^/.]+$/, ".js"); // replace .css file extension with .js

  rules.forEach(function(rule) {

    var convertedRule = convertRule(rule);

    //if the line isn't a comment
    if (convertedRule !== undefined) {

      fs.appendFile(convertedFileName, jsStringify(convertedRule), function(err) {
        if(err) {
          return console.log(err);
        }
      });
    }
  });
  console.log("The file was saved!");
}