value-processor.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. /*
  2. * MIT License http://opensource.org/licenses/MIT
  3. * Author: Ben Holloway @bholloway
  4. */
  5. 'use strict';
  6. var path = require('path'),
  7. loaderUtils = require('loader-utils');
  8. /**
  9. * Create a value processing function for a given file path.
  10. *
  11. * @param {string} filename The current file being processed
  12. * @param {{absolute:string, keepQuery:boolean, join:function, root:string}} options Options hash
  13. * @return {function} value processing function
  14. */
  15. function valueProcessor(filename, options) {
  16. var URL_STATEMENT_REGEX = /(url\s*\()\s*(?:(['"])((?:(?!\2).)*)(\2)|([^'"](?:(?!\)).)*[^'"]))\s*(\))/g;
  17. var directory = path.dirname(filename);
  18. var join = options.join(filename, options);
  19. /**
  20. * Process the given CSS declaration value.
  21. *
  22. * @param {string} value A declaration value that may or may not contain a url() statement
  23. * @param {string|Iterator.<string>} candidate An absolute path that may be the correct base or an Iterator thereof
  24. */
  25. return function transformValue(value, candidate) {
  26. // allow multiple url() values in the declaration
  27. // split by url statements and process the content
  28. // additional capture groups are needed to match quotations correctly
  29. // escaped quotations are not considered
  30. return value
  31. .split(URL_STATEMENT_REGEX)
  32. .map(eachSplitOrGroup)
  33. .join('');
  34. /**
  35. * Encode the content portion of <code>url()</code> statements.
  36. * There are 4 capture groups in the split making every 5th unmatched.
  37. *
  38. * @param {string} token A single split item
  39. * @param {number} i The index of the item in the split
  40. * @param {Array} arr The array of split values
  41. * @returns {string} Every 3 or 5 items is an encoded url everything else is as is
  42. */
  43. function eachSplitOrGroup(token, i, arr) {
  44. // we can get groups as undefined under certain match circumstances
  45. var initialised = token || '';
  46. // the content of the url() statement is either in group 3 or group 5
  47. var mod = i % 7;
  48. if ((mod === 3) || (mod === 5)) {
  49. // detect quoted url and unescape backslashes
  50. var before = arr[i - 1],
  51. after = arr[i + 1],
  52. isQuoted = (before === after) && ((before === '\'') || (before === '"')),
  53. unescaped = isQuoted ? initialised.replace(/\\{2}/g, '\\') : initialised;
  54. // split into uri and query/hash and then find the absolute path to the uri
  55. var split = unescaped.split(/([?#])/g),
  56. uri = split[0],
  57. absolute = testIsRelative(uri) && join(uri, candidate) || testIsAbsolute(uri) && join(uri),
  58. query = options.keepQuery ? split.slice(1).join('') : '';
  59. // use the absolute path in absolute mode or else relative path (or default to initialised)
  60. // #6 - backslashes are not legal in URI
  61. if (!absolute) {
  62. return initialised;
  63. } else if (options.absolute) {
  64. return absolute.replace(/\\/g, '/') + query;
  65. } else {
  66. return loaderUtils.urlToRequest(
  67. path.relative(directory, absolute).replace(/\\/g, '/') + query
  68. );
  69. }
  70. }
  71. // everything else, including parentheses and quotation (where present) and media statements
  72. else {
  73. return initialised;
  74. }
  75. }
  76. };
  77. /**
  78. * The loaderUtils.isUrlRequest() doesn't support windows absolute paths on principle. We do not subscribe to that
  79. * dogma so we add path.isAbsolute() check to allow them.
  80. *
  81. * We also eliminate module relative (~) paths.
  82. *
  83. * @param {string|undefined} uri A uri string possibly empty or undefined
  84. * @return {boolean} True for relative uri
  85. */
  86. function testIsRelative(uri) {
  87. return !!uri && loaderUtils.isUrlRequest(uri, false) && !path.isAbsolute(uri) && (uri.indexOf('~') !== 0);
  88. }
  89. /**
  90. * The loaderUtils.isUrlRequest() doesn't support windows absolute paths on principle. We do not subscribe to that
  91. * dogma so we add path.isAbsolute() check to allow them.
  92. *
  93. * @param {string|undefined} uri A uri string possibly empty or undefined
  94. * @return {boolean} True for absolute uri
  95. */
  96. function testIsAbsolute(uri) {
  97. return !!uri && (typeof options.root === 'string') && loaderUtils.isUrlRequest(uri, options.root) &&
  98. (/^\//.test(uri) || path.isAbsolute(uri));
  99. }
  100. }
  101. module.exports = valueProcessor;