270 lines
8.6 KiB
JavaScript
270 lines
8.6 KiB
JavaScript
|
'use strict';
|
||
|
var through = require('through2');
|
||
|
var fs = require('graceful-fs');
|
||
|
var path = require('path');
|
||
|
var File = require('vinyl');
|
||
|
var convert = require('convert-source-map');
|
||
|
var stripBom = require('strip-bom');
|
||
|
|
||
|
var PLUGIN_NAME = 'gulp-sourcemap';
|
||
|
var urlRegex = /^(https?|webpack(-[^:]+)?):\/\//;
|
||
|
|
||
|
/**
|
||
|
* Initialize source mapping chain
|
||
|
*/
|
||
|
module.exports.init = function init(options) {
|
||
|
function sourceMapInit(file, encoding, callback) {
|
||
|
/*jshint validthis:true */
|
||
|
|
||
|
// pass through if file is null or already has a source map
|
||
|
if (file.isNull() || file.sourceMap) {
|
||
|
this.push(file);
|
||
|
return callback();
|
||
|
}
|
||
|
|
||
|
if (file.isStream()) {
|
||
|
return callback(new Error(PLUGIN_NAME + '-init: Streaming not supported'));
|
||
|
}
|
||
|
|
||
|
var fileContent = file.contents.toString();
|
||
|
var sourceMap;
|
||
|
|
||
|
if (options && options.loadMaps) {
|
||
|
var sourcePath = ''; //root path for the sources in the map
|
||
|
|
||
|
// Try to read inline source map
|
||
|
sourceMap = convert.fromSource(fileContent);
|
||
|
if (sourceMap) {
|
||
|
sourceMap = sourceMap.toObject();
|
||
|
// sources in map are relative to the source file
|
||
|
sourcePath = path.dirname(file.path);
|
||
|
fileContent = convert.removeComments(fileContent);
|
||
|
} else {
|
||
|
// look for source map comment referencing a source map file
|
||
|
var mapComment = convert.mapFileCommentRegex.exec(fileContent);
|
||
|
|
||
|
var mapFile;
|
||
|
if (mapComment) {
|
||
|
mapFile = path.resolve(path.dirname(file.path), mapComment[1] || mapComment[2]);
|
||
|
fileContent = convert.removeMapFileComments(fileContent);
|
||
|
// if no comment try map file with same name as source file
|
||
|
} else {
|
||
|
mapFile = file.path + '.map';
|
||
|
}
|
||
|
|
||
|
// sources in external map are relative to map file
|
||
|
sourcePath = path.dirname(mapFile);
|
||
|
|
||
|
try {
|
||
|
sourceMap = JSON.parse(stripBom(fs.readFileSync(mapFile, 'utf8')));
|
||
|
} catch(e) {}
|
||
|
}
|
||
|
|
||
|
// fix source paths and sourceContent for imported source map
|
||
|
if (sourceMap) {
|
||
|
sourceMap.sourcesContent = sourceMap.sourcesContent || [];
|
||
|
sourceMap.sources.forEach(function(source, i) {
|
||
|
if (source.match(urlRegex)) {
|
||
|
sourceMap.sourcesContent[i] = sourceMap.sourcesContent[i] || null;
|
||
|
return;
|
||
|
}
|
||
|
var absPath = path.resolve(sourcePath, source);
|
||
|
sourceMap.sources[i] = unixStylePath(path.relative(file.base, absPath));
|
||
|
|
||
|
if (!sourceMap.sourcesContent[i]) {
|
||
|
var sourceContent = null;
|
||
|
if (sourceMap.sourceRoot) {
|
||
|
if (sourceMap.sourceRoot.match(urlRegex)) {
|
||
|
sourceMap.sourcesContent[i] = null;
|
||
|
return;
|
||
|
}
|
||
|
absPath = path.resolve(sourcePath, sourceMap.sourceRoot, source);
|
||
|
}
|
||
|
|
||
|
// if current file: use content
|
||
|
if (absPath === file.path) {
|
||
|
sourceContent = fileContent;
|
||
|
|
||
|
// else load content from file
|
||
|
} else {
|
||
|
try {
|
||
|
if (options.debug)
|
||
|
console.log(PLUGIN_NAME + '-init: No source content for "' + source + '". Loading from file.');
|
||
|
sourceContent = stripBom(fs.readFileSync(absPath, 'utf8'));
|
||
|
} catch (e) {
|
||
|
if (options.debug)
|
||
|
console.warn(PLUGIN_NAME + '-init: source file not found: ' + absPath);
|
||
|
}
|
||
|
}
|
||
|
sourceMap.sourcesContent[i] = sourceContent;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// remove source map comment from source
|
||
|
file.contents = new Buffer(fileContent, 'utf8');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!sourceMap) {
|
||
|
// Make an empty source map
|
||
|
sourceMap = {
|
||
|
version : 3,
|
||
|
names: [],
|
||
|
mappings: '',
|
||
|
sources: [unixStylePath(file.relative)],
|
||
|
sourcesContent: [fileContent]
|
||
|
};
|
||
|
}
|
||
|
|
||
|
sourceMap.file = unixStylePath(file.relative);
|
||
|
file.sourceMap = sourceMap;
|
||
|
|
||
|
this.push(file);
|
||
|
callback();
|
||
|
}
|
||
|
|
||
|
return through.obj(sourceMapInit);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Write the source map
|
||
|
*
|
||
|
* @param options options to change the way the source map is written
|
||
|
*
|
||
|
*/
|
||
|
module.exports.write = function write(destPath, options) {
|
||
|
if (options === undefined && Object.prototype.toString.call(destPath) === '[object Object]') {
|
||
|
options = destPath;
|
||
|
destPath = undefined;
|
||
|
}
|
||
|
options = options || {};
|
||
|
|
||
|
// set defaults for options if unset
|
||
|
if (options.includeContent === undefined)
|
||
|
options.includeContent = true;
|
||
|
if (options.addComment === undefined)
|
||
|
options.addComment = true;
|
||
|
|
||
|
function sourceMapWrite(file, encoding, callback) {
|
||
|
/*jshint validthis:true */
|
||
|
|
||
|
if (file.isNull() || !file.sourceMap) {
|
||
|
this.push(file);
|
||
|
return callback();
|
||
|
}
|
||
|
|
||
|
if (file.isStream()) {
|
||
|
return callback(new Error(PLUGIN_NAME + '-write: Streaming not supported'));
|
||
|
}
|
||
|
|
||
|
var sourceMap = file.sourceMap;
|
||
|
// fix paths if Windows style paths
|
||
|
sourceMap.file = unixStylePath(file.relative);
|
||
|
sourceMap.sources = sourceMap.sources.map(function(filePath) {
|
||
|
return unixStylePath(filePath);
|
||
|
});
|
||
|
|
||
|
if (typeof options.sourceRoot === 'function') {
|
||
|
sourceMap.sourceRoot = options.sourceRoot(file);
|
||
|
} else {
|
||
|
sourceMap.sourceRoot = options.sourceRoot;
|
||
|
}
|
||
|
|
||
|
if (options.includeContent) {
|
||
|
sourceMap.sourcesContent = sourceMap.sourcesContent || [];
|
||
|
|
||
|
// load missing source content
|
||
|
for (var i = 0; i < file.sourceMap.sources.length; i++) {
|
||
|
if (!sourceMap.sourcesContent[i]) {
|
||
|
var sourcePath = path.resolve(sourceMap.sourceRoot || file.base, sourceMap.sources[i]);
|
||
|
try {
|
||
|
if (options.debug)
|
||
|
console.log(PLUGIN_NAME + '-write: No source content for "' + sourceMap.sources[i] + '". Loading from file.');
|
||
|
sourceMap.sourcesContent[i] = stripBom(fs.readFileSync(sourcePath, 'utf8'));
|
||
|
} catch (e) {
|
||
|
if (options.debug)
|
||
|
console.warn(PLUGIN_NAME + '-write: source file not found: ' + sourcePath);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (sourceMap.sourceRoot === undefined) {
|
||
|
sourceMap.sourceRoot = '/source/';
|
||
|
} else if (sourceMap.sourceRoot === null) {
|
||
|
sourceMap.sourceRoot = undefined;
|
||
|
}
|
||
|
} else {
|
||
|
delete sourceMap.sourcesContent;
|
||
|
}
|
||
|
|
||
|
var extension = file.relative.split('.').pop();
|
||
|
var commentFormatter;
|
||
|
|
||
|
switch (extension) {
|
||
|
case 'css':
|
||
|
commentFormatter = function(url) { return "\n/*# sourceMappingURL=" + url + " */\n"; };
|
||
|
break;
|
||
|
case 'js':
|
||
|
commentFormatter = function(url) { return "\n//# sourceMappingURL=" + url + "\n"; };
|
||
|
break;
|
||
|
default:
|
||
|
commentFormatter = function(url) { return ""; };
|
||
|
}
|
||
|
|
||
|
var comment, sourceMappingURLPrefix;
|
||
|
if (!destPath) {
|
||
|
// encode source map into comment
|
||
|
var base64Map = new Buffer(JSON.stringify(sourceMap)).toString('base64');
|
||
|
comment = commentFormatter('data:application/json;base64,' + base64Map);
|
||
|
} else {
|
||
|
var sourceMapPath = path.join(file.base, destPath, file.relative) + '.map';
|
||
|
// add new source map file to stream
|
||
|
var sourceMapFile = new File({
|
||
|
cwd: file.cwd,
|
||
|
base: file.base,
|
||
|
path: sourceMapPath,
|
||
|
contents: new Buffer(JSON.stringify(sourceMap)),
|
||
|
stat: {
|
||
|
isFile: function () { return true; },
|
||
|
isDirectory: function () { return false; },
|
||
|
isBlockDevice: function () { return false; },
|
||
|
isCharacterDevice: function () { return false; },
|
||
|
isSymbolicLink: function () { return false; },
|
||
|
isFIFO: function () { return false; },
|
||
|
isSocket: function () { return false; }
|
||
|
}
|
||
|
});
|
||
|
this.push(sourceMapFile);
|
||
|
|
||
|
var sourceMapPathRelative = path.relative(path.dirname(file.path), sourceMapPath);
|
||
|
|
||
|
if (options.sourceMappingURLPrefix) {
|
||
|
var prefix = '';
|
||
|
if (typeof options.sourceMappingURLPrefix === 'function') {
|
||
|
prefix = options.sourceMappingURLPrefix(file);
|
||
|
} else {
|
||
|
prefix = options.sourceMappingURLPrefix;
|
||
|
}
|
||
|
sourceMapPathRelative = prefix+path.join('/', sourceMapPathRelative);
|
||
|
}
|
||
|
comment = commentFormatter(unixStylePath(sourceMapPathRelative));
|
||
|
|
||
|
if (options.sourceMappingURL && typeof options.sourceMappingURL === 'function') {
|
||
|
comment = commentFormatter(options.sourceMappingURL(file));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// append source map comment
|
||
|
if (options.addComment)
|
||
|
file.contents = Buffer.concat([file.contents, new Buffer(comment)]);
|
||
|
|
||
|
this.push(file);
|
||
|
callback();
|
||
|
}
|
||
|
|
||
|
return through.obj(sourceMapWrite);
|
||
|
};
|
||
|
|
||
|
function unixStylePath(filePath) {
|
||
|
return filePath.split(path.sep).join('/');
|
||
|
}
|