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