UNPKG

6.75 kBJavaScriptView Raw
1// Copyright 2014 Simon Lydell
2// X11 (“MIT”) Licensed. (See LICENSE.)
3
4var sourceMappingURL = require("source-map-url")
5var resolveUrl = require("./resolve-url")
6var urix = require("urix")
7var atob = require("atob")
8
9
10
11function callbackAsync(callback, error, result) {
12 setImmediate(function() { callback(error, result) })
13}
14
15function parseMapToJSON(string) {
16 return JSON.parse(string.replace(/^\)\]\}'/, ""))
17}
18
19
20
21function resolveSourceMap(code, codeUrl, read, callback) {
22 var mapData
23 try {
24 mapData = resolveSourceMapHelper(code, codeUrl)
25 } catch (error) {
26 return callbackAsync(callback, error)
27 }
28 if (!mapData || mapData.map) {
29 return callbackAsync(callback, null, mapData)
30 }
31 read(mapData.url, function(error, result) {
32 if (error) {
33 return callback(error)
34 }
35 try {
36 mapData.map = parseMapToJSON(String(result))
37 } catch (error) {
38 return callback(error)
39 }
40 callback(null, mapData)
41 })
42}
43
44function resolveSourceMapSync(code, codeUrl, read) {
45 var mapData = resolveSourceMapHelper(code, codeUrl)
46 if (!mapData || mapData.map) {
47 return mapData
48 }
49 mapData.map = parseMapToJSON(String(read(mapData.url)))
50 return mapData
51}
52
53var dataUriRegex = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/
54var jsonMimeTypeRegex = /^(?:application|text)\/json$/
55
56function resolveSourceMapHelper(code, codeUrl) {
57 codeUrl = urix(codeUrl)
58
59 var url = sourceMappingURL.getFrom(code)
60 if (!url) {
61 return null
62 }
63
64 var dataUri = url.match(dataUriRegex)
65 if (dataUri) {
66 var mimeType = dataUri[1]
67 var lastParameter = dataUri[2]
68 var encoded = dataUri[3]
69 if (!jsonMimeTypeRegex.test(mimeType)) {
70 throw new Error("Unuseful data uri mime type: " + (mimeType || "text/plain"))
71 }
72 return {
73 sourceMappingURL: url,
74 url: null,
75 sourcesRelativeTo: codeUrl,
76 map: parseMapToJSON(lastParameter === ";base64" ? atob(encoded) : decodeURIComponent(encoded))
77 }
78 }
79
80 var mapUrl = resolveUrl(codeUrl, url)
81 return {
82 sourceMappingURL: url,
83 url: mapUrl,
84 sourcesRelativeTo: mapUrl,
85 map: null
86 }
87}
88
89
90
91function resolveSources(map, mapUrl, read, options, callback) {
92 if (typeof options === "function") {
93 callback = options
94 options = {}
95 }
96 var pending = map.sources.length
97 var errored = false
98 var result = {
99 sourcesResolved: [],
100 sourcesContent: []
101 }
102
103 var done = function(error) {
104 if (errored) {
105 return
106 }
107 if (error) {
108 errored = true
109 return callback(error)
110 }
111 pending--
112 if (pending === 0) {
113 callback(null, result)
114 }
115 }
116
117 resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) {
118 result.sourcesResolved[index] = fullUrl
119 if (typeof sourceContent === "string") {
120 result.sourcesContent[index] = sourceContent
121 callbackAsync(done, null)
122 } else {
123 read(fullUrl, function(error, source) {
124 result.sourcesContent[index] = String(source)
125 done(error)
126 })
127 }
128 })
129}
130
131function resolveSourcesSync(map, mapUrl, read, options) {
132 var result = {
133 sourcesResolved: [],
134 sourcesContent: []
135 }
136 resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) {
137 result.sourcesResolved[index] = fullUrl
138 if (read !== null) {
139 if (typeof sourceContent === "string") {
140 result.sourcesContent[index] = sourceContent
141 } else {
142 result.sourcesContent[index] = String(read(fullUrl))
143 }
144 }
145 })
146 return result
147}
148
149var endingSlash = /\/?$/
150
151function resolveSourcesHelper(map, mapUrl, options, fn) {
152 options = options || {}
153 mapUrl = urix(mapUrl)
154 var fullUrl
155 var sourceContent
156 var sourceRoot
157 for (var index = 0, len = map.sources.length; index < len; index++) {
158 sourceRoot = null
159 if (typeof options.sourceRoot === "string") {
160 sourceRoot = options.sourceRoot
161 } else if (typeof map.sourceRoot === "string" && options.sourceRoot !== false) {
162 sourceRoot = map.sourceRoot
163 }
164 // If the sourceRoot is the empty string, it is equivalent to not setting
165 // the property at all.
166 if (sourceRoot === null || sourceRoot === '') {
167 fullUrl = resolveUrl(mapUrl, map.sources[index])
168 } else {
169 // Make sure that the sourceRoot ends with a slash, so that `/scripts/subdir` becomes
170 // `/scripts/subdir/<source>`, not `/scripts/<source>`. Pointing to a file as source root
171 // does not make sense.
172 fullUrl = resolveUrl(mapUrl, sourceRoot.replace(endingSlash, "/"), map.sources[index])
173 }
174 sourceContent = (map.sourcesContent || [])[index]
175 fn(fullUrl, sourceContent, index)
176 }
177}
178
179
180
181function resolve(code, codeUrl, read, options, callback) {
182 if (typeof options === "function") {
183 callback = options
184 options = {}
185 }
186 if (code === null) {
187 var mapUrl = codeUrl
188 read(mapUrl, function(error, result) {
189 if (error) {
190 return callback(error)
191 }
192 var map
193 try {
194 map = parseMapToJSON(String(result))
195 } catch (error) {
196 return callback(error)
197 }
198 _resolveSources({
199 sourceMappingURL: null,
200 url: mapUrl,
201 sourcesRelativeTo: mapUrl,
202 map: map
203 })
204 })
205 } else {
206 resolveSourceMap(code, codeUrl, read, function(error, mapData) {
207 if (error) {
208 return callback(error)
209 }
210 if (!mapData) {
211 return callback(null, null)
212 }
213 _resolveSources(mapData)
214 })
215 }
216
217 function _resolveSources(mapData) {
218 resolveSources(mapData.map, mapData.sourcesRelativeTo, read, options, function(error, result) {
219 if (error) {
220 return callback(error)
221 }
222 mapData.sourcesResolved = result.sourcesResolved
223 mapData.sourcesContent = result.sourcesContent
224 callback(null, mapData)
225 })
226 }
227}
228
229function resolveSync(code, codeUrl, read, options) {
230 var mapData
231 if (code === null) {
232 var mapUrl = codeUrl
233 mapData = {
234 sourceMappingURL: null,
235 url: mapUrl,
236 sourcesRelativeTo: mapUrl,
237 map: parseMapToJSON(String(read(mapUrl)))
238 }
239 } else {
240 mapData = resolveSourceMapSync(code, codeUrl, read)
241 if (!mapData) {
242 return null
243 }
244 }
245 var result = resolveSourcesSync(mapData.map, mapData.sourcesRelativeTo, read, options)
246 mapData.sourcesResolved = result.sourcesResolved
247 mapData.sourcesContent = result.sourcesContent
248 return mapData
249}
250
251
252
253module.exports = {
254 resolveSourceMap: resolveSourceMap,
255 resolveSourceMapSync: resolveSourceMapSync,
256 resolveSources: resolveSources,
257 resolveSourcesSync: resolveSourcesSync,
258 resolve: resolve,
259 resolveSync: resolveSync,
260 parseMapToJSON: parseMapToJSON
261}