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

index.js 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. 'use strict';
  2. var fs = require('fs');
  3. var path = require('path');
  4. var css = require('css');
  5. var globby = require('globby');
  6. var parse = require('parse-import');
  7. var urlRegex = require('url-regex');
  8. /**
  9. * Get options
  10. *
  11. * @param {Array} rules
  12. * @param {Object} opts
  13. * @api private
  14. */
  15. function getOptions(rules, opts) {
  16. var dir;
  17. var obj = {
  18. source: opts.source,
  19. transform: opts.transform || function (val) {
  20. return val;
  21. }
  22. };
  23. obj.path = opts.path || [];
  24. obj.path = Array.isArray(obj.path) ? obj.path : [obj.path];
  25. if (rules.length && rules[0].position && rules[0].position.source) {
  26. dir = path.dirname(rules[0].position.source);
  27. }
  28. if (dir && obj.path.indexOf(dir) === -1) {
  29. obj.path.unshift(dir);
  30. }
  31. if (!obj.path.length) {
  32. obj.path.push(process.cwd());
  33. }
  34. return obj;
  35. }
  36. /**
  37. * Create error
  38. *
  39. * @param {String} file
  40. * @param {String} src
  41. * @param {Array} paths
  42. * @api private
  43. */
  44. function createError(file, src, paths) {
  45. var err = ['Failed to find ' + file];
  46. if (src) {
  47. err.push('from ' + src);
  48. }
  49. err.push([
  50. 'in [',
  51. ' ' + paths.join(',\n '),
  52. ']'
  53. ].join('\n'));
  54. return err.join(' ');
  55. }
  56. /**
  57. * Create bad import rule error
  58. *
  59. * @param {String} rule
  60. * @api private
  61. */
  62. function createImportError(rule) {
  63. var url = rule.import ? rule.import.replace(/\r?\n/g, '\\n') : '<no url>';
  64. var err = ['Bad import url: @import ' + url];
  65. if (rule.position) {
  66. err.push(' starting at line ' + rule.position.start.line + ' column ' + rule.position.start.column);
  67. err.push(' ending at line ' + rule.position.end.line + ' column ' + rule.position.end.column);
  68. if (rule.position.source) {
  69. err.push(' in ' + rule.position.source);
  70. }
  71. }
  72. return err.join('\n');
  73. }
  74. /**
  75. * Check if a file exists
  76. *
  77. * @param {String} file
  78. * @param {String} src
  79. * @param {Object} opts
  80. * @api private
  81. */
  82. function exists(file, src, opts) {
  83. var files = opts.path.map(function (dir) {
  84. return path.join(dir, file);
  85. });
  86. files = globby.sync(files);
  87. if (!files.length) {
  88. throw new Error(createError(file, src, opts.path));
  89. }
  90. return files[0];
  91. }
  92. /**
  93. * Read the contents of a file
  94. *
  95. * @param {String} file
  96. * @param {Object} opts
  97. * @api private
  98. */
  99. function read(file, opts) {
  100. var encoding = opts.encoding || 'utf8';
  101. var data = opts.transform(fs.readFileSync(file, encoding));
  102. return css.parse(data, {source: file}).stylesheet;
  103. }
  104. /**
  105. * Run
  106. *
  107. * @param {Object} style
  108. * @param {Object} opts
  109. * @api private
  110. */
  111. function run(style, opts) {
  112. opts = getOptions(style.rules, opts || {});
  113. var rules = style.rules || [];
  114. var ret = [];
  115. rules.forEach(function (rule) {
  116. if (rule.type !== 'import') {
  117. ret.push(rule);
  118. return;
  119. }
  120. var importRule = '@import ' + rule.import + ';';
  121. var data = parse(importRule)[0];
  122. var pos = rule.position ? rule.position.source : null;
  123. if (!data) {
  124. throw Error(createImportError(rule));
  125. }
  126. if (urlRegex({ exact: true }).test(data.path)) {
  127. ret.push(rule);
  128. return;
  129. }
  130. opts.source = exists(data.path, pos, opts);
  131. if (opts.path.indexOf(path.dirname(opts.source)) === -1) {
  132. opts.path.unshift(path.dirname(opts.source));
  133. }
  134. var content = read(opts.source, opts);
  135. run(content, opts);
  136. if (!data.condition || !data.condition.length) {
  137. ret = ret.concat(content.rules);
  138. return;
  139. }
  140. ret.push({
  141. media: data.condition,
  142. rules: content.rules,
  143. type: 'media'
  144. });
  145. });
  146. style.rules = ret;
  147. }
  148. /**
  149. * Module exports
  150. */
  151. module.exports = function (opts) {
  152. return function (style) {
  153. run(style, opts);
  154. };
  155. };