Skip to content

Commit aa3918a

Browse files
refactor: loader
1 parent 186f5b6 commit aa3918a

File tree

3 files changed

+97
-110
lines changed

3 files changed

+97
-110
lines changed

lib/loader.js

+70-100
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,54 @@
11
/*
2-
MIT License http://www.opensource.org/licenses/mit-license.php
3-
Author Tobias Koppers @sokra
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
44
*/
5-
var loaderUtils = require("loader-utils");
6-
var postcss = require("postcss");
7-
var plugin = require("./plugin");
8-
var getImportPrefix = require("./getImportPrefix");
9-
var CssLoaderError = require("./CssLoaderError");
5+
const loaderUtils = require("loader-utils");
6+
const postcss = require("postcss");
7+
const plugin = require("./plugin");
8+
const getImportPrefix = require("./getImportPrefix");
9+
const CssLoaderError = require("./CssLoaderError");
1010

1111
module.exports = function(content, map) {
12-
var callback = this.async();
13-
var query = loaderUtils.getOptions(this) || {};
14-
var sourceMap = query.sourceMap || false;
15-
var loaderContext = this;
16-
17-
if (sourceMap) {
18-
if (map) {
19-
if (typeof map === "string") {
20-
map = JSON.stringify(map);
21-
}
12+
const options = loaderUtils.getOptions(this) || {};
2213

23-
if (map.sources) {
24-
map.sources = map.sources.map(function(source) {
25-
return source.replace(/\\/g, "/");
26-
});
27-
map.sourceRoot = "";
28-
}
14+
// Todo validate options
15+
16+
const cb = this.async();
17+
const sourceMap = options.sourceMap;
18+
19+
var parserOptions = {
20+
url: options.url !== false,
21+
import: options.import !== false
22+
};
23+
24+
if (sourceMap && map) {
25+
if (typeof map === "string") {
26+
map = JSON.parse(map);
27+
}
28+
29+
if (map.sources) {
30+
map.sources = map.sources.map(source => source.replace(/\\/g, "/"));
31+
map.sourceRoot = "";
2932
}
3033
} else {
3134
// Some loaders (example `"postcss-loader": "1.x.x"`) always generates source map, we should remove it
3235
map = null;
3336
}
3437

35-
var parserOptions = {
36-
url: query.url !== false,
37-
import: query.import !== false,
38-
resolve: loaderContext.resolve
39-
};
40-
38+
// We need a prefix to avoid path rewriting of PostCSS
4139
const from =
4240
"/css-loader!" +
4341
loaderUtils
44-
.getRemainingRequest(loaderContext)
42+
.getRemainingRequest(this)
4543
.split("!")
4644
.pop();
4745
const to = loaderUtils
48-
.getCurrentRequest(loaderContext)
46+
.getCurrentRequest(this)
4947
.split("!")
5048
.pop();
5149

5250
postcss([plugin(parserOptions)])
5351
.process(content, {
54-
// we need a prefix to avoid path rewriting of PostCSS
5552
from,
5653
to,
5754
map: sourceMap
@@ -63,14 +60,14 @@ module.exports = function(content, map) {
6360
}
6461
: null
6562
})
66-
.then(function(result) {
63+
.then(result => {
6764
var cssAsString = JSON.stringify(result.css);
68-
var importItems = parserOptions.importItems;
6965

70-
if (query.import !== false && importItems.length > 0) {
66+
if (options.import !== false) {
7167
var alreadyImported = {};
72-
var importJs = importItems
73-
.filter(function(imp) {
68+
var importJs = result.messages
69+
.filter(message => message.type === "at-rule-import")
70+
.filter(imp => {
7471
if (!imp.mediaQuery) {
7572
if (alreadyImported[imp.url]) {
7673
return false;
@@ -81,7 +78,7 @@ module.exports = function(content, map) {
8178

8279
return true;
8380
})
84-
.map(function(imp) {
81+
.map(imp => {
8582
if (!loaderUtils.isUrlRequest(imp.url)) {
8683
return (
8784
"exports.push([module.id, " +
@@ -93,7 +90,7 @@ module.exports = function(content, map) {
9390
}
9491

9592
// for importing CSS
96-
var importUrlPrefix = getImportPrefix(loaderContext, query);
93+
var importUrlPrefix = getImportPrefix(this, options);
9794
var importUrl = importUrlPrefix + imp.url;
9895

9996
return (
@@ -103,61 +100,36 @@ module.exports = function(content, map) {
103100
JSON.stringify(imp.mediaQuery) +
104101
");"
105102
);
106-
}, loaderContext)
103+
})
107104
.join("\n");
108105
}
109106

110-
// helper for ensuring valid CSS strings from requires
111-
var urlEscapeHelper = "";
112-
var urlItems = parserOptions.urlItems;
107+
// Helper for ensuring valid CSS strings from requires
108+
let urlEscapeHelper = "";
113109

114-
if (query.url !== false && urlItems.length > 0) {
110+
if (options.url !== false) {
115111
urlEscapeHelper =
116112
"var runtimeEscape = require(" +
117113
loaderUtils.stringifyRequest(
118-
loaderContext,
114+
this,
119115
require.resolve("./runtimeEscape.js")
120116
) +
121117
");\n";
122118

123-
cssAsString = cssAsString.replace(
124-
/___CSS_LOADER_URL___([0-9]+)___/g,
125-
function(item) {
126-
var match = /___CSS_LOADER_URL___([0-9]+)___/.exec(item);
127-
var idx = +match[1];
128-
var urlItem = urlItems[idx];
129-
var url = urlItem.url;
130-
131-
idx = url.indexOf("?#");
132-
133-
if (idx < 0) {
134-
idx = url.indexOf("#");
135-
}
136-
137-
var urlRequest;
138-
139-
if (idx > 0) {
140-
// idx === 0 is catched by isUrlRequest
141-
// in cases like url('webfont.eot?#iefix')
142-
urlRequest = url.substr(0, idx);
143-
144-
return (
145-
'" + runtimeEscape(require(' +
146-
loaderUtils.stringifyRequest(loaderContext, urlRequest) +
147-
')) + "' +
148-
url.substr(idx)
149-
);
150-
}
151-
152-
urlRequest = url;
153-
154-
return (
119+
result.messages
120+
.filter(message => message.type === "css-loader-import-url")
121+
.forEach(message => {
122+
const { placeholder, url } = message;
123+
const splittedURL = url.split(/(\?)?#/);
124+
const importURLString =
155125
'" + runtimeEscape(require(' +
156-
loaderUtils.stringifyRequest(loaderContext, urlRequest) +
157-
')) + "'
158-
);
159-
}
160-
);
126+
loaderUtils.stringifyRequest(this, splittedURL[0]) +
127+
')) + "' +
128+
(splittedURL[1] ? splittedURL[1] : "") +
129+
(splittedURL[2] ? `#${splittedURL[2]}` : "");
130+
131+
cssAsString = cssAsString.replace(placeholder, importURLString);
132+
});
161133
}
162134

163135
// Todo need save backward compatibility with old `style-loader`
@@ -170,16 +142,15 @@ module.exports = function(content, map) {
170142
var moduleJs;
171143

172144
if (sourceMap && result.map) {
173-
// add a SourceMap
174145
map = result.map.toJSON();
175146

176147
if (map.sources) {
177-
map.sources = map.sources.map(function(source) {
178-
return source
148+
map.sources = map.sources.map(source =>
149+
source
179150
.split("!")
180151
.pop()
181-
.replace(/\\/g, "/");
182-
}, loaderContext);
152+
.replace(/\\/g, "/")
153+
);
183154
map.sourceRoot = "";
184155
}
185156

@@ -196,14 +167,11 @@ module.exports = function(content, map) {
196167
}
197168

198169
// embed runtime
199-
callback(
170+
cb(
200171
null,
201172
urlEscapeHelper +
202173
"exports = module.exports = require(" +
203-
loaderUtils.stringifyRequest(
204-
loaderContext,
205-
require.resolve("./runtime.js")
206-
) +
174+
loaderUtils.stringifyRequest(this, require.resolve("./runtime.js")) +
207175
")(" +
208176
sourceMap +
209177
");\n" +
@@ -217,18 +185,20 @@ module.exports = function(content, map) {
217185
exportJs
218186
);
219187
})
220-
.catch(function(error) {
221-
callback(
222-
error.name === "CssSyntaxError"
188+
.catch(err => {
189+
// Todo if (err.file) this.addDependency(err.file)
190+
191+
cb(
192+
err.name === "CssSyntaxError"
223193
? new CssLoaderError(
224194
"Syntax Error",
225-
error.reason,
226-
error.line != null && error.column != null
227-
? { line: error.line, column: error.column }
195+
err.reason,
196+
err.line != null && err.column != null
197+
? { line: err.line, column: err.column }
228198
: null,
229-
error.input.source
199+
err.input.source
230200
)
231-
: error
201+
: err
232202
);
233203
});
234204
};

lib/plugin.js

+18-10
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ const postcss = require("postcss");
22
const valueParser = require("postcss-value-parser");
33
const loaderUtils = require("loader-utils");
44

5-
module.exports = postcss.plugin("css-loader-parser", function(options) {
6-
return function(css) {
7-
const importItems = [];
8-
const urlItems = [];
5+
const pluginName = "postcss-css-loader";
96

7+
module.exports = postcss.plugin(pluginName, function(options) {
8+
return function(css, result) {
109
if (options.import) {
1110
css.walkAtRules(/^import$/i, function(rule) {
1211
const parsedValue = valueParser(rule.params);
@@ -44,7 +43,9 @@ module.exports = postcss.plugin("css-loader-parser", function(options) {
4443
.stringify(parsedValue.nodes.slice(1))
4544
.trim();
4645

47-
importItems.push({
46+
result.messages.push({
47+
pluginName,
48+
type: "at-rule-import",
4849
url: url,
4950
mediaQuery: mediaQuery
5051
});
@@ -54,6 +55,8 @@ module.exports = postcss.plugin("css-loader-parser", function(options) {
5455
}
5556

5657
if (options.url) {
58+
let index = 0;
59+
5760
css.walkDecls(decl => {
5861
if (!/url\(/i.test(decl.value)) {
5962
return decl;
@@ -89,22 +92,27 @@ module.exports = postcss.plugin("css-loader-parser", function(options) {
8992
node.after = "";
9093

9194
const requestedURL = loaderUtils.urlToRequest(URLValue);
95+
const placeholder = "___CSS_LOADER_IMPORT_URL_PLACEHOLDER___" + index + "___";
9296

97+
URLNode.value = placeholder;
9398
// Strip quotes, they will be re-added if the module needs them
9499
URLNode.quote = "";
95-
URLNode.value = "___CSS_LOADER_URL___" + urlItems.length + "___";
96100

97-
urlItems.push({
101+
result.messages.push({
102+
pluginName,
103+
type: "css-loader-import-url",
104+
placeholder: placeholder,
98105
url: requestedURL
99106
});
107+
108+
index++;
109+
110+
return false;
100111
})
101112
.toString();
102113

103114
return decl;
104115
});
105116
}
106-
107-
options.importItems = importItems;
108-
options.urlItems = urlItems;
109117
};
110118
});

test/urlTest.js

+9
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ describe("url", function() {
9191
test("external schema-less url", ".class { background: green url(//raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz }", [
9292
[1, ".class { background: green url(//raw.githubusercontent.com/webpack/media/master/logo/icon.png) xyz }", ""]
9393
]);
94+
test("font face with url", "@font-face { src: url('My-Font.woff2') format('woff2') }", [
95+
[1, "@font-face { src: url({./My-Font.woff2}) format('woff2') }", ""]
96+
], "");
97+
test("font face with url and query and fragment identifier", "@font-face { src: url('webfont.eot?#iefix') format('embedded-opentype') }", [
98+
[1, "@font-face { src: url({./webfont.eot}?#iefix) format('embedded-opentype') }", ""]
99+
], "");
100+
test("font face with url and fragment identifier", "@font-face { src: url('webfont.svg#svgFontName') format('svg') }", [
101+
[1, "@font-face { src: url({./webfont.svg}#svgFontName) format('svg') }", ""]
102+
], "");
94103

95104
test("module wrapped in spaces", ".class { background: green url(module) xyz }", [
96105
[1, ".class { background: green url(module-wrapped) xyz }", ""]

0 commit comments

Comments
 (0)