utils.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.fetchFromURL = fetchFromURL;
  6. exports.flattenSourceMap = flattenSourceMap;
  7. exports.getSourceMappingURL = getSourceMappingURL;
  8. exports.isURL = isURL;
  9. var _path = _interopRequireDefault(require("path"));
  10. var _url = _interopRequireDefault(require("url"));
  11. var _sourceMapJs = _interopRequireDefault(require("source-map-js"));
  12. var _iconvLite = require("iconv-lite");
  13. var _parseDataUrl = _interopRequireDefault(require("./parse-data-url"));
  14. var _labelsToNames = _interopRequireDefault(require("./labels-to-names"));
  15. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  16. // Matches only the last occurrence of sourceMappingURL
  17. const innerRegex = /\s*[#@]\s*sourceMappingURL\s*=\s*([^\s'"]*)\s*/;
  18. /* eslint-disable prefer-template */
  19. const sourceMappingURLRegex = RegExp("(?:" + "/\\*" + "(?:\\s*\r?\n(?://)?)?" + "(?:" + innerRegex.source + ")" + "\\s*" + "\\*/" + "|" + "//(?:" + innerRegex.source + ")" + ")" + "\\s*");
  20. /* eslint-enable prefer-template */
  21. function labelToName(label) {
  22. const labelLowercase = String(label).trim().toLowerCase();
  23. return _labelsToNames.default[labelLowercase] || null;
  24. }
  25. async function flattenSourceMap(map) {
  26. const consumer = await new _sourceMapJs.default.SourceMapConsumer(map);
  27. const generatedMap = map.file ? new _sourceMapJs.default.SourceMapGenerator({
  28. file: map.file
  29. }) : new _sourceMapJs.default.SourceMapGenerator();
  30. consumer.sources.forEach(sourceFile => {
  31. const sourceContent = consumer.sourceContentFor(sourceFile, true);
  32. generatedMap.setSourceContent(sourceFile, sourceContent);
  33. });
  34. consumer.eachMapping(mapping => {
  35. const {
  36. source
  37. } = consumer.originalPositionFor({
  38. line: mapping.generatedLine,
  39. column: mapping.generatedColumn
  40. });
  41. const mappings = {
  42. source,
  43. original: {
  44. line: mapping.originalLine,
  45. column: mapping.originalColumn
  46. },
  47. generated: {
  48. line: mapping.generatedLine,
  49. column: mapping.generatedColumn
  50. }
  51. };
  52. if (source) {
  53. generatedMap.addMapping(mappings);
  54. }
  55. });
  56. return generatedMap.toJSON();
  57. }
  58. function getSourceMappingURL(code) {
  59. const lines = code.split(/^/m);
  60. let match;
  61. for (let i = lines.length - 1; i >= 0; i--) {
  62. match = lines[i].match(sourceMappingURLRegex);
  63. if (match) {
  64. break;
  65. }
  66. }
  67. const sourceMappingURL = match ? match[1] || match[2] || "" : null;
  68. return {
  69. sourceMappingURL: sourceMappingURL ? decodeURI(sourceMappingURL) : sourceMappingURL,
  70. replacementString: match ? match[0] : null
  71. };
  72. }
  73. function getAbsolutePath(context, request, sourceRoot) {
  74. if (isURL(sourceRoot)) {
  75. return new URL(request, sourceRoot).toString();
  76. }
  77. if (sourceRoot) {
  78. if (_path.default.isAbsolute(sourceRoot)) {
  79. return _path.default.join(sourceRoot, request);
  80. }
  81. return _path.default.join(context, sourceRoot, request);
  82. }
  83. return _path.default.join(context, request);
  84. }
  85. function fetchFromDataURL(loaderContext, sourceURL) {
  86. const dataURL = (0, _parseDataUrl.default)(sourceURL);
  87. if (dataURL) {
  88. // https://tools.ietf.org/html/rfc4627
  89. // JSON text SHALL be encoded in Unicode. The default encoding is UTF-8.
  90. const encodingName = labelToName(dataURL.parameters.get("charset")) || "UTF-8";
  91. return (0, _iconvLite.decode)(dataURL.body, encodingName);
  92. }
  93. throw new Error(`Failed to parse source map from "data" URL: ${sourceURL}`);
  94. }
  95. async function fetchFromFilesystem(loaderContext, sourceURL) {
  96. let buffer;
  97. if (isURL(sourceURL)) {
  98. return {
  99. path: sourceURL
  100. };
  101. }
  102. try {
  103. buffer = await new Promise((resolve, reject) => {
  104. loaderContext.fs.readFile(sourceURL, (error, data) => {
  105. if (error) {
  106. return reject(error);
  107. }
  108. return resolve(data);
  109. });
  110. });
  111. } catch (error) {
  112. throw new Error(`Failed to parse source map from '${sourceURL}' file: ${error}`);
  113. }
  114. return {
  115. path: sourceURL,
  116. data: buffer.toString()
  117. };
  118. }
  119. async function fetchPathsFromFilesystem(loaderContext, possibleRequests, errorsAccumulator = "") {
  120. let result;
  121. try {
  122. result = await fetchFromFilesystem(loaderContext, possibleRequests[0], errorsAccumulator);
  123. } catch (error) {
  124. // eslint-disable-next-line no-param-reassign
  125. errorsAccumulator += `${error.message}\n\n`;
  126. const [, ...tailPossibleRequests] = possibleRequests;
  127. if (tailPossibleRequests.length === 0) {
  128. error.message = errorsAccumulator;
  129. throw error;
  130. }
  131. return fetchPathsFromFilesystem(loaderContext, tailPossibleRequests, errorsAccumulator);
  132. }
  133. return result;
  134. }
  135. function isURL(value) {
  136. return /^[a-z][a-z0-9+.-]*:/i.test(value) && !_path.default.win32.isAbsolute(value);
  137. }
  138. async function fetchFromURL(loaderContext, context, url, sourceRoot, skipReading = false) {
  139. // 1. It's an absolute url and it is not `windows` path like `C:\dir\file`
  140. if (isURL(url)) {
  141. const {
  142. protocol
  143. } = _url.default.parse(url);
  144. if (protocol === "data:") {
  145. if (skipReading) {
  146. return {
  147. sourceURL: ""
  148. };
  149. }
  150. const sourceContent = fetchFromDataURL(loaderContext, url);
  151. return {
  152. sourceURL: "",
  153. sourceContent
  154. };
  155. }
  156. if (skipReading) {
  157. return {
  158. sourceURL: url
  159. };
  160. }
  161. if (protocol === "file:") {
  162. const pathFromURL = _url.default.fileURLToPath(url);
  163. const sourceURL = _path.default.normalize(pathFromURL);
  164. const {
  165. data: sourceContent
  166. } = await fetchFromFilesystem(loaderContext, sourceURL);
  167. return {
  168. sourceURL,
  169. sourceContent
  170. };
  171. }
  172. throw new Error(`Failed to parse source map: '${url}' URL is not supported`);
  173. }
  174. // 2. It's a scheme-relative
  175. if (/^\/\//.test(url)) {
  176. throw new Error(`Failed to parse source map: '${url}' URL is not supported`);
  177. }
  178. // 3. Absolute path
  179. if (_path.default.isAbsolute(url)) {
  180. let sourceURL = _path.default.normalize(url);
  181. let sourceContent;
  182. if (!skipReading) {
  183. const possibleRequests = [sourceURL];
  184. if (url.startsWith("/")) {
  185. possibleRequests.push(getAbsolutePath(context, sourceURL.slice(1), sourceRoot));
  186. }
  187. const result = await fetchPathsFromFilesystem(loaderContext, possibleRequests);
  188. sourceURL = result.path;
  189. sourceContent = result.data;
  190. }
  191. return {
  192. sourceURL,
  193. sourceContent
  194. };
  195. }
  196. // 4. Relative path
  197. const sourceURL = getAbsolutePath(context, url, sourceRoot);
  198. let sourceContent;
  199. if (!skipReading) {
  200. const {
  201. data
  202. } = await fetchFromFilesystem(loaderContext, sourceURL);
  203. sourceContent = data;
  204. }
  205. return {
  206. sourceURL,
  207. sourceContent
  208. };
  209. }