processCss.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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 && url.type === "url") {
  45. url = url.url;
  46. } else if(url && 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)) {
  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 isAlias(url) {
  80. // Handle alias starting by / and root disabled
  81. return url !== options.resolve(url)
  82. }
  83. function processNode(item) {
  84. switch (item.type) {
  85. case "value":
  86. item.nodes.forEach(processNode);
  87. break;
  88. case "nested-item":
  89. item.nodes.forEach(processNode);
  90. break;
  91. case "item":
  92. var importIndex = imports["$" + item.name];
  93. if (typeof importIndex === "number") {
  94. item.name = "___CSS_LOADER_IMPORT___" + importIndex + "___";
  95. }
  96. break;
  97. case "url":
  98. if (options.url && item.url.replace(/\s/g, '').length && !/^#/.test(item.url) && (isAlias(item.url) || loaderUtils.isUrlRequest(item.url, options.root))) {
  99. // Strip quotes, they will be re-added if the module needs them
  100. item.stringType = "";
  101. delete item.innerSpacingBefore;
  102. delete item.innerSpacingAfter;
  103. var url = item.url;
  104. item.url = "___CSS_LOADER_URL___" + urlItems.length + "___";
  105. urlItems.push({
  106. url: url
  107. });
  108. }
  109. break;
  110. }
  111. }
  112. css.walkDecls(function(decl) {
  113. var values = Tokenizer.parseValues(decl.value);
  114. values.nodes.forEach(function(value) {
  115. value.nodes.forEach(processNode);
  116. });
  117. decl.value = Tokenizer.stringifyValues(values);
  118. });
  119. css.walkAtRules(function(atrule) {
  120. if(typeof atrule.params === "string") {
  121. atrule.params = replaceImportsInString(atrule.params);
  122. }
  123. });
  124. options.importItems = importItems;
  125. options.urlItems = urlItems;
  126. options.exports = exports;
  127. };
  128. });
  129. module.exports = function processCss(inputSource, inputMap, options, callback) {
  130. var query = options.query;
  131. var root = query.root && query.root.length > 0 ? query.root.replace(/\/$/, "") : query.root;
  132. var context = query.context;
  133. var localIdentName = query.localIdentName || "[hash:base64]";
  134. var localIdentRegExp = query.localIdentRegExp;
  135. var forceMinimize = query.minimize;
  136. var minimize = typeof forceMinimize !== "undefined" ? !!forceMinimize : options.minimize;
  137. var customGetLocalIdent = query.getLocalIdent || getLocalIdent;
  138. var parserOptions = {
  139. root: root,
  140. mode: options.mode,
  141. url: query.url !== false,
  142. import: query.import !== false,
  143. resolve: options.resolve
  144. };
  145. var pipeline = postcss([
  146. modulesValues,
  147. localByDefault({
  148. mode: options.mode,
  149. rewriteUrl: function(global, url) {
  150. if(parserOptions.url){
  151. url = url.trim();
  152. if(!url.replace(/\s/g, '').length || !loaderUtils.isUrlRequest(url, root)) {
  153. return url;
  154. }
  155. if(global) {
  156. return loaderUtils.urlToRequest(url, root);
  157. }
  158. }
  159. return url;
  160. }
  161. }),
  162. extractImports(),
  163. modulesScope({
  164. generateScopedName: function generateScopedName (exportName) {
  165. return customGetLocalIdent(options.loaderContext, localIdentName, exportName, {
  166. regExp: localIdentRegExp,
  167. hashPrefix: query.hashPrefix || "",
  168. context: context
  169. });
  170. }
  171. }),
  172. parserPlugin(parserOptions)
  173. ]);
  174. if(minimize) {
  175. var cssnano = require("cssnano");
  176. var minimizeOptions = assign({}, query.minimize);
  177. ["zindex", "normalizeUrl", "discardUnused", "mergeIdents", "reduceIdents", "autoprefixer"].forEach(function(name) {
  178. if(typeof minimizeOptions[name] === "undefined")
  179. minimizeOptions[name] = false;
  180. });
  181. pipeline.use(cssnano(minimizeOptions));
  182. }
  183. pipeline.process(inputSource, {
  184. // we need a prefix to avoid path rewriting of PostCSS
  185. from: "/css-loader!" + options.from,
  186. to: options.to,
  187. map: options.sourceMap ? {
  188. prev: inputMap,
  189. sourcesContent: true,
  190. inline: false,
  191. annotation: false
  192. } : null
  193. }).then(function(result) {
  194. callback(null, {
  195. source: result.css,
  196. map: result.map && result.map.toJSON(),
  197. exports: parserOptions.exports,
  198. importItems: parserOptions.importItems,
  199. importItemRegExpG: /___CSS_LOADER_IMPORT___([0-9]+)___/g,
  200. importItemRegExp: /___CSS_LOADER_IMPORT___([0-9]+)___/,
  201. urlItems: parserOptions.urlItems,
  202. urlItemRegExpG: /___CSS_LOADER_URL___([0-9]+)___/g,
  203. urlItemRegExp: /___CSS_LOADER_URL___([0-9]+)___/
  204. });
  205. }).catch(function(err) {
  206. if (err.name === 'CssSyntaxError') {
  207. var wrappedError = new CSSLoaderError(
  208. 'Syntax Error',
  209. err.reason,
  210. err.line != null && err.column != null
  211. ? {line: err.line, column: err.column}
  212. : null,
  213. err.input.source
  214. );
  215. callback(wrappedError);
  216. } else {
  217. callback(err);
  218. }
  219. });
  220. };
  221. function formatMessage(message, loc, source) {
  222. var formatted = message;
  223. if (loc) {
  224. formatted = formatted
  225. + ' (' + loc.line + ':' + loc.column + ')';
  226. }
  227. if (loc && source) {
  228. formatted = formatted
  229. + '\n\n' + formatCodeFrame(source, loc.line, loc.column) + '\n';
  230. }
  231. return formatted;
  232. }
  233. function CSSLoaderError(name, message, loc, source, error) {
  234. Error.call(this);
  235. Error.captureStackTrace(this, CSSLoaderError);
  236. this.name = name;
  237. this.error = error;
  238. this.message = formatMessage(message, loc, source);
  239. this.hideStack = true;
  240. }
  241. CSSLoaderError.prototype = Object.create(Error.prototype);
  242. CSSLoaderError.prototype.constructor = CSSLoaderError;