postcss-url-parser.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _postcss = _interopRequireDefault(require("postcss"));
  7. var _postcssValueParser = _interopRequireDefault(require("postcss-value-parser"));
  8. var _utils = require("../utils");
  9. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  10. const pluginName = 'postcss-url-parser';
  11. const isUrlFunc = /url/i;
  12. const isImageSetFunc = /^(?:-webkit-)?image-set$/i;
  13. const needParseDecl = /(?:url|(?:-webkit-)?image-set)\(/i;
  14. function getNodeFromUrlFunc(node) {
  15. return node.nodes && node.nodes[0];
  16. }
  17. function walkUrls(parsed, callback) {
  18. parsed.walk(node => {
  19. if (node.type !== 'function') {
  20. return;
  21. }
  22. if (isUrlFunc.test(node.value)) {
  23. const {
  24. nodes
  25. } = node;
  26. const isStringValue = nodes.length !== 0 && nodes[0].type === 'string';
  27. const url = isStringValue ? nodes[0].value : _postcssValueParser.default.stringify(nodes);
  28. callback(getNodeFromUrlFunc(node), url, false, isStringValue); // Do not traverse inside `url`
  29. // eslint-disable-next-line consistent-return
  30. return false;
  31. }
  32. if (isImageSetFunc.test(node.value)) {
  33. node.nodes.forEach(nNode => {
  34. const {
  35. type,
  36. value
  37. } = nNode;
  38. if (type === 'function' && isUrlFunc.test(value)) {
  39. const {
  40. nodes
  41. } = nNode;
  42. const isStringValue = nodes.length !== 0 && nodes[0].type === 'string';
  43. const url = isStringValue ? nodes[0].value : _postcssValueParser.default.stringify(nodes);
  44. callback(getNodeFromUrlFunc(nNode), url, false, isStringValue);
  45. }
  46. if (type === 'string') {
  47. callback(nNode, value, true, true);
  48. }
  49. }); // Do not traverse inside `image-set`
  50. // eslint-disable-next-line consistent-return
  51. return false;
  52. }
  53. });
  54. }
  55. function getUrlsFromValue(value, result, filter, decl) {
  56. if (!needParseDecl.test(value)) {
  57. return;
  58. }
  59. const parsed = (0, _postcssValueParser.default)(value);
  60. const urls = [];
  61. walkUrls(parsed, (node, url, needQuotes, isStringValue) => {
  62. if (url.trim().replace(/\\[\r\n]/g, '').length === 0) {
  63. result.warn(`Unable to find uri in '${decl ? decl.toString() : value}'`, {
  64. node: decl
  65. });
  66. return;
  67. }
  68. if (filter && !filter(url)) {
  69. return;
  70. }
  71. const splittedUrl = url.split(/(\?)?#/);
  72. const [urlWithoutHash, singleQuery, hashValue] = splittedUrl;
  73. const hash = singleQuery || hashValue ? `${singleQuery ? '?' : ''}${hashValue ? `#${hashValue}` : ''}` : '';
  74. const normalizedUrl = (0, _utils.normalizeUrl)(urlWithoutHash, isStringValue);
  75. urls.push({
  76. node,
  77. url: normalizedUrl,
  78. hash,
  79. needQuotes
  80. });
  81. }); // eslint-disable-next-line consistent-return
  82. return {
  83. parsed,
  84. urls
  85. };
  86. }
  87. function walkDecls(css, result, filter) {
  88. const items = [];
  89. css.walkDecls(decl => {
  90. const item = getUrlsFromValue(decl.value, result, filter, decl);
  91. if (!item || item.urls.length === 0) {
  92. return;
  93. }
  94. items.push({
  95. decl,
  96. parsed: item.parsed,
  97. urls: item.urls
  98. });
  99. });
  100. return items;
  101. }
  102. function flatten(array) {
  103. return array.reduce((a, b) => a.concat(b), []);
  104. }
  105. function collectUniqueUrlsWithNodes(array) {
  106. return array.reduce((accumulator, currentValue) => {
  107. const {
  108. url,
  109. needQuotes,
  110. hash,
  111. node
  112. } = currentValue;
  113. const found = accumulator.find(item => url === item.url && needQuotes === item.needQuotes && hash === item.hash);
  114. if (!found) {
  115. accumulator.push({
  116. url,
  117. hash,
  118. needQuotes,
  119. nodes: [node]
  120. });
  121. } else {
  122. found.nodes.push(node);
  123. }
  124. return accumulator;
  125. }, []);
  126. }
  127. var _default = _postcss.default.plugin(pluginName, options => function process(css, result) {
  128. const traversed = walkDecls(css, result, options.filter);
  129. const flattenTraversed = flatten(traversed.map(item => item.urls));
  130. const urlsWithNodes = collectUniqueUrlsWithNodes(flattenTraversed);
  131. const replacers = new Map();
  132. urlsWithNodes.forEach((urlWithNodes, index) => {
  133. const {
  134. url,
  135. hash,
  136. needQuotes,
  137. nodes
  138. } = urlWithNodes;
  139. const replacementName = `___CSS_LOADER_URL_REPLACEMENT_${index}___`;
  140. result.messages.push({
  141. pluginName,
  142. type: 'import',
  143. value: {
  144. type: 'url',
  145. replacementName,
  146. url,
  147. needQuotes,
  148. hash
  149. }
  150. }, {
  151. pluginName,
  152. type: 'replacer',
  153. value: {
  154. type: 'url',
  155. replacementName
  156. }
  157. });
  158. nodes.forEach(node => {
  159. replacers.set(node, replacementName);
  160. });
  161. });
  162. traversed.forEach(item => {
  163. walkUrls(item.parsed, node => {
  164. const replacementName = replacers.get(node);
  165. if (!replacementName) {
  166. return;
  167. } // eslint-disable-next-line no-param-reassign
  168. node.type = 'word'; // eslint-disable-next-line no-param-reassign
  169. node.value = replacementName;
  170. }); // eslint-disable-next-line no-param-reassign
  171. item.decl.value = item.parsed.toString();
  172. });
  173. });
  174. exports.default = _default;