Skip to content

Commit d2332f9

Browse files
feat(plugins/url): add url() filter support (options.url) (webpack-contrib#655)
1 parent b479984 commit d2332f9

File tree

15 files changed

+239
-97
lines changed

15 files changed

+239
-97
lines changed

README.md

+27
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export default `
7474
}
7575
`
7676
```
77+
7778
#### `{Boolean}`
7879

7980
To disable `url()` resolving by `css-loader` set the option to `false`
@@ -88,6 +89,32 @@ To disable `url()` resolving by `css-loader` set the option to `false`
8889
}
8990
```
9091

92+
#### `{RegExp}`
93+
94+
**webpack.config.js**
95+
```js
96+
{
97+
loader: 'css-loader',
98+
options: {
99+
url: /filter/
100+
}
101+
}
102+
```
103+
104+
#### `{Function}`
105+
106+
**webpack.config.js**
107+
```js
108+
{
109+
loader: 'css-loader',
110+
options: {
111+
url (url) {
112+
return /filter/.test(url)
113+
}
114+
}
115+
}
116+
```
117+
91118
### `import`
92119

93120
```css

src/index.js

+40-43
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { getOptions } from 'loader-utils';
99
import validateOptions from 'schema-utils';
1010

1111
import postcss from 'postcss';
12-
// TODO(michael-ciniawsky)
12+
// TODO(michael-ciniawsky)
1313
// replace with postcss-icss-{url, import}
1414
import urls from './plugins/url';
1515
import imports from './plugins/import';
@@ -26,15 +26,12 @@ const DEFAULTS = {
2626
sourceMap: false,
2727
};
2828

29-
export default function loader (css, map, meta) {
29+
export default function loader(css, map, meta) {
3030
// Loader Options
31-
const options = Object.assign(
32-
{},
33-
DEFAULTS,
34-
getOptions(this)
35-
);
31+
const options = Object.assign({}, DEFAULTS, getOptions(this));
3632

3733
validateOptions(schema, options, 'CSS Loader');
34+
3835
// Loader Mode (Async)
3936
const cb = this.async();
4037
const file = this.resourcePath;
@@ -47,14 +44,13 @@ export default function loader (css, map, meta) {
4744
map = false;
4845
}
4946

50-
// CSS Plugins
5147
const plugins = [];
5248

5349
// URL Plugin
5450
if (options.url) {
55-
plugins.push(urls());
51+
plugins.push(urls(options));
5652
}
57-
53+
5854
// Import Plugin
5955
if (options.import) {
6056
plugins.push(imports());
@@ -64,7 +60,7 @@ export default function loader (css, map, meta) {
6460
if (options.minimize) {
6561
plugins.push(minifier());
6662
}
67-
63+
6864
if (meta) {
6965
const { ast } = meta;
7066
// Reuse CSS AST (PostCSS AST e.g 'postcss-loader')
@@ -73,34 +69,33 @@ export default function loader (css, map, meta) {
7369
css = ast.root;
7470
}
7571
}
76-
77-
map = options.sourceMap
72+
73+
map = options.sourceMap
7874
? {
79-
prev: map || false,
80-
inline: false,
81-
annotation: false,
82-
sourcesContent: true,
83-
}
84-
: false
85-
75+
prev: map || false,
76+
inline: false,
77+
annotation: false,
78+
sourcesContent: true,
79+
}
80+
: false;
81+
8682
return postcss(plugins)
8783
.process(css, {
8884
from: `/css-loader!${file}`,
8985
map,
9086
to: file,
91-
}).then(({ css, map, messages }) => {
87+
})
88+
.then(({ css, map, messages }) => {
9289
if (meta && meta.messages) {
93-
messages = messages.concat(meta.messages)
90+
messages = messages.concat(meta.messages);
9491
}
95-
92+
9693
// CSS Imports
9794
const imports = messages
98-
.filter((msg) => msg.type === 'import' ? msg : false)
95+
.filter((msg) => (msg.type === 'import' ? msg : false))
9996
.reduce((imports, msg) => {
10097
try {
101-
msg = typeof msg.import === 'function'
102-
? msg.import()
103-
: msg.import;
98+
msg = typeof msg.import === 'function' ? msg.import() : msg.import;
10499

105100
imports += msg;
106101
} catch (err) {
@@ -109,17 +104,15 @@ export default function loader (css, map, meta) {
109104
this.emitError(err);
110105
}
111106

112-
return imports
113-
}, '')
114-
107+
return imports;
108+
}, '');
109+
115110
// CSS Exports
116111
const exports = messages
117-
.filter((msg) => msg.type === 'export' ? msg : false)
118-
.reduce((exports, msg) => {
119-
try {
120-
msg = typeof msg.export === 'function'
121-
? msg.export()
122-
: msg.export;
112+
.filter((msg) => (msg.type === 'export' ? msg : false))
113+
.reduce((exports, msg) => {
114+
try {
115+
msg = typeof msg.export === 'function' ? msg.export() : msg.export;
123116

124117
exports += msg;
125118
} catch (err) {
@@ -129,23 +122,27 @@ export default function loader (css, map, meta) {
129122
}
130123

131124
return exports;
132-
}, '')
133-
134-
// TODO(michael-ciniawsky)
125+
}, '');
126+
127+
// TODO(michael-ciniawsky)
135128
// triage if and add CSS runtime back
136129
const result = [
137130
imports ? `// CSS Imports\n${imports}\n` : false,
138131
exports ? `// CSS Exports\n${exports}\n` : false,
139-
`// CSS\nexport default \`${css}\``
132+
`// CSS\nexport default \`${css}\``,
140133
]
141134
.filter(Boolean)
142135
.join('\n');
143-
136+
144137
cb(null, result, map ? map.toJSON() : null);
145138

