Skip to content

Commit fdcf687

Browse files
fix: url resolving logic (#843)
BREAKING CHANGE: resolving logic works the same everywhere (it does not matter whether css modules are enabled or not or you setup `global` or `local` mode). Examples - `url('image.png')` as `require('./image.png')`, `url('./image.png')` as `require('./image.png')`, `url('~module/image.png')` as `require('module/image.png')`.
1 parent 889dc7f commit fdcf687

File tree

8 files changed

+1174
-52
lines changed

8 files changed

+1174
-52
lines changed

README.md

+21-15
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,18 @@ You can also use the css-loader results directly as a string, such as in Angular
5555

5656
**webpack.config.js**
5757
```js
58-
{
59-
   test: /\.css$/,
60-
   use: [
61-
     'to-string-loader',
62-
'css-loader'
63-
   ]
58+
module.exports = {
59+
module: {
60+
rules: [
61+
{
62+
test: /\.css$/,
63+
use: [
64+
  'to-string-loader',
65+
'css-loader'
66+
  ]
67+
}
68+
]
69+
}
6470
}
6571
```
6672

@@ -106,13 +112,20 @@ It's useful when you, for instance, need to post process the CSS as a string.
106112

107113
### `url`
108114

109-
To disable `url()` resolving by `css-loader` set the option to `false`.
115+
Enable/disable `url()` resolving. Absolute `urls` are not resolving by default.
110116

111-
To be compatible with existing css files (if not in CSS Module mode).
117+
Examples resolutions:
112118

113119
```
114120
url(image.png) => require('./image.png')
121+
url(./image.png) => require('./image.png')
122+
```
123+
124+
To import styles from a `node_modules` path (include `resolve.modules`) and for `alias`, prefix it with a `~`:
125+
126+
```
115127
url(~module/image.png) => require('module/image.png')
128+
url(~aliasDirectory/image.png) => require('otherDirectory/image.png')
116129
```
117130

118131
### `import`
@@ -184,13 +197,6 @@ exports.locals = {
184197

185198
CamelCase is recommended for local selectors. They are easier to use within the imported JS module.
186199

187-
`url()` URLs in block scoped (`:local .abc`) rules behave like requests in modules.
188-
189-
```
190-
file.png => ./file.png
191-
~module/file.png => module/file.png
192-
```
193-
194200
You can use `:local(#someId)`, but this is not recommended. Use classes instead of ids.
195201

196202
##### `Composing`

lib/loader.js

+7-21
Original file line numberDiff line numberDiff line change
@@ -72,25 +72,7 @@ module.exports = function loader(content, map, meta) {
7272

7373
plugins.push(
7474
modulesValues,
75-
localByDefault({
76-
mode,
77-
rewriteUrl(global, url) {
78-
if (resolveUrl) {
79-
// eslint-disable-next-line no-param-reassign
80-
url = url.trim();
81-
82-
if (!url.replace(/\s/g, '').length || !isUrlRequest(url)) {
83-
return url;
84-
}
85-
86-
if (global) {
87-
return urlToRequest(url);
88-
}
89-
}
90-
91-
return url;
92-
},
93-
}),
75+
localByDefault({ mode }),
9476
extractImports(),
9577
modulesScope({
9678
generateScopedName: function generateScopedName(exportName) {
@@ -117,7 +99,11 @@ module.exports = function loader(content, map, meta) {
11799
}
118100

119101
if (resolveUrl) {
120-
plugins.push(urlParser());
102+
plugins.push(
103+
urlParser({
104+
filter: (value) => isUrlRequest(value),
105+
})
106+
);
121107
}
122108

123109
plugins.push(icssParser());
@@ -251,7 +237,7 @@ module.exports = function loader(content, map, meta) {
251237

252238
return `" + escape(require(${stringifyRequest(
253239
this,
254-
normalizedUrl
240+
urlToRequest(normalizedUrl)
255241
)})${hash ? ` + ${hash}` : ''}) + "`;
256242
}
257243
);

lib/plugins/postcss-url-parser.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const postcss = require('postcss');
22
const valueParser = require('postcss-value-parser');
3-
const { isUrlRequest } = require('loader-utils');
43

54
const pluginName = 'postcss-url-parser';
65

@@ -97,11 +96,9 @@ function uniq(array) {
9796

9897
module.exports = postcss.plugin(
9998
pluginName,
100-
() =>
99+
(options) =>
101100
function process(css, result) {
102-
const traversed = walkDeclsWithUrl(css, result, (value) =>
103-
isUrlRequest(value)
104-
);
101+
const traversed = walkDeclsWithUrl(css, result, options.filter);
105102
const paths = uniq(flatten(traversed.map((item) => item.values)));
106103

107104
if (paths.length === 0) {

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

+1,088-8
Large diffs are not rendered by default.

test/fixtures/url/img-simple.png

76.3 KB
Loading

test/fixtures/url/url.css

+22-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
}
3636

3737
.class {
38-
background: green /* url(~package/img.png) */ url(./other-img.png) xyz;
38+
background: green url(~package/img.png) url(./other-img.png) xyz;
3939
}
4040

4141
.class {
@@ -82,7 +82,7 @@
8282
src: url(./font.woff) format('woff'),
8383
url('./font.woff2') format('woff2'),
8484
url("./font.eot") format('eot'),
85-
/*url(~package/font.ttf) format('truetype'), */
85+
url(~package/font.ttf) format('truetype'),
8686
url("./font with spaces.eot") format("embedded-opentype"),
8787
url('./font.svg#svgFontName') format('svg'),
8888
url('./font.woff2?foo=bar') format('woff2'),
@@ -177,3 +177,23 @@ b {
177177
background: ___CSS_LOADER_IMPORT___INDEX___;
178178
background: ___CSS_LOADER_IMPORT___99999___;
179179
}
180+
181+
.pure-url {
182+
background: url('img-simple.png');
183+
}
184+
185+
.not-resolved {
186+
background: url('/img-simple.png');
187+
}
188+
189+
.above-below {
190+
background: url('../url/img-simple.png');
191+
}
192+
193+
.tilde {
194+
background: url('~package/img.png');
195+
}
196+
197+
.aliases {
198+
background: url('~aliasesImg/img.png') ;
199+
}

test/helpers.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,13 @@ function evaluated(output, modules, moduleId = 1) {
5252
'modules/tests-cases/composes-with-importing',
5353
'modules/tests-cases/media-2',
5454
].map((importedPath) =>
55-
path.resolve(__dirname, `./fixtures/${importedPath}`, module)
55+
path.resolve(
56+
__dirname,
57+
`./fixtures/${importedPath}`,
58+
module.replace('aliasesImg/', '')
59+
)
5660
);
61+
5762
return importedPaths.includes(modulePath);
5863
});
5964

@@ -165,6 +170,11 @@ function compile(fixture, config = {}, options = {}) {
165170
optimization: {
166171
runtimeChunk: true,
167172
},
173+
resolve: {
174+
alias: {
175+
aliasesImg: path.resolve(__dirname, 'fixtures/url'),
176+
},
177+
},
168178
};
169179

170180
// Compiler Options

test/url-option.test.js

+23
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,27 @@ describe('url option', () => {
3131
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
3232
expect(stats.compilation.errors).toMatchSnapshot('errors');
3333
});
34+
35+
[true, 'local', 'global', false].forEach((modulesValue) => {
36+
it(`true and modules \`${modulesValue}\``, async () => {
37+
const config = {
38+
loader: { options: { modules: modulesValue } },
39+
};
40+
const testId = './url/url.css';
41+
const stats = await webpack(testId, config);
42+
const { modules } = stats.toJson();
43+
const module = modules.find((m) => m.id === testId);
44+
45+
expect(module.source).toMatchSnapshot('module');
46+
expect(evaluated(module.source, modules)).toMatchSnapshot(
47+
'module (evaluated)'
48+
);
49+
expect(normalizeErrors(stats.compilation.warnings)).toMatchSnapshot(
50+
'warnings'
51+
);
52+
expect(normalizeErrors(stats.compilation.errors)).toMatchSnapshot(
53+
'errors'
54+
);
55+
});
56+
});
3457
});

0 commit comments

Comments
 (0)