You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
6.6 KiB

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