Skip to content

Commit 62fcfed

Browse files
feat(url): add url() filter support (options.url)
1 parent f4453d2 commit 62fcfed

File tree

7 files changed

+168
-29
lines changed

7 files changed

+168
-29
lines changed

src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default function loader(src, map, meta) {
5050

5151
// CSS URL Plugin
5252
if (options.url) {
53-
plugins.push(urls());
53+
plugins.push(urls(options));
5454
}
5555

5656
// CSS Import Plugin

src/lib/plugins/url.js

+55-27
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import valueParser from 'postcss-value-parser';
44
// ICSS {String}
55
// import { createICSSRules } from "icss-utils";
66

7-
const walkUrls = (parsed, callback) => {
7+
const walkUrls = (parsed, cb) => {
88
parsed.walk((node) => {
99
if (node.type === 'function' && node.value === 'url') {
1010
const content = node.nodes.length !== 0 && node.nodes[0].type === 'string'
1111
? node.nodes[0].value
1212
: valueParser.stringify(node.nodes);
1313

1414
if (content.trim().length !== 0) {
15-
callback(node, content);
15+
cb(node, content);
1616
}
1717

1818
// do not traverse inside url
@@ -22,30 +22,32 @@ const walkUrls = (parsed, callback) => {
2222
};
2323

2424
const mapUrls = (parsed, map) => {
25-
walkUrls(parsed, (node, content) => {
26-
node.nodes = [{ type: 'word', value: map(content) }];
25+
walkUrls(parsed, (node, url) => {
26+
node.nodes = [{ type: 'word', value: map(url) }];
2727
});
2828
};
2929

30-
const filterUrls = (parsed, filter) => {
30+
const filterUrls = (parsed, filter, options) => {
3131
const result = [];
3232

33-
walkUrls(parsed, (node, content) => {
34-
if (filter(content)) {
35-
result.push(content);
33+
walkUrls(parsed, (node, url) => {
34+
if (filter(url, options)) {
35+
return false
3636
}
37+
38+
return result.push(url);
3739
});
3840

3941
return result;
4042
};
4143

42-
const walkDeclsWithUrl = (css, filter) => {
44+
const walkDeclsWithUrl = (css, filter, options) => {
4345
const result = [];
4446

4547
css.walkDecls((decl) => {
4648
if (decl.value.includes('url(')) {
4749
const parsed = valueParser(decl.value);
48-
const values = filterUrls(parsed, filter);
50+
const values = filterUrls(parsed, filter, options);
4951

5052
if (values.length) {
5153
result.push({
@@ -60,31 +62,62 @@ const walkDeclsWithUrl = (css, filter) => {
6062
return result;
6163
};
6264

63-
const filterValues = value => !/^\w+:\/\//.test(value) &&
64-
!value.startsWith('//') &&
65-
!value.startsWith('#') &&
66-
!value.startsWith('data:');
65+
const URL = /^\w+:\/\//;
66+
67+
const filter = (url, options) => {
68+
if (URL.test(url)) {
69+
return true;
70+
}
71+
72+
if (url.startsWith('//')) {
73+
return true;
74+
}
75+
76+
if (url.startsWith('//')) {
77+
return true;
78+
}
79+
80+
if (url.startsWith('#')) {
81+
return true;
82+
}
83+
84+
if (url.startsWith('data:')) {
85+
return true;
86+
}
87+
88+
if (typeof options.url === 'string') {
89+
return url.includes(options.url);
90+
}
91+
92+
if (options.url instanceof RegExp) {
93+
return options.url.test(url);
94+
}
95+
96+
if (typeof options.url === 'function') {
97+
return options.url(url);
98+
}
99+
100+
return false;
101+
}
67102

68103
const flatten = arr => arr.reduce((acc, d) => [...acc, ...d], []);
69104
const uniq = arr => arr.reduce(
70105
(acc, d) => (acc.indexOf(d) === -1 ? [...acc, d] : acc),
71106
[],
72107
);
73108

74-
module.exports = postcss.plugin('postcss-icss-url', () => (css, result) => {
75-
const traversed = walkDeclsWithUrl(css, filterValues);
109+
module.exports = postcss.plugin('postcss-icss-url', (options) => (css, result) => {
110+
const traversed = walkDeclsWithUrl(css, filter, options);
76111
const paths = uniq(flatten(traversed.map(item => item.values)));
77112

78-
// ICSS imports {String}
113+
// ICSS Imports {String}
79114
const urls = {};
80115
const aliases = {};
81116

82117
paths.forEach((path, index) => {
83-
// ICSS Placeholder
118+
// (I)CSS Content Placeholder
84119
const alias = '${' + `CSS__URL__${index}` + '}';
85120

86-
// console.log(alias)
87-
88121
// ICSS {String}
89122
// imports[`'${path}'`] = {
90123
// [alias]: "default"
@@ -100,15 +133,10 @@ module.exports = postcss.plugin('postcss-icss-url', () => (css, result) => {
100133

101134
item.decl.value = item.parsed.toString();
102135
});
103-
136+
137+
// Add URLs to result.messages
104138
result.messages.push({ urls });
105139

106-
// result.messages.push({
107-
// type: 'dependency',
108-
// plugin: 'postcss-icss-url',
109-
// urls
110-
// })
111-
112140
// ICSS {String}
113141
// css.prepend(createICSSRules(imports, {}));
114142
});

src/options.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
"type": "object",
33
"properties": {
44
"url": {
5-
"type": "boolean"
5+
"anyOf": [
6+
{ "type": "string" },
7+
{ "type": "boolean" },
8+
{ "instanceof": "RegExp" },
9+
{ "instanceof": "Function" }
10+
]
611
},
712
"import": {
813
"type": "boolean"

test/fixtures/urls/filter/fixture.css

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.url {
2+
background: url('./file.png');
3+
background: url('./filter/file.png');
4+
}

test/fixtures/urls/filter/fixture.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import css from './fixture.css';

test/options/__snapshots__/url.test.js.snap

+51
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,54 @@ export default \`.url {
1515
}
1616
\`"
1717
`;
18+
19+
exports[`Options url {Function} 1`] = `
20+
"// CSS Imports
21+
22+
import CSS__URL__0 from './file.png';
23+
24+
// CSS Exports
25+
// CSS Modules Exports
26+
27+
28+
// CSS
29+
export default \`.url {
30+
background: url(\${CSS__URL__0});
31+
background: url('./filter/file.png');
32+
}
33+
\`"
34+
`;
35+
36+
exports[`Options url {RegExp} 1`] = `
37+
"// CSS Imports
38+
39+
import CSS__URL__0 from './file.png';
40+
41+
// CSS Exports
42+
// CSS Modules Exports
43+
44+
45+
// CSS
46+
export default \`.url {
47+
background: url(\${CSS__URL__0});
48+
background: url('./filter/file.png');
49+
}
50+
\`"
51+
`;
52+
53+
exports[`Options url {String} 1`] = `
54+
"// CSS Imports
55+
56+
import CSS__URL__0 from './file.png';
57+
58+
// CSS Exports
59+
// CSS Modules Exports
60+
61+
62+
// CSS
63+
export default \`.url {
64+
background: url(\${CSS__URL__0});
65+
background: url('./filter/file.png');
66+
}
67+
\`"
68+
`;

test/options/url.test.js

+50
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,55 @@ describe('Options', () => {
1818

1919
expect(source).toMatchSnapshot();
2020
});
21+
22+
test('{String}', async () => {
23+
const config = {
24+
loader: {
25+
test: /\.css$/,
26+
options: {
27+
url: 'filter'
28+
},
29+
},
30+
};
31+
32+
const stats = await webpack('urls/filter/fixture.js', config);
33+
const { source } = stats.toJson().modules[1];
34+
35+
expect(source).toMatchSnapshot();
36+
});
37+
38+
test('{RegExp}', async () => {
39+
const config = {
40+
loader: {
41+
test: /\.css$/,
42+
options: {
43+
url: /filter/
44+
},
45+
},
46+
};
47+
48+
const stats = await webpack('urls/filter/fixture.js', config);
49+
const { source } = stats.toJson().modules[1];
50+
51+
expect(source).toMatchSnapshot();
52+
});
53+
54+
test('{Function}', async () => {
55+
const config = {
56+
loader: {
57+
test: /\.css$/,
58+
options: {
59+
url (url) {
60+
return /filter/.test(url);
61+
}
62+
},
63+
},
64+
};
65+
66+
const stats = await webpack('urls/filter/fixture.js', config);
67+
const { source } = stats.toJson().modules[1];
68+
69+
expect(source).toMatchSnapshot();
70+
});
2171
});
2272
});

0 commit comments

Comments
 (0)