146139
return null;
147140
})
148141
.catch((err) => {
149-
err.name === 'CssSyntaxError' ? cb(new SyntaxError(err)) : cb(err);
142+
err = err.name === 'CssSyntaxError' ? new SyntaxError(err) : err;
143+
144+
cb(err);
145+
146+
return null;
150147
});
151-
};
148+
}

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"

src/plugins/url.js

+51-22
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
/* eslint-disable */
22
import postcss from 'postcss';
33
import valueParser from 'postcss-value-parser';
4-
// ICSS {String}
5-
// import { createICSSRules } from "icss-utils";
64

7-
const walkUrls = (parsed, callback) => {
5+
const walkUrls = (parsed, cb) => {
86
parsed.walk((node) => {
97
if (node.type === 'function' && node.value === 'url') {
108
const content = node.nodes.length !== 0 && node.nodes[0].type === 'string'
119
? node.nodes[0].value
1210
: valueParser.stringify(node.nodes);
1311

1412
if (content.trim().length !== 0) {
15-
callback(node, content);
13+
cb(node, content);
1614
}
1715

1816
// do not traverse inside url
@@ -22,30 +20,32 @@ const walkUrls = (parsed, callback) => {
2220
};
2321

2422
const mapUrls = (parsed, map) => {
25-
walkUrls(parsed, (node, content) => {
26-
node.nodes = [{ type: 'word', value: map(content) }];
23+
walkUrls(parsed, (node, url) => {
24+
node.nodes = [{ type: 'word', value: map(url) }];
2725
});
2826
};
2927

30-
const filterUrls = (parsed, filter) => {
28+
const filterUrls = (parsed, filter, options) => {
3129
const result = [];
3230

33-
walkUrls(parsed, (node, content) => {
34-
if (filter(content)) {
35-
result.push(content);
31+
walkUrls(parsed, (node, url) => {
32+
if (filter(url, options)) {
33+
return false
3634
}
35+
36+
return result.push(url);
3737
});
3838

3939
return result;
4040
};
4141

42-
const walkDeclsWithUrl = (css, filter) => {
42+
const walkDeclsWithUrl = (css, filter, options) => {
4343
const result = [];
4444

4545
css.walkDecls((decl) => {
4646
if (decl.value.includes('url(')) {
4747
const parsed = valueParser(decl.value);
48-
const values = filterUrls(parsed, filter);
48+
const values = filterUrls(parsed, filter, options);
4949

5050
if (values.length) {
5151
result.push({
@@ -60,10 +60,39 @@ const walkDeclsWithUrl = (css, filter) => {
6060
return result;
6161
};
6262

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

6897
const flatten = arr => arr.reduce((acc, d) => [...acc, ...d], []);
6998

@@ -72,24 +101,23 @@ const uniq = arr => arr.reduce(
72101
[],
73102
);
74103

75-
module.exports = postcss.plugin('postcss-icss-url', () => (css, result) => {
76-
const traversed = walkDeclsWithUrl(css, filterValues);
104+
module.exports = postcss.plugin('postcss-icss-url', (options) => (css, result) => {
105+
const traversed = walkDeclsWithUrl(css, filter, options);
77106
const paths = uniq(flatten(traversed.map(item => item.values)));
78107

79-
// ICSS imports {String}
80108
const aliases = {};
81109

82110
paths.forEach((url, idx) => {
83-
// ICSS Placeholder
111+
// CSS Content Placeholder
84112
const alias = '${' + `CSS__URL__${idx}` + '}';
85-
113+
86114
aliases[url] = alias;
87115

88116
result.messages.push({
89117
type: 'import',
90118
plugin: 'postcss-icss-url',
91119
import: `import CSS__URL__${idx} from '${url}';\n`
92-
})
120+
});
93121
});
94122

95123
traversed.forEach((item) => {
@@ -98,3 +126,4 @@ module.exports = postcss.plugin('postcss-icss-url', () => (css, result) => {
98126
item.decl.value = item.parsed.toString();
99127
});
100128
});
129+

test/fixtures/imports/fixture.js

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
import css from './fixture.css';
2+
3+
export default css;

test/fixtures/urls/file.css

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

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

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

test/fixtures/urls/fixture.js

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
import css from './fixture.css';
2+
3+
export default css;

test/loader.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('Loader', () => {
1414

1515
const stats = await webpack('fixture.js', config);
1616
const { source } = stats.toJson().modules[1];
17-
17+
1818
expect(source).toMatchSnapshot();
1919
});
2020
});

0 commit comments

Comments
 (0)