|
1 | 1 | const postcss = require('postcss');
|
2 |
| -const Tokenizer = require('css-selector-tokenizer'); |
3 |
| -const loaderUtils = require('loader-utils'); |
| 2 | +const valueParser = require('postcss-value-parser'); |
| 3 | +const { isUrlRequest } = require('loader-utils'); |
4 | 4 |
|
5 | 5 | const pluginName = 'postcss-url-parser';
|
6 | 6 |
|
| 7 | +function walkUrls(parsed, callback) { |
| 8 | + parsed.walk((node) => { |
| 9 | + if (node.type !== 'function' || node.value.toLowerCase() !== 'url') { |
| 10 | + return; |
| 11 | + } |
| 12 | + |
| 13 | + const url = |
| 14 | + node.nodes.length !== 0 && node.nodes[0].type === 'string' |
| 15 | + ? node.nodes[0].value |
| 16 | + : valueParser.stringify(node.nodes); |
| 17 | + |
| 18 | + /* eslint-disable */ |
| 19 | + node.before = ''; |
| 20 | + node.after = ''; |
| 21 | + /* eslint-enable */ |
| 22 | + |
| 23 | + if (url.trim().replace(/\\[\r\n]/g, '').length !== 0) { |
| 24 | + callback(node, url); |
| 25 | + } |
| 26 | + |
| 27 | + // Do not traverse inside url |
| 28 | + // eslint-disable-next-line consistent-return |
| 29 | + return false; |
| 30 | + }); |
| 31 | +} |
| 32 | + |
| 33 | +function filterUrls(parsed, filter) { |
| 34 | + const result = []; |
| 35 | + |
| 36 | + walkUrls(parsed, (node, content) => { |
| 37 | + if (!filter(content)) { |
| 38 | + return; |
| 39 | + } |
| 40 | + |
| 41 | + result.push(content); |
| 42 | + }); |
| 43 | + |
| 44 | + return result; |
| 45 | +} |
| 46 | + |
| 47 | +function walkDeclsWithUrl(css, filter) { |
| 48 | + const result = []; |
| 49 | + |
| 50 | + css.walkDecls((decl) => { |
| 51 | + if (!/url\(/i.test(decl.value)) { |
| 52 | + return; |
| 53 | + } |
| 54 | + |
| 55 | + const parsed = valueParser(decl.value); |
| 56 | + const values = filterUrls(parsed, filter); |
| 57 | + |
| 58 | + if (values.length === 0) { |
| 59 | + return; |
| 60 | + } |
| 61 | + |
| 62 | + result.push({ decl, parsed, values }); |
| 63 | + }); |
| 64 | + |
| 65 | + return result; |
| 66 | +} |
| 67 | + |
| 68 | +function flatten(array) { |
| 69 | + return array.reduce((acc, d) => [...acc, ...d], []); |
| 70 | +} |
| 71 | + |
| 72 | +function mapUrls(parsed, map) { |
| 73 | + walkUrls(parsed, (node, content) => { |
| 74 | + const placeholder = map(content); |
| 75 | + |
| 76 | + if (!placeholder) { |
| 77 | + return; |
| 78 | + } |
| 79 | + |
| 80 | + // eslint-disable-next-line no-param-reassign |
| 81 | + node.nodes = [{ type: 'word', value: map(content) }]; |
| 82 | + }); |
| 83 | +} |
| 84 | + |
7 | 85 | module.exports = postcss.plugin(
|
8 | 86 | pluginName,
|
9 | 87 | (options) =>
|
10 | 88 | function process(css) {
|
11 | 89 | const urlItems = [];
|
| 90 | + const traversed = walkDeclsWithUrl(css, (value) => isUrlRequest(value)); |
| 91 | + const paths = flatten(traversed.map((item) => item.values)); |
12 | 92 |
|
13 |
| - function processNode(item) { |
14 |
| - switch (item.type) { |
15 |
| - case 'value': |
16 |
| - item.nodes.forEach(processNode); |
17 |
| - break; |
18 |
| - case 'nested-item': |
19 |
| - item.nodes.forEach(processNode); |
20 |
| - break; |
21 |
| - case 'url': |
22 |
| - if ( |
23 |
| - item.url.replace(/\s/g, '').length && |
24 |
| - !/^#/.test(item.url) && |
25 |
| - loaderUtils.isUrlRequest(item.url) |
26 |
| - ) { |
27 |
| - // Strip quotes, they will be re-added if the module needs them |
28 |
| - /* eslint-disable no-param-reassign */ |
29 |
| - item.stringType = ''; |
30 |
| - delete item.innerSpacingBefore; |
31 |
| - delete item.innerSpacingAfter; |
32 |
| - const { url } = item; |
33 |
| - item.url = `___CSS_LOADER_URL___${urlItems.length}___`; |
34 |
| - /* eslint-enable no-param-reassign */ |
35 |
| - urlItems.push({ |
36 |
| - url, |
37 |
| - }); |
38 |
| - } |
39 |
| - break; |
40 |
| - // no default |
41 |
| - } |
| 93 | + if (paths.length === 0) { |
| 94 | + return; |
42 | 95 | }
|
43 | 96 |
|
44 |
| - css.walkDecls((decl) => { |
45 |
| - const values = Tokenizer.parseValues(decl.value); |
46 |
| - values.nodes.forEach((value) => { |
47 |
| - value.nodes.forEach(processNode); |
48 |
| - }); |
| 97 | + const urls = {}; |
| 98 | + |
| 99 | + paths.forEach((path, index) => { |
| 100 | + urls[path] = `___CSS_LOADER_URL___${index}___`; |
| 101 | + urlItems.push({ url: path }); |
| 102 | + }); |
| 103 | + |
| 104 | + traversed.forEach((item) => { |
| 105 | + mapUrls(item.parsed, (value) => urls[value]); |
49 | 106 | // eslint-disable-next-line no-param-reassign
|
50 |
| - decl.value = Tokenizer.stringifyValues(values); |
| 107 | + item.decl.value = item.parsed.toString(); |
51 | 108 | });
|
52 | 109 |
|
53 | 110 | // eslint-disable-next-line no-param-reassign
|
|
0 commit comments