Skip to content

Commit 3109c97

Browse files
committed
custom parser, better minimizer, SourceMaps
fixes #43
1 parent a894573 commit 3109c97

10 files changed

+375
-222
lines changed

cssToString.js

-16
This file was deleted.

index.js

+1-160
Original file line numberDiff line numberDiff line change
@@ -2,163 +2,4 @@
22
MIT License http://www.opensource.org/licenses/mit-license.php
33
Author Tobias Koppers @sokra
44
*/
5-
var csso = require("csso");
6-
var SourceMapGenerator = require("source-map").SourceMapGenerator;
7-
var loaderUtils = require("loader-utils");
8-
9-
module.exports = function(content, map) {
10-
this.cacheable && this.cacheable();
11-
var result = [];
12-
var queryString = this.query || "";
13-
var query = loaderUtils.parseQuery(this.query);
14-
var root = query.root;
15-
var forceMinimize = query.minimize;
16-
var importLoaders = parseInt(query.importLoaders, 10) || 0;
17-
var minimize = typeof forceMinimize !== "undefined" ? !!forceMinimize : (this && this.minimize);
18-
var tree = csso.parse(content, "stylesheet");
19-
if(tree && minimize) {
20-
tree = csso.compress(tree, query.disableStructuralMinification);
21-
tree = csso.cleanInfo(tree);
22-
}
23-
24-
if(tree) {
25-
var imports = extractImports(tree);
26-
annotateUrls(tree);
27-
28-
imports.forEach(function(imp) {
29-
if(!loaderUtils.isUrlRequest(imp.url)) {
30-
result.push("exports.push([module.id, " + JSON.stringify("@import url(" + imp.url + ");") + ", " + JSON.stringify(imp.media.join("")) + "]);");
31-
} else {
32-
var importUrl = "-!" +
33-
this.loaders.slice(
34-
this.loaderIndex,
35-
this.loaderIndex + 1 + importLoaders
36-
).map(function(x) { return x.request; }).join("!") + "!" +
37-
loaderUtils.urlToRequest(imp.url);
38-
result.push("require(" + JSON.stringify(require.resolve("./mergeImport")) + ")(exports, require(" + JSON.stringify(importUrl) + "), " + JSON.stringify(imp.media.join("")) + ");");
39-
}
40-
}, this);
41-
}
42-
43-
var css = JSON.stringify(tree ? csso.translate(tree) : "");
44-
var uriRegExp = /%CSSURL\[%(.*?)%\]CSSURL%/g;
45-
css = css.replace(uriRegExp, function(str) {
46-
var match = /^%CSSURL\[%(["']?(.*?)["']?)%\]CSSURL%$/.exec(JSON.parse('"' + str + '"'));
47-
var url = loaderUtils.parseString(match[1]);
48-
if(!loaderUtils.isUrlRequest(url, root)) return JSON.stringify(match[1]).replace(/^"|"$/g, "");
49-
var idx = url.indexOf("?#");
50-
if(idx < 0) idx = url.indexOf("#");
51-
if(idx > 0) {
52-
// in cases like url('webfont.eot?#iefix')
53-
var request = url.substr(0, idx);
54-
return "\"+require(" + JSON.stringify(loaderUtils.urlToRequest(request, root)) + ")+\"" + url.substr(idx);
55-
} else if(idx === 0) {
56-
// only hash
57-
return JSON.stringify(match[1]).replace(/^"|"$/g, "");
58-
}
59-
return "\"+require(" + JSON.stringify(loaderUtils.urlToRequest(url, root)) + ")+\"";
60-
});
61-
if(query.sourceMap && !minimize) {
62-
var cssRequest = loaderUtils.getRemainingRequest(this);
63-
var request = loaderUtils.getCurrentRequest(this);
64-
if(!map) {
65-
var sourceMap = new SourceMapGenerator({
66-
file: request
67-
});
68-
var lines = content.split("\n").length;
69-
for(var i = 0; i < lines; i++) {
70-
sourceMap.addMapping({
71-
generated: {
72-
line: i+1,
73-
column: 0
74-
},
75-
source: cssRequest,
76-
original: {
77-
line: i+1,
78-
column: 0
79-
},
80-
});
81-
}
82-
sourceMap.setSourceContent(cssRequest, content);
83-
map = JSON.stringify(sourceMap.toJSON());
84-
} else if(typeof map !== "string") {
85-
map = JSON.stringify(map);
86-
}
87-
result.push("exports.push([module.id, " + css + ", \"\", " + map + "]);");
88-
} else {
89-
result.push("exports.push([module.id, " + css + ", \"\"]);");
90-
}
91-
return "exports = module.exports = require(" + JSON.stringify(require.resolve("./cssToString")) + ")();\n" +
92-
result.join("\n");
93-
}
94-
95-
function extractImports(tree) {
96-
var results = [];
97-
var removes = [];
98-
for(var i = 1; i < tree.length; i++) {
99-
var rule = tree[i];
100-
if(rule[0] === "atrules" &&
101-
rule[1][0] === "atkeyword" &&
102-
rule[1][1][0] === "ident" &&
103-
rule[1][1][1] === "import") {
104-
var imp = {
105-
url: null,
106-
media: []
107-
};
108-
for(var j = 2; j < rule.length; j++) {
109-
var item = rule[j];
110-
if(item[0] === "string") {
111-
imp.url = loaderUtils.parseString(item[1]);
112-
} else if(item[0] === "uri") {
113-
imp.url = item[1][0] === "string" ? loaderUtils.parseString(item[1][1]) : item[1][1];
114-
} else if(item[0] === "ident" && item[1] !== "url") {
115-
imp.media.push(csso.translate(item));
116-
} else if(item[0] !== "s" || imp.media.length > 0) {
117-
imp.media.push(csso.translate(item));
118-
}
119-
}
120-
while(imp.media.length > 0 &&
121-
/^\s*$/.test(imp.media[imp.media.length-1]))
122-
imp.media.pop();
123-
if(imp.url !== null) {
124-
results.push(imp);
125-
removes.push(i);
126-
}
127-
}
128-
}
129-
removes.reverse().forEach(function(i) {
130-
tree.splice(i, 1);
131-
});
132-
return results;
133-
}
134-
function annotateUrls(tree) {
135-
function iterateChildren() {
136-
for(var i = 1; i < tree.length; i++) {
137-
annotateUrls(tree[i]);
138-
}
139-
}
140-
switch(tree[0]) {
141-
case "stylesheet": return iterateChildren();
142-
case "ruleset": return iterateChildren();
143-
case "block": return iterateChildren();
144-
case "atruleb": return iterateChildren();
145-
case "atruler": return iterateChildren();
146-
case "atrulers": return iterateChildren();
147-
case "declaration": return iterateChildren();
148-
case "value": return iterateChildren();
149-
case "uri":
150-
for(var i = 1; i < tree.length; i++) {
151-
var item = tree[i];
152-
switch(item[0]) {
153-
case "ident":
154-
case "raw":
155-
item[1] = "%CSSURL[%" + item[1] + "%]CSSURL%";
156-
return;
157-
case "string":
158-
item[1] = "%CSSURL[%" + item[1] + "%]CSSURL%";
159-
return;
160-
}
161-
}
162-
}
163-
}
164-
5+
module.exports = require("./lib/loader");

lib/css-base.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
// css base code, injected by the css-loader
6+
//
7+
module.exports = function() {
8+
var list = [];
9+
10+
// return the list of modules as css string
11+
list.toString = function toString() {
12+
var result = [];
13+
for(var i = 0; i < this.length; i++) {
14+
var item = this[i];
15+
if(item[2]) {
16+
result.push("@media " + item[2] + "{" + item[1] + "}");
17+
} else {
18+
result.push(item[1]);
19+
}
20+
}
21+
return result.join("");
22+
};
23+
24+
// import a list of modules into the list
25+
list.i = function(modules, mediaQuery) {
26+
if(typeof modules === "string")
27+
modules = [[null, modules, ""]];
28+
var alreadyImportedModules = {};
29+
for(var i = 0; i < this.length; i++) {
30+
var id = this[i][0];
31+
if(typeof id === "number")
32+
alreadyImportedModules[id] = true;
33+
}
34+
for(var i = 0; i < modules.length; i++) {
35+
var item = modules[i];
36+
// skip already imported module
37+
// this implementation is not 100% perfect for weird media query combinations
38+
// when a module is imported multiple times with different media queries.
39+
// I hope this will never occur (Hey this way we have smaller bundles)
40+
if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
41+
if(mediaQuery && !item[2]) {
42+
item[2] = mediaQuery;
43+
} else if(mediaQuery) {
44+
item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
45+
}
46+
list.push(item);
47+
}
48+
}
49+
};
50+
return list;
51+
};

lib/loader.js

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
var path = require("path");
6+
var parseSource = require("./parseSource");
7+
var ReplaceMany = require("./ReplaceMany");
8+
var loaderUtils = require("loader-utils");
9+
var SourceListMap = require("source-list-map").SourceListMap;
10+
var fromStringWithSourceMap = require("source-list-map").fromStringWithSourceMap;
11+
var CleanCSS = require("clean-css");
12+
13+
module.exports = function(content, map) {
14+
this.cacheable && this.cacheable();
15+
var query = loaderUtils.parseQuery(this.query);
16+
var root = query.root;
17+
var forceMinimize = query.minimize;
18+
var importLoaders = parseInt(query.importLoaders, 10) || 0;
19+
var minimize = typeof forceMinimize !== "undefined" ? !!forceMinimize : (this && this.minimize);
20+
21+
if(typeof map !== "string") {
22+
map = JSON.stringify(map);
23+
}
24+
25+
var result = [];
26+
27+
var stuff = parseSource(content);
28+
29+
var replacer = new ReplaceMany();
30+
stuff.imports.forEach(function(imp) {
31+
replacer.replace(imp.start, imp.length, "");
32+
if(!loaderUtils.isUrlRequest(imp.url, root)) {
33+
result.push("exports.push([module.id, " +
34+
JSON.stringify("@import url(" + imp.url + ");") + ", " +
35+
JSON.stringify(imp.mediaQuery) + "]);");
36+
} else {
37+
var loadersRequest = this.loaders.slice(
38+
this.loaderIndex,
39+
this.loaderIndex + 1 + importLoaders
40+
).map(function(x) { return x.request; }).join("!");
41+
var importUrl = "-!" +
42+
loadersRequest + "!" +
43+
loaderUtils.urlToRequest(imp.url);
44+
result.push("exports.i(require(" + loaderUtils.stringifyRequest(this, importUrl) + "), " + JSON.stringify(imp.mediaQuery) + ");");
45+
}
46+
}, this);
47+
stuff.urls.forEach(function(url, idx) {
48+
replacer.replace(url.start, url.length, "__CSSLOADERURL_" + idx + "__");
49+
});
50+
var placeholders = {};
51+
stuff.placeholders.forEach(function(placeholder) {
52+
var hash = require("crypto").createHash("md5");
53+
hash.update(this.request);
54+
hash.update(placeholder.name);
55+
var ident = "z" + hash.digest("hex");
56+
placeholders[placeholder.name] = ident;
57+
replacer.replace(placeholder.start, placeholder.length, ident);
58+
}, this);
59+
60+
var cssContent = replacer.run(content);
61+
62+
if(minimize) {
63+
var options = Object.create(query);
64+
if(query.sourceMap && map) {
65+
options.sourceMap = map;
66+
}
67+
var minimizeResult = new CleanCSS(options).minify(cssContent);
68+
map = minimizeResult.sourceMap;
69+
cssContent = minimizeResult.styles;
70+
if(typeof map !== "string")
71+
map = JSON.stringify(map);
72+
}
73+
74+
var css = JSON.stringify(cssContent);
75+
76+
var urlRegExp = /__CSSLOADERURL_[0-9]+__/g;
77+
css = css.replace(urlRegExp, function(str) {
78+
var match = /^__CSSLOADERURL_([0-9]+)__$/.exec(str);
79+
if(!match) return str;
80+
var idx = parseInt(match[1], 10);
81+
if(!stuff.urls[idx]) return str;
82+
var urlItem = stuff.urls[idx];
83+
var url = urlItem.url;
84+
if(!loaderUtils.isUrlRequest(url, root))
85+
return toEmbStr(urlItem.raw);
86+
var idx = url.indexOf("?#");
87+
if(idx < 0) idx = url.indexOf("#");
88+
if(idx > 0) {
89+
// in cases like url('webfont.eot?#iefix')
90+
var request = url.substr(0, idx);
91+
return "\"+require(" + loaderUtils.stringifyRequest(this, loaderUtils.urlToRequest(request, root)) + ")+\"" + url.substr(idx);
92+
} else if(idx === 0) {
93+
// only hash
94+
return toEmbStr(urlItem.raw);
95+
}
96+
return "\"+require(" + loaderUtils.stringifyRequest(this, loaderUtils.urlToRequest(url, root)) + ")+\"";
97+
}.bind(this));
98+
99+
function toEmbStr(str) {
100+
return JSON.stringify(str).replace(/^"|"$/g, "");
101+
}
102+
103+
if(query.sourceMap && !minimize) {
104+
var cssRequest = loaderUtils.getRemainingRequest(this);
105+
var request = loaderUtils.getCurrentRequest(this);
106+
if(!map) {
107+
var sourceMap = new SourceListMap();
108+
sourceMap.add(content, cssRequest, content);
109+
map = sourceMap.toStringWithSourceMap({
110+
file: request
111+
}).map;
112+
if(map.sources) {
113+
map.sources = map.sources.map(function(source) {
114+
var p = path.relative(query.context || this.options.context, source).replace(/\\/g, "/");
115+
if(p.indexOf("../") !== 0)
116+
p = "./" + p;
117+
return "/" + p;
118+
}, this);
119+
map.sourceRoot = "webpack://";
120+
}
121+
map = JSON.stringify(map);
122+
}
123+
result.push("exports.push([module.id, " + css + ", \"\", " + map + "]);");
124+
} else {
125+
result.push("exports.push([module.id, " + css + ", \"\"]);");
126+
}
127+
128+
return "exports = module.exports = require(" + loaderUtils.stringifyRequest(this, require.resolve("./css-base.js")) + ")();\n" +
129+
result.join("\n");
130+
}

0 commit comments

Comments
 (0)