index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. "use strict";
  2. /*
  3. MIT License http://www.opensource.org/licenses/mit-license.php
  4. Authors
  5. Tobias Koppers @sokra
  6. Johannes Ewald @jhnns
  7. */
  8. var less = require("less");
  9. var fs = require("fs");
  10. var loaderUtils = require("loader-utils");
  11. var path = require("path");
  12. var util = require("util");
  13. var trailingSlash = /[\\\/]$/;
  14. module.exports = function(source) {
  15. var loaderContext = this;
  16. var query = loaderUtils.parseQuery(this.query);
  17. var cb = this.async();
  18. var isSync = typeof cb !== "function";
  19. var finalCb = cb || this.callback;
  20. var configKey = query.config || "lessLoader";
  21. var config = {
  22. filename: this.resource,
  23. paths: [],
  24. relativeUrls: true,
  25. compress: !!this.minimize
  26. };
  27. var webpackPlugin = {
  28. install: function(less, pluginManager) {
  29. var WebpackFileManager = getWebpackFileManager(less, loaderContext, query, isSync);
  30. pluginManager.addFileManager(new WebpackFileManager());
  31. },
  32. minVersion: [2, 1, 1]
  33. };
  34. this.cacheable && this.cacheable();
  35. Object.keys(query).forEach(function(attr) {
  36. config[attr] = query[attr];
  37. });
  38. // Now we're adding the webpack plugin, because there might have
  39. // been added some before via query-options.
  40. config.plugins = config.plugins || [];
  41. config.plugins.push(webpackPlugin);
  42. // If present, add custom LESS plugins.
  43. if (this.options[configKey]) {
  44. config.plugins = config.plugins.concat(this.options[configKey].lessPlugins || []);
  45. }
  46. // not using the `this.sourceMap` flag because css source maps are different
  47. // @see https://github.com/webpack/css-loader/pull/40
  48. if (query.sourceMap) {
  49. config.sourceMap = {
  50. outputSourceFiles: true
  51. };
  52. }
  53. less.render(source, config, function(e, result) {
  54. var cb = finalCb;
  55. // Less is giving us double callbacks sometimes :(
  56. // Thus we need to mark the callback as "has been called"
  57. if(!finalCb) return;
  58. finalCb = null;
  59. if(e) return cb(formatLessRenderError(e));
  60. cb(null, result.css, result.map);
  61. });
  62. };
  63. function getWebpackFileManager(less, loaderContext, query, isSync) {
  64. function WebpackFileManager() {
  65. less.FileManager.apply(this, arguments);
  66. }
  67. WebpackFileManager.prototype = Object.create(less.FileManager.prototype);
  68. WebpackFileManager.prototype.supports = function(filename, currentDirectory, options, environment) {
  69. // Our WebpackFileManager handles all the files
  70. return true;
  71. };
  72. WebpackFileManager.prototype.supportsSync = function(filename, currentDirectory, options, environment) {
  73. return isSync;
  74. };
  75. WebpackFileManager.prototype.loadFile = function(filename, currentDirectory, options, environment, callback) {
  76. // Unfortunately we don't have any influence on less to call `loadFile` or `loadFileSync`
  77. // thus we need to decide for ourselves.
  78. // @see https://github.com/less/less.js/issues/2325
  79. if (isSync) {
  80. try {
  81. callback(null, this.loadFileSync(filename, currentDirectory, options, environment));
  82. } catch (err) {
  83. callback(err);
  84. }
  85. return;
  86. }
  87. var moduleRequest = loaderUtils.urlToRequest(filename, query.root);
  88. // Less is giving us trailing slashes, but the context should have no trailing slash
  89. var context = currentDirectory.replace(trailingSlash, "");
  90. loaderContext.resolve(context, moduleRequest, function(err, filename) {
  91. if(err) {
  92. callback(err);
  93. return;
  94. }
  95. loaderContext.dependency && loaderContext.dependency(filename);
  96. // The default (asynchronous)
  97. loaderContext.loadModule("-!" + __dirname + "/stringify.loader.js!" + filename, function(err, data) {
  98. if(err) {
  99. callback(err);
  100. return;
  101. }
  102. callback(null, {
  103. contents: JSON.parse(data),
  104. filename: filename
  105. });
  106. });
  107. });
  108. };
  109. WebpackFileManager.prototype.loadFileSync = util.deprecate(function(filename, currentDirectory, options, environment) {
  110. var moduleRequest = loaderUtils.urlToRequest(filename, query.root);
  111. // Less is giving us trailing slashes, but the context should have no trailing slash
  112. var context = currentDirectory.replace(trailingSlash, "");
  113. var data;
  114. filename = loaderContext.resolveSync(context, moduleRequest);
  115. loaderContext.dependency && loaderContext.dependency(filename);
  116. data = fs.readFileSync(filename, "utf8");
  117. return {
  118. contents: data,
  119. filename: filename
  120. };
  121. }, "We are planing to remove enhanced-require support with the next major release of the less-loader: https://github.com/webpack/less-loader/issues/84");
  122. return WebpackFileManager;
  123. }
  124. function formatLessRenderError(e) {
  125. // Example ``e``:
  126. // { type: 'Name',
  127. // message: '.undefined-mixin is undefined',
  128. // filename: '/path/to/style.less',
  129. // index: 352,
  130. // line: 31,
  131. // callLine: NaN,
  132. // callExtract: undefined,
  133. // column: 6,
  134. // extract:
  135. // [ ' .my-style {',
  136. // ' .undefined-mixin;',
  137. // ' display: block;' ] }
  138. var extract = e.extract? "\n near lines:\n " + e.extract.join("\n ") : "";
  139. var err = new Error(
  140. e.message + "\n @ " + e.filename +
  141. " (line " + e.line + ", column " + e.column + ")" +
  142. extract
  143. );
  144. err.hideStack = true;
  145. return err;
  146. }