Skip to content

Commit 5a8d921

Browse files
refactor: postcss-url-parser
1 parent 9f66e33 commit 5a8d921

File tree

6 files changed

+119
-50
lines changed

6 files changed

+119
-50
lines changed

lib/plugins/postcss-import-parser.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -50,31 +50,31 @@ module.exports = postcss.plugin(
5050
function process(css, result) {
5151
const importItems = [];
5252

53-
css.walkAtRules(/^import$/i, (atrule) => {
53+
css.walkAtRules(/^import$/i, (atRule) => {
5454
// Convert only top-level @import
55-
if (atrule.parent.type !== 'root') {
55+
if (atRule.parent.type !== 'root') {
5656
return;
5757
}
5858

59-
if (atrule.nodes) {
59+
if (atRule.nodes) {
6060
result.warn(
6161
"It looks like you didn't end your @import statement correctly. " +
6262
'Child nodes are attached to it.',
63-
{ node: atrule }
63+
{ node: atRule }
6464
);
6565
return;
6666
}
6767

68-
const parsed = parseImport(atrule.params);
68+
const parsed = parseImport(atRule.params);
6969

7070
if (!parsed) {
7171
// eslint-disable-next-line consistent-return
72-
return result.warn(`Unable to find uri in '${atrule.toString()}'`, {
73-
node: atrule,
72+
return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
73+
node: atRule,
7474
});
7575
}
7676

77-
atrule.remove();
77+
atRule.remove();
7878

7979
const { media } = parsed;
8080
let { url } = parsed;

lib/plugins/postcss-url-parser.js

+94-37
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,110 @@
11
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');
44

55
const pluginName = 'postcss-url-parser';
66

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+
785
module.exports = postcss.plugin(
886
pluginName,
987
(options) =>
1088
function process(css) {
1189
const urlItems = [];
90+
const traversed = walkDeclsWithUrl(css, (value) => isUrlRequest(value));
91+
const paths = flatten(traversed.map((item) => item.values));
1292

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;
4295
}
4396

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]);
49106
// eslint-disable-next-line no-param-reassign
50-
decl.value = Tokenizer.stringifyValues(values);
107+
item.decl.value = item.parsed.toString();
51108
});
52109

53110
// eslint-disable-next-line no-param-reassign

test/__snapshots__/modules-option.test.js.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -1520,7 +1520,7 @@ Array [
15201520
background: url(data:image/png;base64,AAA);
15211521
background: url(http://example.com/image.jpg);
15221522
background: url(//example.com/image.png);
1523-
background: green url(data:image/png;base64,AAA) url(http://example.com/image.jpg) url(//example.com/image.png) url(/webpack/public/path/img.png) xyz;
1523+
background: green url(data:image/png;base64,AAA) url(http://example.com/image.jpg) url(//example.com/image.png) url(/webpack/public/path/img.png) url(\\"/webpack/public/path/img img.png\\") xyz;
15241524
}
15251525
",
15261526
"",
@@ -1554,7 +1554,7 @@ Array [
15541554
background: url(data:image/png;base64,AAA);
15551555
background: url(http://example.com/image.jpg);
15561556
background: url(//example.com/image.png);
1557-
background: green url(data:image/png;base64,AAA) url(http://example.com/image.jpg) url(//example.com/image.png) url(/webpack/public/path/img.png) xyz;
1557+
background: green url(data:image/png;base64,AAA) url(http://example.com/image.jpg) url(//example.com/image.png) url(/webpack/public/path/img.png) url(\\"/webpack/public/path/img img.png\\") xyz;
15581558
}
15591559
",
15601560
"",

0 commit comments

Comments
 (0)