index.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. var loaderUtils = require('loader-utils');
  2. var stylus = require('stylus');
  3. var path = require('path');
  4. var fs = require('fs');
  5. var when = require('when');
  6. var whenNodefn = require('when/node/function');
  7. var cloneDeep = require('lodash.clonedeep');
  8. var CachedPathEvaluator = require('./lib/evaluator');
  9. var PathCache = require('./lib/pathcache');
  10. var resolver = require('./lib/resolver');
  11. var globalImportsCaches = {};
  12. module.exports = function(source) {
  13. var self = this;
  14. this.cacheable && this.cacheable();
  15. var done = this.async();
  16. var options = cloneDeep(loaderUtils.getOptions(this) || {});
  17. options.dest = options.dest || '';
  18. options.filename = options.filename || this.resourcePath;
  19. options.Evaluator = CachedPathEvaluator;
  20. var configKey, stylusOptions;
  21. if (this.stylus) {
  22. configKey = options.config || 'default';
  23. stylusOptions = this.stylus[configKey] || {};
  24. } else if (this.options) {
  25. configKey = options.config || 'stylus';
  26. stylusOptions = this.options[configKey] || {};
  27. } else {
  28. stylusOptions = {};
  29. }
  30. // Instead of assigning to options, we run them manually later so their side effects apply earlier for
  31. // resolving paths.
  32. var use = options.use || stylusOptions.use || [];
  33. options.import = options.import || stylusOptions.import || [];
  34. options.include = options.include || stylusOptions.include || [];
  35. options.set = options.set || stylusOptions.set || {};
  36. options.define = options.define || stylusOptions.define || {};
  37. options.paths = options.paths || stylusOptions.paths;
  38. if (options.sourceMap != null) {
  39. options.sourcemap = options.sourceMap;
  40. delete options.sourceMap;
  41. }
  42. else if (this.sourceMap) {
  43. options.sourcemap = { comment: false };
  44. }
  45. var styl = stylus(source, options);
  46. var paths = [path.dirname(options.filename)];
  47. function needsArray(value) {
  48. return (Array.isArray(value)) ? value : [value];
  49. }
  50. if (options.paths && !Array.isArray(options.paths)) {
  51. paths = paths.concat(options.paths);
  52. options.paths = [options.paths];
  53. }
  54. var manualImports = [];
  55. Object.keys(options).forEach(function(key) {
  56. var value = options[key];
  57. if (key === 'use') {
  58. needsArray(value).forEach(function(plugin) {
  59. if (typeof plugin === 'function') {
  60. styl.use(plugin);
  61. } else {
  62. throw new Error('Plugin should be a function');
  63. }
  64. });
  65. } else if (key === 'set') {
  66. for (var name in value) {
  67. styl.set(name, value[name]);
  68. }
  69. } else if (key === 'define') {
  70. for (var defineName in value) {
  71. styl.define(defineName, value[defineName]);
  72. }
  73. } else if (key === 'include') {
  74. needsArray(value).forEach(styl.include.bind(styl));
  75. } else if (key === 'import') {
  76. needsArray(value).forEach(function(stylusModule) {
  77. manualImports.push(stylusModule);
  78. });
  79. } else {
  80. styl.set(key, value);
  81. if (key === 'resolve url' && value) {
  82. styl.define('url', resolver());
  83. }
  84. }
  85. });
  86. var shouldCacheImports = stylusOptions.importsCache !== false;
  87. var importsCache;
  88. if (stylusOptions.importsCache !== false) {
  89. if (typeof stylusOptions.importsCache === 'object') {
  90. importsCache = stylusOptions.importsCache;
  91. } else {
  92. if(!globalImportsCaches[configKey]) globalImportsCaches[configKey] = {};
  93. importsCache = globalImportsCaches[configKey];
  94. }
  95. }
  96. // Use input file system's readFile if available. The normal webpack input
  97. // file system is cached with entries purged when they are detected to be
  98. // changed on disk by the watcher.
  99. var readFile;
  100. try {
  101. var inputFileSystem = this._compiler.inputFileSystem;
  102. readFile = inputFileSystem.readFile.bind(inputFileSystem);
  103. } catch (error) {
  104. readFile = fs.readFile;
  105. }
  106. var boundResolvers = PathCache.resolvers(options, this.resolve);
  107. var pathCacheHelpers = {
  108. resolvers: boundResolvers,
  109. readFile: readFile,
  110. };
  111. // Use plugins here so that resolve related side effects can be used while we resolve imports.
  112. (Array.isArray(use) ? use : [use]).forEach(styl.use, styl);
  113. when
  114. // Resolve manual imports like @import files.
  115. .reduce(manualImports, function resolveManualImports(carry, filename) {
  116. return PathCache.resolvers
  117. .reduce(boundResolvers, path.dirname(options.filename), filename)
  118. .then(function(paths) { return carry.concat(paths); });
  119. }, [])
  120. // Resolve dependencies of
  121. .then(function(paths) {
  122. paths.forEach(styl.import.bind(styl));
  123. paths.forEach(self.addDependency);
  124. var readFile = whenNodefn.lift(pathCacheHelpers.readFile);
  125. return when.reduce(paths, function(cache, filepath) {
  126. return readFile(filepath)
  127. .then(function(source) {
  128. return PathCache.createFromFile(
  129. pathCacheHelpers, cache, source.toString(), filepath
  130. );
  131. });
  132. }, {
  133. contexts: {},
  134. sources: {},
  135. imports: importsCache,
  136. });
  137. })
  138. .then(function(cache) {
  139. return PathCache
  140. .createFromFile(pathCacheHelpers, cache, source, options.filename);
  141. })
  142. .then(function(importPathCache) {
  143. // CachedPathEvaluator will use this PathCache to find its dependencies.
  144. options.cache = importPathCache;
  145. importPathCache.allDeps().forEach(function(f) {
  146. self.addDependency(path.normalize(f));
  147. });
  148. // var paths = importPathCache.origins;
  149. styl.render(function(err, css) {
  150. if (err) {
  151. done(err);
  152. } else {
  153. if (styl.sourcemap) {
  154. styl.sourcemap.sourcesContent = styl.sourcemap.sources.map(function (file) {
  155. return importPathCache.sources[path.resolve(file)]
  156. });
  157. }
  158. done(null, css, styl.sourcemap);
  159. }
  160. });
  161. })
  162. .catch(done);
  163. };
  164. var LoaderOptionsPlugin = require('webpack').LoaderOptionsPlugin;
  165. // Webpack 2 plugin for setting options that'll be available to stylus-loader.
  166. function OptionsPlugin(options) {
  167. if (!LoaderOptionsPlugin) {
  168. throw new Error(
  169. 'webpack.LoaderOptionPlugin is not available. A newer version of webpack is needed.'
  170. );
  171. }
  172. var stylusOptions = {};
  173. var test = options.test || /\.styl$/;
  174. var include = options.include;
  175. var exclude = options.exclude;
  176. var loaderOptions = {
  177. stylus: stylusOptions,
  178. };
  179. for (var key in options) {
  180. if (['test', 'include', 'exclude'].indexOf(key) === -1) {
  181. stylusOptions[key] = options[key];
  182. }
  183. }
  184. if (test) {
  185. loaderOptions.test = test;
  186. }
  187. if (include) {
  188. loaderOptions.include = include;
  189. }
  190. if (exclude) {
  191. loaderOptions.exclude = exclude;
  192. }
  193. this.plugin = new LoaderOptionsPlugin(loaderOptions);
  194. };
  195. module.exports.OptionsPlugin = OptionsPlugin;
  196. OptionsPlugin.prototype.apply = function(compiler) {
  197. this.plugin.apply(compiler);
  198. };