createWebpackLessPlugin.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. "use strict";
  2. /* eslint-disable class-methods-use-this */
  3. const less = require('less');
  4. const loaderUtils = require('loader-utils');
  5. const pify = require('pify');
  6. const stringifyLoader = require.resolve('./stringifyLoader.js');
  7. const trailingSlash = /[/\\]$/;
  8. const isLessCompatible = /\.(le|c)ss$/; // Less automatically adds a .less file extension if no extension was given.
  9. // This is problematic if there is a module request like @import "~some-module";
  10. // because in this case Less will call our file manager with `~some-module.less`.
  11. // Since dots in module names are highly discouraged, we can safely assume that
  12. // this is an error and we need to remove the .less extension again.
  13. // However, we must not match something like @import "~some-module/file.less";
  14. const matchMalformedModuleFilename = /(~[^/\\]+)\.less$/; // This somewhat changed in Less 3.x. Now the file name comes without the
  15. // automatically added extension whereas the extension is passed in as `options.ext`.
  16. // So, if the file name matches this regexp, we simply ignore the proposed extension.
  17. const isModuleName = /^~[^/\\]+$/;
  18. /**
  19. * Creates a Less plugin that uses webpack's resolving engine that is provided by the loaderContext.
  20. *
  21. * @param {LoaderContext} loaderContext
  22. * @param {string=} root
  23. * @returns {LessPlugin}
  24. */
  25. function createWebpackLessPlugin(loaderContext) {
  26. const {
  27. fs
  28. } = loaderContext;
  29. const resolve = pify(loaderContext.resolve.bind(loaderContext));
  30. const loadModule = pify(loaderContext.loadModule.bind(loaderContext));
  31. const readFile = pify(fs.readFile.bind(fs));
  32. class WebpackFileManager extends less.FileManager {
  33. supports() {
  34. // Our WebpackFileManager handles all the files
  35. return true;
  36. } // Sync resolving is used at least by the `data-uri` function.
  37. // This file manager doesn't know how to do it, so let's delegate it
  38. // to the default file manager of Less.
  39. // We could probably use loaderContext.resolveSync, but it's deprecated,
  40. // see https://webpack.js.org/api/loaders/#this-resolvesync
  41. supportsSync() {
  42. return false;
  43. }
  44. loadFile(filename, currentDirectory, options) {
  45. let url;
  46. if (less.version[0] >= 3) {
  47. if (options.ext && !isModuleName.test(filename)) {
  48. url = this.tryAppendExtension(filename, options.ext);
  49. } else {
  50. url = filename;
  51. }
  52. } else {
  53. url = filename.replace(matchMalformedModuleFilename, '$1');
  54. }
  55. const moduleRequest = loaderUtils.urlToRequest(url, url.charAt(0) === '/' ? '' : null); // Less is giving us trailing slashes, but the context should have no trailing slash
  56. const context = currentDirectory.replace(trailingSlash, '');
  57. let resolvedFilename;
  58. return resolve(context, moduleRequest).then(f => {
  59. resolvedFilename = f;
  60. loaderContext.addDependency(resolvedFilename);
  61. if (isLessCompatible.test(resolvedFilename)) {
  62. return readFile(resolvedFilename).then(contents => contents.toString('utf8'));
  63. }
  64. return loadModule([stringifyLoader, resolvedFilename].join('!')).then(JSON.parse);
  65. }).then(contents => {
  66. return {
  67. contents,
  68. filename: resolvedFilename
  69. };
  70. });
  71. }
  72. }
  73. return {
  74. install(lessInstance, pluginManager) {
  75. pluginManager.addFileManager(new WebpackFileManager());
  76. },
  77. minVersion: [2, 1, 1]
  78. };
  79. }
  80. module.exports = createWebpackLessPlugin;