123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- var fs = require('fs');
- var path = require('path');
- var isAllowedResource = require('./is-allowed-resource');
- var matchDataUri = require('./match-data-uri');
- var rebaseLocalMap = require('./rebase-local-map');
- var rebaseRemoteMap = require('./rebase-remote-map');
- var Token = require('../tokenizer/token');
- var hasProtocol = require('../utils/has-protocol');
- var isDataUriResource = require('../utils/is-data-uri-resource');
- var isRemoteResource = require('../utils/is-remote-resource');
- var MAP_MARKER_PATTERN = /^\/\*# sourceMappingURL=(\S+) \*\/$/;
- function applySourceMaps(tokens, context, callback) {
- var applyContext = {
- callback: callback,
- fetch: context.options.fetch,
- index: 0,
- inline: context.options.inline,
- inlineRequest: context.options.inlineRequest,
- inlineTimeout: context.options.inlineTimeout,
- inputSourceMapTracker: context.inputSourceMapTracker,
- localOnly: context.localOnly,
- processedTokens: [],
- rebaseTo: context.options.rebaseTo,
- sourceTokens: tokens,
- warnings: context.warnings
- };
- return context.options.sourceMap && tokens.length > 0 ?
- doApplySourceMaps(applyContext) :
- callback(tokens);
- }
- function doApplySourceMaps(applyContext) {
- var singleSourceTokens = [];
- var lastSource = findTokenSource(applyContext.sourceTokens[0]);
- var source;
- var token;
- var l;
- for (l = applyContext.sourceTokens.length; applyContext.index < l; applyContext.index++) {
- token = applyContext.sourceTokens[applyContext.index];
- source = findTokenSource(token);
- if (source != lastSource) {
- singleSourceTokens = [];
- lastSource = source;
- }
- singleSourceTokens.push(token);
- applyContext.processedTokens.push(token);
- if (token[0] == Token.COMMENT && MAP_MARKER_PATTERN.test(token[1])) {
- return fetchAndApplySourceMap(token[1], source, singleSourceTokens, applyContext);
- }
- }
- return applyContext.callback(applyContext.processedTokens);
- }
- function findTokenSource(token) {
- var scope;
- var metadata;
- if (token[0] == Token.AT_RULE || token[0] == Token.COMMENT) {
- metadata = token[2][0];
- } else {
- scope = token[1][0];
- metadata = scope[2][0];
- }
- return metadata[2];
- }
- function fetchAndApplySourceMap(sourceMapComment, source, singleSourceTokens, applyContext) {
- return extractInputSourceMapFrom(sourceMapComment, applyContext, function (inputSourceMap) {
- if (inputSourceMap) {
- applyContext.inputSourceMapTracker.track(source, inputSourceMap);
- applySourceMapRecursively(singleSourceTokens, applyContext.inputSourceMapTracker);
- }
- applyContext.index++;
- return doApplySourceMaps(applyContext);
- });
- }
- function extractInputSourceMapFrom(sourceMapComment, applyContext, whenSourceMapReady) {
- var uri = MAP_MARKER_PATTERN.exec(sourceMapComment)[1];
- var absoluteUri;
- var sourceMap;
- var rebasedMap;
- if (isDataUriResource(uri)) {
- sourceMap = extractInputSourceMapFromDataUri(uri);
- return whenSourceMapReady(sourceMap);
- } else if (isRemoteResource(uri)) {
- return loadInputSourceMapFromRemoteUri(uri, applyContext, function (sourceMap) {
- var parsedMap;
- if (sourceMap) {
- parsedMap = JSON.parse(sourceMap);
- rebasedMap = rebaseRemoteMap(parsedMap, uri);
- whenSourceMapReady(rebasedMap);
- } else {
- whenSourceMapReady(null);
- }
- });
- } else {
- // at this point `uri` is already rebased, see lib/reader/rebase.js#rebaseSourceMapComment
- // it is rebased to be consistent with rebasing other URIs
- // however here we need to resolve it back to read it from disk
- absoluteUri = path.resolve(applyContext.rebaseTo, uri);
- sourceMap = loadInputSourceMapFromLocalUri(absoluteUri, applyContext);
- if (sourceMap) {
- rebasedMap = rebaseLocalMap(sourceMap, absoluteUri, applyContext.rebaseTo);
- return whenSourceMapReady(rebasedMap);
- } else {
- return whenSourceMapReady(null);
- }
- }
- }
- function extractInputSourceMapFromDataUri(uri) {
- var dataUriMatch = matchDataUri(uri);
- var charset = dataUriMatch[2] ? dataUriMatch[2].split(/[=;]/)[2] : 'us-ascii';
- var encoding = dataUriMatch[3] ? dataUriMatch[3].split(';')[1] : 'utf8';
- var data = encoding == 'utf8' ? global.unescape(dataUriMatch[4]) : dataUriMatch[4];
- var buffer = new Buffer(data, encoding);
- buffer.charset = charset;
- return JSON.parse(buffer.toString());
- }
- function loadInputSourceMapFromRemoteUri(uri, applyContext, whenLoaded) {
- var isAllowed = isAllowedResource(uri, true, applyContext.inline);
- var isRuntimeResource = !hasProtocol(uri);
- if (applyContext.localOnly) {
- applyContext.warnings.push('Cannot fetch remote resource from "' + uri + '" as no callback given.');
- return whenLoaded(null);
- } else if (isRuntimeResource) {
- applyContext.warnings.push('Cannot fetch "' + uri + '" as no protocol given.');
- return whenLoaded(null);
- } else if (!isAllowed) {
- applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
- return whenLoaded(null);
- }
- applyContext.fetch(uri, applyContext.inlineRequest, applyContext.inlineTimeout, function (error, body) {
- if (error) {
- applyContext.warnings.push('Missing source map at "' + uri + '" - ' + error);
- return whenLoaded(null);
- }
- whenLoaded(body);
- });
- }
- function loadInputSourceMapFromLocalUri(uri, applyContext) {
- var isAllowed = isAllowedResource(uri, false, applyContext.inline);
- var sourceMap;
- if (!fs.existsSync(uri) || !fs.statSync(uri).isFile()) {
- applyContext.warnings.push('Ignoring local source map at "' + uri + '" as resource is missing.');
- return null;
- } else if (!isAllowed) {
- applyContext.warnings.push('Cannot fetch "' + uri + '" as resource is not allowed.');
- return null;
- }
- sourceMap = fs.readFileSync(uri, 'utf-8');
- return JSON.parse(sourceMap);
- }
- function applySourceMapRecursively(tokens, inputSourceMapTracker) {
- var token;
- var i, l;
- for (i = 0, l = tokens.length; i < l; i++) {
- token = tokens[i];
- switch (token[0]) {
- case Token.AT_RULE:
- applySourceMapTo(token, inputSourceMapTracker);
- break;
- case Token.AT_RULE_BLOCK:
- applySourceMapRecursively(token[1], inputSourceMapTracker);
- applySourceMapRecursively(token[2], inputSourceMapTracker);
- break;
- case Token.AT_RULE_BLOCK_SCOPE:
- applySourceMapTo(token, inputSourceMapTracker);
- break;
- case Token.NESTED_BLOCK:
- applySourceMapRecursively(token[1], inputSourceMapTracker);
- applySourceMapRecursively(token[2], inputSourceMapTracker);
- break;
- case Token.NESTED_BLOCK_SCOPE:
- applySourceMapTo(token, inputSourceMapTracker);
- break;
- case Token.COMMENT:
- applySourceMapTo(token, inputSourceMapTracker);
- break;
- case Token.PROPERTY:
- applySourceMapRecursively(token, inputSourceMapTracker);
- break;
- case Token.PROPERTY_BLOCK:
- applySourceMapRecursively(token[1], inputSourceMapTracker);
- break;
- case Token.PROPERTY_NAME:
- applySourceMapTo(token, inputSourceMapTracker);
- break;
- case Token.PROPERTY_VALUE:
- applySourceMapTo(token, inputSourceMapTracker);
- break;
- case Token.RULE:
- applySourceMapRecursively(token[1], inputSourceMapTracker);
- applySourceMapRecursively(token[2], inputSourceMapTracker);
- break;
- case Token.RULE_SCOPE:
- applySourceMapTo(token, inputSourceMapTracker);
- }
- }
- return tokens;
- }
- function applySourceMapTo(token, inputSourceMapTracker) {
- var value = token[1];
- var metadata = token[2];
- var newMetadata = [];
- var i, l;
- for (i = 0, l = metadata.length; i < l; i++) {
- newMetadata.push(inputSourceMapTracker.originalPositionFor(metadata[i], value.length));
- }
- token[2] = newMetadata;
- }
- module.exports = applySourceMaps;
|