processCss.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. var formatCodeFrame = require("babel-code-frame");
  6. var Tokenizer = require("css-selector-tokenizer");
  7. var postcss = require("postcss");
  8. var loaderUtils = require("loader-utils");
  9. var assign = require("object-assign");
  10. var getLocalIdent = require("./getLocalIdent");
  11. var icssUtils = require('icss-utils');
  12. var localByDefault = require("postcss-modules-local-by-default");
  13. var extractImports = require("postcss-modules-extract-imports");
  14. var modulesScope = require("postcss-modules-scope");
  15. var modulesValues = require("postcss-modules-values");
  16. var valueParser = require('postcss-value-parser');
  17. var parserPlugin = postcss.plugin("css-loader-parser", function(options) {
  18. return function(css) {
  19. var imports = {};
  20. var exports = {};
  21. var importItems = [];
  22. var urlItems = [];
  23. function replaceImportsInString(str) {
  24. if(options.import) {
  25. var tokens = valueParser(str);
  26. tokens.walk(function (node) {
  27. if (node.type !== 'word') {
  28. return;
  29. }
  30. var token = node.value;
  31. var importIndex = imports["$" + token];
  32. if(typeof importIndex === "number") {
  33. node.value = "___CSS_LOADER_IMPORT___" + importIndex + "___";
  34. }
  35. })
  36. return tokens.toString();
  37. }
  38. return str;
  39. }
  40. if(options.import) {
  41. css.walkAtRules(/import/i, function(rule) {
  42. var values = Tokenizer.parseValues(rule.params);
  43. var url = values.nodes[0].nodes[0];
  44. if(url.type === "url") {
  45. url = url.url;
  46. } else if(url.type === "string") {
  47. url = url.value;
  48. } else throw rule.error("Unexpected format " + rule.params);
  49. if (!url.replace(/\s/g, '').length) {
  50. return;
  51. }
  52. values.nodes[0].nodes.shift();
  53. var mediaQuery = Tokenizer.stringifyValues(values);
  54. if(loaderUtils.isUrlRequest(url, options.root) && options.mode === "global") {
  55. url = loaderUtils.urlToRequest(url, options.root);
  56. }
  57. importItems.push({
  58. url: url,
  59. mediaQuery: mediaQuery
  60. });
  61. rule.remove();
  62. });
  63. }
  64. var icss = icssUtils.extractICSS(css);
  65. exports = icss.icssExports;
  66. Object.keys(icss.icssImports).forEach(function(key) {
  67. var url = loaderUtils.parseString(key);
  68. Object.keys(icss.icssImports[key]).forEach(function(prop) {
  69. imports["$" + prop] = importItems.length;
  70. importItems.push({
  71. url: url,
  72. export: icss.icssImports[key][prop]
  73. });
  74. })
  75. });
  76. Object.keys(exports).forEach(function(exportName) {
  77. exports[exportName] = replaceImportsInString(exports[exportName]);
  78. });
  79. function processNode(item) {
  80. switch (item.type) {
  81. case "value":
  82. item.nodes.forEach(processNode);
  83. break;
  84. case "nested-item":
  85. item.nodes.forEach(processNode);
  86. break;
  87. case "item":
  88. var importIndex = imports["$" + item.name];
  89. if (typeof importIndex === "number") {
  90. item.name = "___CSS_LOADER_IMPORT___" + importIndex + "___";
  91. }
  92. break;
  93. case "url":
  94. if (options.url && item.url.replace(/\s/g, '').length && !/^#/.test(item.url) && loaderUtils.isUrlRequest(item.url, options.root)) {
  95. // Don't remove quotes around url when contain space
  96. if (item.url.indexOf(" ") === -1) {
  97. item.stringType = "";
  98. }
  99. delete item.innerSpacingBefore;
  100. delete item.innerSpacingAfter;
  101. var url = item.url;
  102. item.url = "___CSS_LOADER_URL___" + urlItems.length + "___";
  103. urlItems.push({
  104. url: url
  105. });
  106. }
  107. break;
  108. }
  109. }
  110. css.walkDecls(function(decl) {
  111. var values = Tokenizer.parseValues(decl.value);
  112. values.nodes.forEach(function(value) {
  113. value.nodes.forEach(processNode);
  114. });
  115. decl.value = Tokenizer.stringifyValues(values);
  116. });
  117. css.walkAtRules(function(atrule) {
  118. if(typeof atrule.params === "string") {
  119. atrule.params = replaceImportsInString(atrule.params);
  120. }
  121. });
  122. options.importItems = importItems;
  123. options.urlItems = urlItems;
  124. options.exports = exports;
  125. };
  126. });
  127. module.exports = function processCss(inputSource, inputMap, options, callback) {
  128. var query = options.query;
  129. var root = query.root && query.root.length > 0 ? query.root.replace(/\/$/, "") : query.root;
  130. var context = query.context;
  131. var localIdentName = query.localIdentName || "[hash:base64]";
  132. var localIdentRegExp = query.localIdentRegExp;
  133. var forceMinimize = query.minimize;
  134. var minimize = typeof forceMinimize !== "undefined" ? !!forceMinimize : options.minimize;
  135. var customGetLocalIdent = query.getLocalIdent || getLocalIdent;
  136. var parserOptions = {
  137. root: root,
  138. mode: options.mode,
  139. url: query.url !== false,
  140. import: query.import !== false
  141. };
  142. var pipeline = postcss([
  143. modulesValues,
  144. localByDefault({
  145. mode: options.mode,
  146. rewriteUrl: function(global, url) {
  147. if(parserOptions.url){
  148. url = url.trim();
  149. if(!url.replace(/\s/g, '').length || !loaderUtils.isUrlRequest(url, root)) {
  150. return url;
  151. }
  152. if(global) {
  153. return loaderUtils.urlToRequest(url, root);
  154. }
  155. }
  156. return url;
  157. }
  158. }),
  159. extractImports(),
  160. modulesScope({
  161. generateScopedName: function generateScopedName (exportName) {
  162. return customGetLocalIdent(options.loaderContext, localIdentName, exportName, {
  163. regExp: localIdentRegExp,
  164. hashPrefix: query.hashPrefix || "",
  165. context: context
  166. });
  167. }
  168. }),
  169. parserPlugin(parserOptions)
  170. ]);
  171. if(minimize) {
  172. var cssnano = require("cssnano");
  173. var minimizeOptions = assign({}, query.minimize);
  174. ["zindex", "normalizeUrl", "discardUnused", "mergeIdents", "reduceIdents", "autoprefixer"].forEach(function(name) {
  175. if(typeof minimizeOptions[name] === "undefined")
  176. minimizeOptions[name] = false;
  177. });
  178. pipeline.use(cssnano(minimizeOptions));
  179. }
  180. pipeline.process(inputSource, {
  181. // we need a prefix to avoid path rewriting of PostCSS
  182. from: "/css-loader!" + options.from,
  183. to: options.to,
  184. map: options.sourceMap ? {
  185. prev: inputMap,
  186. sourcesContent: true,
  187. inline: false,
  188. annotation: false
  189. } : null
  190. }).then(function(result) {
  191. callback(null, {
  192. source: result.css,
  193. map: result.map && result.map.toJSON(),
  194. exports: parserOptions.exports,
  195. importItems: parserOptions.importItems,
  196. importItemRegExpG: /___CSS_LOADER_IMPORT___([0-9]+)___/g,
  197. importItemRegExp: /___CSS_LOADER_IMPORT___([0-9]+)___/,
  198. urlItems: parserOptions.urlItems,
  199. urlItemRegExpG: /___CSS_LOADER_URL___([0-9]+)___/g,
  200. urlItemRegExp: /___CSS_LOADER_URL___([0-9]+)___/
  201. });
  202. }).catch(function(err) {
  203. if (err.name === 'CssSyntaxError') {
  204. var wrappedError = new CSSLoaderError(
  205. 'Syntax Error',
  206. err.reason,
  207. err.line != null && err.column != null
  208. ? {line: err.line, column: err.column}
  209. : null,
  210. err.input.source
  211. );
  212. callback(wrappedError);
  213. } else {
  214. callback(err);
  215. }
  216. });
  217. };
  218. function formatMessage(message, loc, source) {
  219. var formatted = message;
  220. if (loc) {
  221. formatted = formatted
  222. + ' (' + loc.line + ':' + loc.column + ')';
  223. }
  224. if (loc && source) {
  225. formatted = formatted
  226. + '\n\n' + formatCodeFrame(source, loc.line, loc.column) + '\n';
  227. }
  228. return formatted;
  229. }
  230. function CSSLoaderError(name, message, loc, source, error) {
  231. Error.call(this);
  232. Error.captureStackTrace(this, CSSLoaderError);
  233. this.name = name;
  234. this.error = error;
  235. this.message = formatMessage(message, loc, source);
  236. this.hideStack = true;
  237. }
  238. CSSLoaderError.prototype = Object.create(Error.prototype);
  239. CSSLoaderError.prototype.constructor = CSSLoaderError;