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.

source-map-resolve-node.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. var sourceMappingURL = require("source-map-url")
  2. var resolveUrl = require("./resolve-url")
  3. var decodeUriComponent = require("./decode-uri-component")
  4. var urix = require("urix")
  5. var atob = require("atob")
  6. function callbackAsync(callback, error, result) {
  7. setImmediate(function() { callback(error, result) })
  8. }
  9. function parseMapToJSON(string, data) {
  10. try {
  11. return JSON.parse(string.replace(/^\)\]\}'/, ""))
  12. } catch (error) {
  13. error.sourceMapData = data
  14. throw error
  15. }
  16. }
  17. function readSync(read, url, data) {
  18. var readUrl = decodeUriComponent(url)
  19. try {
  20. return String(read(readUrl))
  21. } catch (error) {
  22. error.sourceMapData = data
  23. throw error
  24. }
  25. }
  26. function resolveSourceMap(code, codeUrl, read, callback) {
  27. var mapData
  28. try {
  29. mapData = resolveSourceMapHelper(code, codeUrl)
  30. } catch (error) {
  31. return callbackAsync(callback, error)
  32. }
  33. if (!mapData || mapData.map) {
  34. return callbackAsync(callback, null, mapData)
  35. }
  36. var readUrl = decodeUriComponent(mapData.url)
  37. read(readUrl, function(error, result) {
  38. if (error) {
  39. error.sourceMapData = mapData
  40. return callback(error)
  41. }
  42. mapData.map = String(result)
  43. try {
  44. mapData.map = parseMapToJSON(mapData.map, mapData)
  45. } catch (error) {
  46. return callback(error)
  47. }
  48. callback(null, mapData)
  49. })
  50. }
  51. function resolveSourceMapSync(code, codeUrl, read) {
  52. var mapData = resolveSourceMapHelper(code, codeUrl)
  53. if (!mapData || mapData.map) {
  54. return mapData
  55. }
  56. mapData.map = readSync(read, mapData.url, mapData)
  57. mapData.map = parseMapToJSON(mapData.map, mapData)
  58. return mapData
  59. }
  60. var dataUriRegex = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/
  61. /**
  62. * The media type for JSON text is application/json.
  63. *
  64. * {@link https://tools.ietf.org/html/rfc8259#section-11 | IANA Considerations }
  65. *
  66. * `text/json` is non-standard media type
  67. */
  68. var jsonMimeTypeRegex = /^(?:application|text)\/json$/
  69. /**
  70. * JSON text exchanged between systems that are not part of a closed ecosystem
  71. * MUST be encoded using UTF-8.
  72. *
  73. * {@link https://tools.ietf.org/html/rfc8259#section-8.1 | Character Encoding}
  74. */
  75. var jsonCharacterEncoding = "utf-8"
  76. function base64ToBuf(b64) {
  77. var binStr = atob(b64)
  78. var len = binStr.length
  79. var arr = new Uint8Array(len)
  80. for (var i = 0; i < len; i++) {
  81. arr[i] = binStr.charCodeAt(i)
  82. }
  83. return arr
  84. }
  85. function decodeBase64String(b64) {
  86. if (typeof TextDecoder === "undefined" || typeof Uint8Array === "undefined") {
  87. return atob(b64)
  88. }
  89. var buf = base64ToBuf(b64);
  90. // Note: `decoder.decode` method will throw a `DOMException` with the
  91. // `"EncodingError"` value when an coding error is found.
  92. var decoder = new TextDecoder(jsonCharacterEncoding, {fatal: true})
  93. return decoder.decode(buf);
  94. }
  95. function resolveSourceMapHelper(code, codeUrl) {
  96. codeUrl = urix(codeUrl)
  97. var url = sourceMappingURL.getFrom(code)
  98. if (!url) {
  99. return null
  100. }
  101. var dataUri = url.match(dataUriRegex)
  102. if (dataUri) {
  103. var mimeType = dataUri[1] || "text/plain"
  104. var lastParameter = dataUri[2] || ""
  105. var encoded = dataUri[3] || ""
  106. var data = {
  107. sourceMappingURL: url,
  108. url: null,
  109. sourcesRelativeTo: codeUrl,
  110. map: encoded
  111. }
  112. if (!jsonMimeTypeRegex.test(mimeType)) {
  113. var error = new Error("Unuseful data uri mime type: " + mimeType)
  114. error.sourceMapData = data
  115. throw error
  116. }
  117. try {
  118. data.map = parseMapToJSON(
  119. lastParameter === ";base64" ? decodeBase64String(encoded) : decodeURIComponent(encoded),
  120. data
  121. )
  122. } catch (error) {
  123. error.sourceMapData = data
  124. throw error
  125. }
  126. return data
  127. }
  128. var mapUrl = resolveUrl(codeUrl, url)
  129. return {
  130. sourceMappingURL: url,
  131. url: mapUrl,
  132. sourcesRelativeTo: mapUrl,
  133. map: null
  134. }
  135. }
  136. function resolveSources(map, mapUrl, read, options, callback) {
  137. if (typeof options === "function") {
  138. callback = options
  139. options = {}
  140. }
  141. var pending = map.sources ? map.sources.length : 0
  142. var result = {
  143. sourcesResolved: [],
  144. sourcesContent: []
  145. }
  146. if (pending === 0) {
  147. callbackAsync(callback, null, result)
  148. return
  149. }
  150. var done = function() {
  151. pending--
  152. if (pending === 0) {
  153. callback(null, result)
  154. }
  155. }
  156. resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) {
  157. result.sourcesResolved[index] = fullUrl
  158. if (typeof sourceContent === "string") {
  159. result.sourcesContent[index] = sourceContent
  160. callbackAsync(done, null)
  161. } else {
  162. var readUrl = decodeUriComponent(fullUrl)
  163. read(readUrl, function(error, source) {
  164. result.sourcesContent[index] = error ? error : String(source)
  165. done()
  166. })
  167. }
  168. })
  169. }
  170. function resolveSourcesSync(map, mapUrl, read, options) {
  171. var result = {
  172. sourcesResolved: [],
  173. sourcesContent: []
  174. }
  175. if (!map.sources || map.sources.length === 0) {
  176. return result
  177. }
  178. resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) {
  179. result.sourcesResolved[index] = fullUrl
  180. if (read !== null) {
  181. if (typeof sourceContent === "string") {
  182. result.sourcesContent[index] = sourceContent
  183. } else {
  184. var readUrl = decodeUriComponent(fullUrl)
  185. try {
  186. result.sourcesContent[index] = String(read(readUrl))
  187. } catch (error) {
  188. result.sourcesContent[index] = error
  189. }
  190. }
  191. }
  192. })
  193. return result
  194. }
  195. var endingSlash = /\/?$/
  196. function resolveSourcesHelper(map, mapUrl, options, fn) {
  197. options = options || {}
  198. mapUrl = urix(mapUrl)
  199. var fullUrl
  200. var sourceContent
  201. var sourceRoot
  202. for (var index = 0, len = map.sources.length; index < len; index++) {
  203. sourceRoot = null
  204. if (typeof options.sourceRoot === "string") {
  205. sourceRoot = options.sourceRoot
  206. } else if (typeof map.sourceRoot === "string" && options.sourceRoot !== false) {
  207. sourceRoot = map.sourceRoot
  208. }
  209. // If the sourceRoot is the empty string, it is equivalent to not setting
  210. // the property at all.
  211. if (sourceRoot === null || sourceRoot === '') {
  212. fullUrl = resolveUrl(mapUrl, map.sources[index])
  213. } else {
  214. // Make sure that the sourceRoot ends with a slash, so that `/scripts/subdir` becomes
  215. // `/scripts/subdir/<source>`, not `/scripts/<source>`. Pointing to a file as source root
  216. // does not make sense.
  217. fullUrl = resolveUrl(mapUrl, sourceRoot.replace(endingSlash, "/"), map.sources[index])
  218. }
  219. sourceContent = (map.sourcesContent || [])[index]
  220. fn(fullUrl, sourceContent, index)
  221. }
  222. }
  223. function resolve(code, codeUrl, read, options, callback) {
  224. if (typeof options === "function") {
  225. callback = options
  226. options = {}
  227. }
  228. if (code === null) {
  229. var mapUrl = codeUrl
  230. var data = {
  231. sourceMappingURL: null,
  232. url: mapUrl,
  233. sourcesRelativeTo: mapUrl,
  234. map: null
  235. }
  236. var readUrl = decodeUriComponent(mapUrl)
  237. read(readUrl, function(error, result) {
  238. if (error) {
  239. error.sourceMapData = data
  240. return callback(error)
  241. }
  242. data.map = String(result)
  243. try {
  244. data.map = parseMapToJSON(data.map, data)
  245. } catch (error) {
  246. return callback(error)
  247. }
  248. _resolveSources(data)
  249. })
  250. } else {
  251. resolveSourceMap(code, codeUrl, read, function(error, mapData) {
  252. if (error) {
  253. return callback(error)
  254. }
  255. if (!mapData) {
  256. return callback(null, null)
  257. }
  258. _resolveSources(mapData)
  259. })
  260. }
  261. function _resolveSources(mapData) {
  262. resolveSources(mapData.map, mapData.sourcesRelativeTo, read, options, function(error, result) {
  263. if (error) {
  264. return callback(error)
  265. }
  266. mapData.sourcesResolved = result.sourcesResolved
  267. mapData.sourcesContent = result.sourcesContent
  268. callback(null, mapData)
  269. })
  270. }
  271. }
  272. function resolveSync(code, codeUrl, read, options) {
  273. var mapData
  274. if (code === null) {
  275. var mapUrl = codeUrl
  276. mapData = {
  277. sourceMappingURL: null,
  278. url: mapUrl,
  279. sourcesRelativeTo: mapUrl,
  280. map: null
  281. }
  282. mapData.map = readSync(read, mapUrl, mapData)
  283. mapData.map = parseMapToJSON(mapData.map, mapData)
  284. } else {
  285. mapData = resolveSourceMapSync(code, codeUrl, read)
  286. if (!mapData) {
  287. return null
  288. }
  289. }
  290. var result = resolveSourcesSync(mapData.map, mapData.sourcesRelativeTo, read, options)
  291. mapData.sourcesResolved = result.sourcesResolved
  292. mapData.sourcesContent = result.sourcesContent
  293. return mapData
  294. }
  295. module.exports = {
  296. resolveSourceMap: resolveSourceMap,
  297. resolveSourceMapSync: resolveSourceMapSync,
  298. resolveSources: resolveSources,
  299. resolveSourcesSync: resolveSourcesSync,
  300. resolve: resolve,
  301. resolveSync: resolveSync,
  302. parseMapToJSON: parseMapToJSON
  303. }