gasolin/provecss

View on GitHub
import.js

Summary

Maintainability
A
35 mins
Test Coverage
//
// # Import
//
// Subsitute `@import { file: filename; }` with the contents of `filename`.
//

/*jslint node: true */
"use strict";

var fs         = require('fs');
var path       = require('path');
var whitespace = require('css-whitespace');
var rework     = require('rework');

//
// ## Register plugin
//
// * **opts**, options object. May contain the following:
//
//   * path: base path for resolving imports.
//   * whitespace: boolean, set to true if imported files use significant
//     whitespace instead of curlies.
//
module.exports = function (opts) {
  return function (style) {
    return new Import(opts).visit(style);
  };
};

//
// ## Importer
//
function Import(opts) {
  if(!opts.base) {
    throw new Error("Must specify a file path");
  }

  opts            = opts || {};
  this.opts       = opts;
  this.base       = opts.base || process.cwd();
  this.path       = opts.path;
  this.visit      = this.visit.bind(this);
  this.importFile = this.importFile.bind(this);
  this.map        = opts.map || [];
  this.target     = opts.target;

  // is relative?
  if(path.resolve(this.path) !== this.path) {
    this.path = path.resolve(this.base, this.path);
  }
}

Import.prototype.visit = function (node, index, arr) {
  if (!node) return;
  var type = node.type || 'stylesheet';
  if (!this[type]) return;
  this[type](node, index, arr);
};

Import.prototype.stylesheet = function (stylesheet) {
  for (var i = stylesheet.rules.length; i >= 0; i-=1) {
    this.visit(stylesheet.rules[i], i, stylesheet.rules);
  }
};

Import.prototype.import = function (node, index, arr) {
  var regex    = /url\(['"]?(.*?)['"]?\)/;
  var filename = node.import.match(regex);
  if (filename && filename[1] && !isUrl(filename[1])) {
    if(this.target) {
      // /(\w+)_(common|target)\.\w+/
      var targetRegex = new RegExp('(\\w+)_(' + this.target.join('|') + ')\\.\\w+');
      var matchTarget = filename[1].match(targetRegex);
      if(!matchTarget) {
        arr.splice(index, 1);
        return;
      }
    }

    var ast = this.parseFile(filename[1]);
    var i = 0;
    arr.splice(index, 1);

    ast.rules.forEach(function (rule) {
      arr.splice(0 + i + index, 0, rule);
      i++;
    });
  }
};

Import.prototype.rule = function (rule, index, base) {
  if (rule.selectors[0] == '@import') {
    var ast   = rule.declarations.map(this.importFile);
    var rules = [];
    ast.filter(function (item) {
      return !!item;
    }).forEach(function (item) {
      rules = rules.concat(item.rules);
    });

    var removed = base.splice(index, 1);
    // Insert rules at same index
    var i = 0; // To make imports in order.
    rules.forEach(function (rule) {
      var removed = base.splice(index + i, 0, rule);
      i++;
    });
  }
};

Import.prototype.importFile = function (declaration) {
  if (declaration.property !== 'file') return;
  return this.parseFile(declaration.value);
};

Import.prototype.parseFile = function (file) {
  var load;
  //is absolute?
  if(path.resolve(file) === file) {
    load = path.join(this.base, file);
  } else {
    load = path.resolve(path.dirname(this.path), file);
  }

  // Skip circular imports.
  if (this.map.indexOf(load) !== -1) {
    return false;
  }
  var data = fs.readFileSync(load, this.opts.encoding || 'utf8');

  if (this.opts.whitespace) {
    data = whitespace(data);
  }

  this.map.push(load);
  // Create AST and look for imports in imported code.
  var opts = {
    whitespace: this.opts.whitespace,
    map: this.map,
    base: this.base,
    path: load
  };

  var ast = rework(data).use(module.exports(opts));
  return ast.obj.stylesheet;
};


function isUrl(url) {
  return (/^([\w]+:)?\/\/./).test(url);
}