UNPKG

7.93 kBJavaScriptView Raw
1var fs = require('fs');
2var path = require('path');
3
4var isAllowedResource = require('./is-allowed-resource');
5var matchDataUri = require('./match-data-uri');
6var rebaseLocalMap = require('./rebase-local-map');
7var rebaseRemoteMap = require('./rebase-remote-map');
8
9var Token = require('../tokenizer/token');
10var hasProtocol = require('../utils/has-protocol');
11var isDataUriResource = require('../utils/is-data-uri-resource');
12var isRemoteResource = require('../utils/is-remote-resource');
13
14var MAP_MARKER_PATTERN = /^\/\*# sourceMappingURL=(\S+) \*\/$/;
15
16function applySourceMaps(tokens, context, callback) {
17 var applyContext = {
18 callback: callback,
19 fetch: context.options.fetch,
20 index: 0,
21 inline: context.options.inline,
22 inlineRequest: context.options.inlineRequest,
23 inlineTimeout: context.options.inlineTimeout,
24 inputSourceMapTracker: context.inputSourceMapTracker,
25 localOnly: context.localOnly,
26 processedTokens: [],
27 rebaseTo: context.options.rebaseTo,
28 sourceTokens: tokens,
29 warnings: context.warnings
30 };
31
32 return context.options.sourceMap && tokens.length > 0 ?
33 doApplySourceMaps(applyContext) :
34 callback(tokens);
35}
36
37function doApplySourceMaps(applyContext) {
38 var singleSourceTokens = [];
39 var lastSource = findTokenSource(applyContext.sourceTokens[0]);
40 var source;
41 var token;
42 var l;
43
44 for (l = applyContext.sourceTokens.length; applyContext.index < l; applyContext.index++) {
45 token = applyContext.sourceTokens[applyContext.index];
46 source = findTokenSource(token);
47
48 if (source != lastSource) {
49 singleSourceTokens = [];
50 lastSource = source;
51 }
52
53 singleSourceTokens.push(token);
54 applyContext.processedTokens.push(token);
55
56 if (token[0] == Token.COMMENT && MAP_MARKER_PATTERN.test(token[1])) {
57 return fetchAndApplySourceMap(token[1], source, singleSourceTokens, applyContext);
58 }
59 }
60
61 return applyContext.callback(applyContext.processedTokens);
62}
63
64function findTokenSource(token) {
65 var scope;
66 var metadata;
67
68 if (token[0] == Token.AT_RULE || token[0] == Token.COMMENT) {
69 metadata = token[2][0];
70 } else {
71 scope = token[1][0];
72 metadata = scope[2][0];
73 }
74
75 return metadata[2];
76}
77
78function fetchAndApplySourceMap(sourceMapComment, source, singleSourceTokens, applyContext) {
79 return extractInputSourceMapFrom(sourceMapComment, applyContext, function (inputSourceMap) {
80 if (inputSourceMap) {
81 applyContext.inputSourceMapTracker.track(source, inputSourceMap);
82 applySourceMapRecursively(singleSourceTokens, applyContext.inputSourceMapTracker);
83 }
84
85 applyContext.index++;
86 return doApplySourceMaps(applyContext);
87 });
88}
89
90function extractInputSourceMapFrom(sourceMapComment, applyContext, whenSourceMapReady) {
91 var uri = MAP_MARKER_PATTERN.exec(sourceMapComment)[1];
92 var absoluteUri;
93 var sourceMap;
94 var rebasedMap;
95
96 if (isDataUriResource(uri)) {
97 sourceMap = extractInputSourceMapFromDataUri(uri);
98 return whenSourceMapReady(sourceMap);
99 } else if (isRemoteResource(uri)) {
100 return loadInputSourceMapFromRemoteUri(uri, applyContext, function (sourceMap) {
101 var parsedMap;
102
103 if (sourceMap) {
104 parsedMap = JSON.parse(sourceMap);
105 rebasedMap = rebaseRemoteMap(parsedMap, uri);
106 whenSourceMapReady(rebasedMap);
107 } else {
108 whenSourceMapReady(null);
109 }
110 });
111 } else {
112 // at this point `uri` is already rebased, see lib/reader/rebase.js#rebaseSourceMapComment
113 // it is rebased to be consistent with rebasing other URIs
114 // however here we need to resolve it back to read it from disk
115 absoluteUri = path.resolve(applyContext.rebaseTo, uri);
116 sourceMap = loadInputSourceMapFromLocalUri(absoluteUri, applyContext);
117
118 if (sourceMap) {
119 rebasedMap = rebaseLocalMap(sourceMap, absoluteUri, applyContext.rebaseTo);
120 return whenSourceMapReady(rebasedMap);
121 } else {
122 return whenSourceMapReady(null);
123 }
124 }
125}
126
127function extractInputSourceMapFromDataUri(uri) {
128 var dataUriMatch = matchDataUri(uri);
129 var charset = dataUriMatch[2] ? dataUriMatch[2].split(/[=;]/)[2] : 'us-ascii';
130 var encoding = dataUriMatch[3] ? dataUriMatch[3].split(';')[1] : 'utf8';
131 var data = encoding == 'utf8' ? global.unescape(dataUriMatch[4]) : dataUriMatch[4];
132
133 var buffer = new Buffer(data, encoding);
134 buffer.charset = charset;
135
136 return JSON.parse(buffer.toString());
137}
138
139function loadInputSourceMapFromRemoteUri(uri, applyContext, whenLoaded) {
140 var isAllowed = isAllowedResource(uri, true, applyContext.inline);
141 var isRuntimeResource = !hasProtocol(uri);
142
143 if (applyContext.localOnly) {
144 applyContext.warnings.push('Cannot fetch remote resource from "' + uri + '" as no callback given.');
145 return whenLoaded(null);
146 } else if (isRuntimeResource) {
147 applyContext.warnings.push('Cannot fetch "' + uri + '" as no protocol given.');
148 return whenLoaded(null);
149 } else if (!isAllowed) {
150 applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
151 return whenLoaded(null);
152 }
153
154 applyContext.fetch(uri, applyContext.inlineRequest, applyContext.inlineTimeout, function (error, body) {
155 if (error) {
156 applyContext.warnings.push('Missing source map at "' + uri + '" - ' + error);
157 return whenLoaded(null);
158 }
159
160 whenLoaded(body);
161 });
162}
163
164function loadInputSourceMapFromLocalUri(uri, applyContext) {
165 var isAllowed = isAllowedResource(uri, false, applyContext.inline);
166 var sourceMap;
167
168 if (!fs.existsSync(uri) || !fs.statSync(uri).isFile()) {
169 applyContext.warnings.push('Ignoring local source map at "' + uri + '" as resource is missing.');
170 return null;
171 } else if (!isAllowed) {
172 applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
173 return null;
174 }
175
176 sourceMap = fs.readFileSync(uri, 'utf-8');
177 return JSON.parse(sourceMap);
178}
179
180function applySourceMapRecursively(tokens, inputSourceMapTracker) {
181 var token;
182 var i, l;
183
184 for (i = 0, l = tokens.length; i < l; i++) {
185 token = tokens[i];
186
187 switch (token[0]) {
188 case Token.AT_RULE:
189 applySourceMapTo(token, inputSourceMapTracker);
190 break;
191 case Token.AT_RULE_BLOCK:
192 applySourceMapRecursively(token[1], inputSourceMapTracker);
193 applySourceMapRecursively(token[2], inputSourceMapTracker);
194 break;
195 case Token.AT_RULE_BLOCK_SCOPE:
196 applySourceMapTo(token, inputSourceMapTracker);
197 break;
198 case Token.NESTED_BLOCK:
199 applySourceMapRecursively(token[1], inputSourceMapTracker);
200 applySourceMapRecursively(token[2], inputSourceMapTracker);
201 break;
202 case Token.NESTED_BLOCK_SCOPE:
203 applySourceMapTo(token, inputSourceMapTracker);
204 break;
205 case Token.COMMENT:
206 applySourceMapTo(token, inputSourceMapTracker);
207 break;
208 case Token.PROPERTY:
209 applySourceMapRecursively(token, inputSourceMapTracker);
210 break;
211 case Token.PROPERTY_BLOCK:
212 applySourceMapRecursively(token[1], inputSourceMapTracker);
213 break;
214 case Token.PROPERTY_NAME:
215 applySourceMapTo(token, inputSourceMapTracker);
216 break;
217 case Token.PROPERTY_VALUE:
218 applySourceMapTo(token, inputSourceMapTracker);
219 break;
220 case Token.RULE:
221 applySourceMapRecursively(token[1], inputSourceMapTracker);
222 applySourceMapRecursively(token[2], inputSourceMapTracker);
223 break;
224 case Token.RULE_SCOPE:
225 applySourceMapTo(token, inputSourceMapTracker);
226 }
227 }
228
229 return tokens;
230}
231
232function applySourceMapTo(token, inputSourceMapTracker) {
233 var value = token[1];
234 var metadata = token[2];
235 var newMetadata = [];
236 var i, l;
237
238 for (i = 0, l = metadata.length; i < l; i++) {
239 newMetadata.push(inputSourceMapTracker.originalPositionFor(metadata[i], value.length));
240 }
241
242 token[2] = newMetadata;
243}
244
245module.exports = applySourceMaps;