value-processor.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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 {function(Object):string} join The inner join function
  12. * @param {string} root The loader options.root value where given
  13. * @param {string} directory The directory of the file webpack is currently processing
  14. * @return {function} value processing function
  15. */
  16. function valueProcessor({ join, root, directory }) {
  17. var URL_STATEMENT_REGEX = /(url\s*\(\s*)(?:(['"])((?:(?!\2).)*)(\2)|([^'"](?:(?!\)).)*[^'"]))(\s*\))/g,
  18. QUERY_REGEX = /([?#])/g;
  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 {function(number):Object} getPathsAtChar Given an offset in the declaration value get a
  24. * list of possible absolute path strings
  25. */
  26. return function transformValue(value, getPathsAtChar) {
  27. // allow multiple url() values in the declaration
  28. // split by url statements and process the content
  29. // additional capture groups are needed to match quotations correctly
  30. // escaped quotations are not considered
  31. return value
  32. .split(URL_STATEMENT_REGEX)
  33. .map(initialise)
  34. .map(eachSplitOrGroup)
  35. .join('');
  36. /**
  37. * Ensure all capture group tokens are a valid string.
  38. *
  39. * @param {string|void} token A capture group or uncaptured token
  40. * @returns {string}
  41. */
  42. function initialise(token) {
  43. return typeof token === 'string' ? token : '';
  44. }
  45. /**
  46. * An Array reduce function that accumulates string length.
  47. */
  48. function accumulateLength(accumulator, element) {
  49. return accumulator + element.length;
  50. }
  51. /**
  52. * Encode the content portion of <code>url()</code> statements.
  53. * There are 6 capture groups in the split making every 7th unmatched.
  54. *
  55. * @param {string} element A single split item
  56. * @param {number} i The index of the item in the split
  57. * @param {Array} arr The array of split values
  58. * @returns {string} Every 3 or 5 items is an encoded url everything else is as is
  59. */
  60. function eachSplitOrGroup(element, i, arr) {
  61. // the content of the url() statement is either in group 3 or group 5
  62. var mod = i % 7;
  63. // only one of the capture groups 3 or 5 will match the other will be falsey
  64. if (element && ((mod === 3) || (mod === 5))) {
  65. // calculate the offset of the match from the front of the string
  66. var position = arr.slice(0, i - mod + 1).reduce(accumulateLength, 0);
  67. // detect quoted url and unescape backslashes
  68. var before = arr[i - 1],
  69. after = arr[i + 1],
  70. isQuoted = (before === after) && ((before === '\'') || (before === '"')),
  71. unescaped = isQuoted ? element.replace(/\\{2}/g, '\\') : element;
  72. // split into uri and query/hash and then determine if the uri is some type of file
  73. var split = unescaped.split(QUERY_REGEX),
  74. uri = split[0],
  75. query = split.slice(1).join(''),
  76. isRelative = testIsRelative(uri),
  77. isAbsolute = testIsAbsolute(uri);
  78. // file like URIs are processed but not all URIs are files
  79. if (isRelative || isAbsolute) {
  80. var bases = getPathsAtChar(position), // construct iterator as late as possible in case sourcemap invalid
  81. absolute = join({ uri, query, isAbsolute, bases });
  82. if (typeof absolute === 'string') {
  83. var relative = path.relative(directory, absolute)
  84. .replace(/\\/g, '/'); // #6 - backslashes are not legal in URI
  85. return loaderUtils.urlToRequest(relative + query);
  86. }
  87. }
  88. }
  89. // everything else, including parentheses and quotation (where present) and media statements
  90. return element;
  91. }
  92. };
  93. /**
  94. * The loaderUtils.isUrlRequest() doesn't support windows absolute paths on principle. We do not subscribe to that
  95. * dogma so we add path.isAbsolute() check to allow them.
  96. *
  97. * We also eliminate module relative (~) paths.
  98. *
  99. * @param {string|undefined} uri A uri string possibly empty or undefined
  100. * @return {boolean} True for relative uri
  101. */
  102. function testIsRelative(uri) {
  103. return !!uri && loaderUtils.isUrlRequest(uri, false) && !path.isAbsolute(uri) && (uri.indexOf('~') !== 0);
  104. }
  105. /**
  106. * The loaderUtils.isUrlRequest() doesn't support windows absolute paths on principle. We do not subscribe to that
  107. * dogma so we add path.isAbsolute() check to allow them.
  108. *
  109. * @param {string|undefined} uri A uri string possibly empty or undefined
  110. * @return {boolean} True for absolute uri
  111. */
  112. function testIsAbsolute(uri) {
  113. return !!uri && (typeof root === 'string') && loaderUtils.isUrlRequest(uri, root) &&
  114. (/^\//.test(uri) || path.isAbsolute(uri));
  115. }
  116. }
  117. module.exports = valueProcessor;