Skip to content

Commit eef3c26

Browse files
refactor: postcss-url-parser (#820)
1 parent 9f66e33 commit eef3c26

File tree

9 files changed

+295
-68
lines changed

9 files changed

+295
-68
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-parser.js

+16-18
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,24 @@ module.exports = postcss.plugin(
88
'postcss-parser',
99
(options) =>
1010
function process(css) {
11-
const imports = {};
12-
let exports = {};
1311
const importItems = options.importItems || [];
1412
const urlItems = options.urlItems || [];
13+
const icss = icssUtils.extractICSS(css);
14+
15+
const imports = {};
16+
const exports = icss.icssExports;
17+
18+
Object.keys(icss.icssImports).forEach((key) => {
19+
const url = loaderUtils.parseString(key);
20+
21+
Object.keys(icss.icssImports[key]).forEach((prop) => {
22+
imports[`$${prop}`] = importItems.length;
23+
importItems.push({
24+
url,
25+
export: icss.icssImports[key][prop],
26+
});
27+
});
28+
});
1529

1630
function replaceImportsInString(str) {
1731
if (options.import) {
@@ -36,22 +50,6 @@ module.exports = postcss.plugin(
3650
return str;
3751
}
3852

39-
const icss = icssUtils.extractICSS(css);
40-
41-
exports = icss.icssExports;
42-
43-
Object.keys(icss.icssImports).forEach((key) => {
44-
const url = loaderUtils.parseString(key);
45-
46-
Object.keys(icss.icssImports[key]).forEach((prop) => {
47-
imports[`$${prop}`] = importItems.length;
48-
importItems.push({
49-
url,
50-
export: icss.icssImports[key][prop],
51-
});
52-
});
53-
});
54-
5553
Object.keys(exports).forEach((exportName) => {
5654
exports[exportName] = replaceImportsInString(exports[exportName]);
5755
});

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

0 commit comments

Comments
 (0)