deps-resolver.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /**
  2. * Module dependencies.
  3. */
  4. var Visitor = require('./')
  5. , Parser = require('../parser')
  6. , nodes = require('../nodes')
  7. , utils = require('../utils')
  8. , dirname = require('path').dirname
  9. , fs = require('fs');
  10. /**
  11. * Initialize a new `DepsResolver` with the given `root` Node
  12. * and the `options`.
  13. *
  14. * @param {Node} root
  15. * @param {Object} options
  16. * @api private
  17. */
  18. var DepsResolver = module.exports = function DepsResolver(root, options) {
  19. this.root = root;
  20. this.filename = options.filename;
  21. this.paths = options.paths || [];
  22. this.paths.push(dirname(options.filename || '.'));
  23. this.options = options;
  24. this.functions = {};
  25. this.deps = [];
  26. };
  27. /**
  28. * Inherit from `Visitor.prototype`.
  29. */
  30. DepsResolver.prototype.__proto__ = Visitor.prototype;
  31. var visit = DepsResolver.prototype.visit;
  32. DepsResolver.prototype.visit = function(node) {
  33. switch (node.nodeName) {
  34. case 'root':
  35. case 'block':
  36. case 'expression':
  37. this.visitRoot(node);
  38. break;
  39. case 'group':
  40. case 'media':
  41. case 'atblock':
  42. case 'atrule':
  43. case 'keyframes':
  44. case 'each':
  45. case 'supports':
  46. this.visit(node.block);
  47. break;
  48. default:
  49. visit.call(this, node);
  50. }
  51. };
  52. /**
  53. * Visit Root.
  54. */
  55. DepsResolver.prototype.visitRoot = function(block) {
  56. for (var i = 0, len = block.nodes.length; i < len; ++i) {
  57. this.visit(block.nodes[i]);
  58. }
  59. };
  60. /**
  61. * Visit Ident.
  62. */
  63. DepsResolver.prototype.visitIdent = function(ident) {
  64. this.visit(ident.val);
  65. };
  66. /**
  67. * Visit If.
  68. */
  69. DepsResolver.prototype.visitIf = function(node) {
  70. this.visit(node.block);
  71. this.visit(node.cond);
  72. for (var i = 0, len = node.elses.length; i < len; ++i) {
  73. this.visit(node.elses[i]);
  74. }
  75. };
  76. /**
  77. * Visit Function.
  78. */
  79. DepsResolver.prototype.visitFunction = function(fn) {
  80. this.functions[fn.name] = fn.block;
  81. };
  82. /**
  83. * Visit Call.
  84. */
  85. DepsResolver.prototype.visitCall = function(call) {
  86. if (call.name in this.functions) this.visit(this.functions[call.name]);
  87. if (call.block) this.visit(call.block);
  88. };
  89. /**
  90. * Visit Import.
  91. */
  92. DepsResolver.prototype.visitImport = function(node) {
  93. // If it's a url() call, skip
  94. if (node.path.first.name === 'url') return;
  95. var path = !node.path.first.val.isNull && node.path.first.val || node.path.first.name
  96. , literal, found, oldPath;
  97. if (!path) return;
  98. literal = /\.css(?:"|$)/.test(path);
  99. // support optional .styl
  100. if (!literal && !/\.styl$/i.test(path)) {
  101. oldPath = path;
  102. path += '.styl';
  103. }
  104. // Lookup
  105. found = utils.find(path, this.paths, this.filename);
  106. // support optional index
  107. if (!found && oldPath) found = utils.lookupIndex(oldPath, this.paths, this.filename);
  108. if (!found) return;
  109. this.deps = this.deps.concat(found);
  110. if (literal) return;
  111. // nested imports
  112. for (var i = 0, len = found.length; i < len; ++i) {
  113. var file = found[i]
  114. , dir = dirname(file)
  115. , str = fs.readFileSync(file, 'utf-8')
  116. , block = new nodes.Block
  117. , parser = new Parser(str, utils.merge({ root: block }, this.options));
  118. if (!~this.paths.indexOf(dir)) this.paths.push(dir);
  119. try {
  120. block = parser.parse();
  121. } catch (err) {
  122. err.filename = file;
  123. err.lineno = parser.lexer.lineno;
  124. err.column = parser.lexer.column;
  125. err.input = str;
  126. throw err;
  127. }
  128. this.visit(block);
  129. }
  130. };
  131. /**
  132. * Get dependencies.
  133. */
  134. DepsResolver.prototype.resolve = function() {
  135. this.visit(this.root);
  136. return utils.uniq(this.deps);
  137. };