index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. var JSON5 = require("json5");
  2. var path = require("path");
  3. var util = require("util");
  4. var os = require("os");
  5. var assign = require("object-assign");
  6. var emojiRegex = /[\uD800-\uDFFF]./;
  7. var emojiList = require("emojis-list").filter(function(emoji) {
  8. return emojiRegex.test(emoji)
  9. });
  10. var matchAbsolutePath = /^\/|^[A-Z]:[/\\]|^\\\\/i; // node 0.10 does not support path.isAbsolute()
  11. var matchAbsoluteWin32Path = /^[A-Z]:[/\\]|^\\\\/i;
  12. var matchRelativePath = /^\.\.?[/\\]/;
  13. var baseEncodeTables = {
  14. 26: "abcdefghijklmnopqrstuvwxyz",
  15. 32: "123456789abcdefghjkmnpqrstuvwxyz", // no 0lio
  16. 36: "0123456789abcdefghijklmnopqrstuvwxyz",
  17. 49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no lIO
  18. 52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
  19. 58: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no 0lIO
  20. 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
  21. 64: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
  22. };
  23. var emojiCache = {};
  24. var parseQueryDeprecationWarning = util.deprecate(function() {},
  25. "loaderUtils.parseQuery() received a non-string value which can be problematic, " +
  26. "see https://github.com/webpack/loader-utils/issues/56" + os.EOL +
  27. "parseQuery() will be replaced with getOptions() in the next major version of loader-utils."
  28. );
  29. function encodeStringToEmoji(content, length) {
  30. if (emojiCache[content]) return emojiCache[content];
  31. length = length || 1;
  32. var emojis = [];
  33. do {
  34. var index = Math.floor(Math.random() * emojiList.length);
  35. emojis.push(emojiList[index]);
  36. emojiList.splice(index, 1);
  37. } while (--length > 0);
  38. var emojiEncoding = emojis.join('');
  39. emojiCache[content] = emojiEncoding;
  40. return emojiEncoding;
  41. }
  42. function encodeBufferToBase(buffer, base) {
  43. var encodeTable = baseEncodeTables[base];
  44. if (!encodeTable) throw new Error("Unknown encoding base" + base);
  45. var readLength = buffer.length;
  46. var Big = require('big.js');
  47. Big.RM = Big.DP = 0;
  48. var b = new Big(0);
  49. for (var i = readLength - 1; i >= 0; i--) {
  50. b = b.times(256).plus(buffer[i]);
  51. }
  52. var output = "";
  53. while (b.gt(0)) {
  54. output = encodeTable[b.mod(base)] + output;
  55. b = b.div(base);
  56. }
  57. Big.DP = 20;
  58. Big.RM = 1;
  59. return output;
  60. }
  61. exports.parseQuery = function parseQuery(query) {
  62. var specialValues = {
  63. 'null': null,
  64. 'true': true,
  65. 'false': false
  66. };
  67. if(!query) return {};
  68. if(typeof query !== "string") {
  69. parseQueryDeprecationWarning();
  70. return query;
  71. }
  72. if(query.substr(0, 1) !== "?")
  73. throw new Error("a valid query string passed to parseQuery should begin with '?'");
  74. query = query.substr(1);
  75. var queryLength = query.length;
  76. if(query.substr(0, 1) === "{" && query.substr(-1) === "}") {
  77. return JSON5.parse(query);
  78. }
  79. var queryArgs = query.split(/[,\&]/g);
  80. var result = {};
  81. queryArgs.forEach(function(arg) {
  82. var idx = arg.indexOf("=");
  83. if(idx >= 0) {
  84. var name = arg.substr(0, idx);
  85. var value = decodeURIComponent(arg.substr(idx+1));
  86. if (specialValues.hasOwnProperty(value)) {
  87. value = specialValues[value];
  88. }
  89. if(name.substr(-2) === "[]") {
  90. name = decodeURIComponent(name.substr(0, name.length-2));
  91. if(!Array.isArray(result[name]))
  92. result[name] = [];
  93. result[name].push(value);
  94. } else {
  95. name = decodeURIComponent(name);
  96. result[name] = value;
  97. }
  98. } else {
  99. if(arg.substr(0, 1) === "-") {
  100. result[decodeURIComponent(arg.substr(1))] = false;
  101. } else if(arg.substr(0, 1) === "+") {
  102. result[decodeURIComponent(arg.substr(1))] = true;
  103. } else {
  104. result[decodeURIComponent(arg)] = true;
  105. }
  106. }
  107. });
  108. return result;
  109. };
  110. exports.getLoaderConfig = function(loaderContext, defaultConfigKey) {
  111. var query = exports.parseQuery(loaderContext.query);
  112. var configKey = query.config || defaultConfigKey;
  113. if (configKey) {
  114. var config = loaderContext.options[configKey] || {};
  115. delete query.config;
  116. return assign({}, config, query);
  117. }
  118. return query;
  119. };
  120. exports.stringifyRequest = function(loaderContext, request) {
  121. var splitted = request.split("!");
  122. var context = loaderContext.context || (loaderContext.options && loaderContext.options.context);
  123. return JSON.stringify(splitted.map(function(part) {
  124. // First, separate singlePath from query, because the query might contain paths again
  125. var splittedPart = part.match(/^(.*?)(\?.*)/);
  126. var singlePath = splittedPart ? splittedPart[1] : part;
  127. var query = splittedPart ? splittedPart[2] : "";
  128. if(matchAbsolutePath.test(singlePath) && context) {
  129. singlePath = path.relative(context, singlePath);
  130. if(matchAbsolutePath.test(singlePath)) {
  131. // If singlePath still matches an absolute path, singlePath was on a different drive than context.
  132. // In this case, we leave the path platform-specific without replacing any separators.
  133. // @see https://github.com/webpack/loader-utils/pull/14
  134. return singlePath + query;
  135. }
  136. if(matchRelativePath.test(singlePath) === false) {
  137. // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules).
  138. singlePath = "./" + singlePath;
  139. }
  140. }
  141. return singlePath.replace(/\\/g, "/") + query;
  142. }).join("!"));
  143. };
  144. function dotRequest(obj) {
  145. return obj.request;
  146. }
  147. exports.getRemainingRequest = function(loaderContext) {
  148. if(loaderContext.remainingRequest)
  149. return loaderContext.remainingRequest;
  150. var request = loaderContext.loaders.slice(loaderContext.loaderIndex+1).map(dotRequest).concat([loaderContext.resource]);
  151. return request.join("!");
  152. };
  153. exports.getCurrentRequest = function(loaderContext) {
  154. if(loaderContext.currentRequest)
  155. return loaderContext.currentRequest;
  156. var request = loaderContext.loaders.slice(loaderContext.loaderIndex).map(dotRequest).concat([loaderContext.resource]);
  157. return request.join("!");
  158. };
  159. exports.isUrlRequest = function(url, root) {
  160. // An URL is not an request if
  161. // 1. it's a Data Url
  162. // 2. it's an absolute url or and protocol-relative
  163. // 3. it's some kind of url for a template
  164. if(/^data:|^chrome-extension:|^(https?:)?\/\/|^[\{\}\[\]#*;,'§\$%&\(=?`´\^°<>]/.test(url)) return false;
  165. // 4. It's also not an request if root isn't set and it's a root-relative url
  166. if((root === undefined || root === false) && /^\//.test(url)) return false;
  167. return true;
  168. };
  169. exports.urlToRequest = function(url, root) {
  170. var moduleRequestRegex = /^[^?]*~/;
  171. var request;
  172. if(matchAbsoluteWin32Path.test(url)) {
  173. // absolute windows path, keep it
  174. request = url;
  175. } else if(root !== undefined && root !== false && /^\//.test(url)) {
  176. // if root is set and the url is root-relative
  177. switch(typeof root) {
  178. // 1. root is a string: root is prefixed to the url
  179. case "string":
  180. // special case: `~` roots convert to module request
  181. if (moduleRequestRegex.test(root)) {
  182. request = root.replace(/([^~\/])$/, "$1/") + url.slice(1);
  183. } else {
  184. request = root + url;
  185. }
  186. break;
  187. // 2. root is `true`: absolute paths are allowed
  188. // *nix only, windows-style absolute paths are always allowed as they doesn't start with a `/`
  189. case "boolean":
  190. request = url;
  191. break;
  192. default:
  193. throw new Error("Unexpected parameters to loader-utils 'urlToRequest': url = " + url + ", root = " + root + ".");
  194. }
  195. } else if(/^\.\.?\//.test(url)) {
  196. // A relative url stays
  197. request = url;
  198. } else {
  199. // every other url is threaded like a relative url
  200. request = "./" + url;
  201. }
  202. // A `~` makes the url an module
  203. if (moduleRequestRegex.test(request)) {
  204. request = request.replace(moduleRequestRegex, "");
  205. }
  206. return request;
  207. };
  208. exports.parseString = function parseString(str) {
  209. try {
  210. if(str[0] === '"') return JSON.parse(str);
  211. if(str[0] === "'" && str.substr(str.length - 1) === "'") {
  212. return parseString(str.replace(/\\.|"/g, function(x) {
  213. if(x === '"') return '\\"';
  214. return x;
  215. }).replace(/^'|'$/g, '"'));
  216. }
  217. return JSON.parse('"' + str + '"');
  218. } catch(e) {
  219. return str;
  220. }
  221. };
  222. exports.getHashDigest = function getHashDigest(buffer, hashType, digestType, maxLength) {
  223. hashType = hashType || "md5";
  224. maxLength = maxLength || 9999;
  225. var hash = require("crypto").createHash(hashType);
  226. hash.update(buffer);
  227. if (digestType === "base26" || digestType === "base32" || digestType === "base36" ||
  228. digestType === "base49" || digestType === "base52" || digestType === "base58" ||
  229. digestType === "base62" || digestType === "base64") {
  230. return encodeBufferToBase(hash.digest(), digestType.substr(4)).substr(0, maxLength);
  231. } else {
  232. return hash.digest(digestType || "hex").substr(0, maxLength);
  233. }
  234. };
  235. exports.interpolateName = function interpolateName(loaderContext, name, options) {
  236. var filename;
  237. if (typeof name === "function") {
  238. filename = name(loaderContext.resourcePath);
  239. } else {
  240. filename = name || "[hash].[ext]";
  241. }
  242. var context = options.context;
  243. var content = options.content;
  244. var regExp = options.regExp;
  245. var ext = "bin";
  246. var basename = "file";
  247. var directory = "";
  248. var folder = "";
  249. if(loaderContext.resourcePath) {
  250. var resourcePath = loaderContext.resourcePath;
  251. var idx = resourcePath.lastIndexOf(".");
  252. var i = resourcePath.lastIndexOf("\\");
  253. var j = resourcePath.lastIndexOf("/");
  254. var p = i < 0 ? j : j < 0 ? i : i < j ? i : j;
  255. if(idx >= 0) {
  256. ext = resourcePath.substr(idx+1);
  257. resourcePath = resourcePath.substr(0, idx);
  258. }
  259. if(p >= 0) {
  260. basename = resourcePath.substr(p+1);
  261. resourcePath = resourcePath.substr(0, p+1);
  262. }
  263. if (typeof context !== 'undefined') {
  264. directory = path.relative(context, resourcePath + "_").replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1");
  265. directory = directory.substr(0, directory.length-1);
  266. }
  267. else {
  268. directory = resourcePath.replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1");
  269. }
  270. if (directory.length === 1) {
  271. directory = "";
  272. } else if (directory.length > 1) {
  273. folder = path.basename(directory);
  274. }
  275. }
  276. var url = filename;
  277. if(content) {
  278. // Match hash template
  279. url = url.replace(/\[(?:(\w+):)?hash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, function() {
  280. return exports.getHashDigest(content, arguments[1], arguments[2], parseInt(arguments[3], 10));
  281. }).replace(/\[emoji(?::(\d+))?\]/ig, function() {
  282. return encodeStringToEmoji(content, arguments[1]);
  283. });
  284. }
  285. url = url.replace(/\[ext\]/ig, function() {
  286. return ext;
  287. }).replace(/\[name\]/ig, function() {
  288. return basename;
  289. }).replace(/\[path\]/ig, function() {
  290. return directory;
  291. }).replace(/\[folder\]/ig, function() {
  292. return folder;
  293. });
  294. if(regExp && loaderContext.resourcePath) {
  295. var re = new RegExp(regExp);
  296. var match = loaderContext.resourcePath.match(re);
  297. if(match) {
  298. for (var i = 0; i < match.length; i++) {
  299. var re = new RegExp("\\[" + i + "\\]", "ig");
  300. url = url.replace(re, match[i]);
  301. }
  302. }
  303. }
  304. if(typeof loaderContext.options === "object" && typeof loaderContext.options.customInterpolateName === "function") {
  305. url = loaderContext.options.customInterpolateName.call(loaderContext, url, name, options);
  306. }
  307. return url;
  308. };