index.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. 'use strict';
  2. var through = require('through2');
  3. var fs = require('fs');
  4. var path = require('path');
  5. var File = require('vinyl');
  6. var convert = require('convert-source-map');
  7. var PLUGIN_NAME = 'gulp-sourcemap';
  8. var urlRegex = /^https?:\/\//;
  9. /**
  10. * Initialize source mapping chain
  11. */
  12. module.exports.init = function init(options) {
  13. function sourceMapInit(file, encoding, callback) {
  14. /*jshint validthis:true */
  15. if (file.isNull()) {
  16. this.push(file);
  17. return callback();
  18. }
  19. if (file.isStream()) {
  20. return callback(new Error(PLUGIN_NAME + '-init: Streaming not supported'));
  21. }
  22. var fileContent = file.contents.toString();
  23. var sourceMap;
  24. if (options && options.loadMaps) {
  25. var sourcePath = ''; //root path for the sources in the map
  26. // Try to read inline source map
  27. sourceMap = convert.fromSource(fileContent);
  28. if (sourceMap) {
  29. sourceMap = sourceMap.toObject();
  30. // sources in map are relative to the source file
  31. sourcePath = path.dirname(file.path);
  32. fileContent = convert.removeComments(fileContent);
  33. } else {
  34. // look for source map comment referencing a source map file
  35. var mapComment = convert.mapFileCommentRegex.exec(fileContent);
  36. var mapFile;
  37. if (mapComment) {
  38. mapFile = path.resolve(path.dirname(file.path), mapComment[1] || mapComment[2]);
  39. fileContent = convert.removeMapFileComments(fileContent);
  40. // if no comment try map file with same name as source file
  41. } else {
  42. mapFile = file.path + '.map';
  43. }
  44. // sources in external map are relative to map file
  45. sourcePath = path.dirname(mapFile);
  46. try {
  47. sourceMap = JSON.parse(fs.readFileSync(mapFile).toString());
  48. } catch(e) {}
  49. }
  50. // fix source paths and sourceContent for imported source map
  51. if (sourceMap) {
  52. sourceMap.sourcesContent = sourceMap.sourcesContent || [];
  53. sourceMap.sources.forEach(function(source, i) {
  54. if (source.match(urlRegex)) {
  55. sourceMap.sourcesContent[i] = sourceMap.sourcesContent[i] || null;
  56. return;
  57. }
  58. var absPath = path.resolve(sourcePath, source);
  59. sourceMap.sources[i] = unixStylePath(path.relative(file.base, absPath));
  60. if (!sourceMap.sourcesContent[i]) {
  61. var sourceContent = null;
  62. if (sourceMap.sourceRoot) {
  63. if (sourceMap.sourceRoot.match(urlRegex)) {
  64. sourceMap.sourcesContent[i] = null;
  65. return;
  66. }
  67. absPath = path.resolve(sourcePath, sourceMap.sourceRoot, source);
  68. }
  69. // if current file: use content
  70. if (absPath === file.path) {
  71. sourceContent = fileContent;
  72. // else load content from file
  73. } else {
  74. try {
  75. if (options.debug)
  76. console.log(PLUGIN_NAME + '-init: No source content for "' + source + '". Loading from file.');
  77. sourceContent = fs.readFileSync(absPath).toString();
  78. } catch (e) {
  79. if (options.debug)
  80. console.warn(PLUGIN_NAME + '-init: source file not found: ' + absPath);
  81. }
  82. }
  83. sourceMap.sourcesContent[i] = sourceContent;
  84. }
  85. });
  86. // remove source map comment from source
  87. file.contents = new Buffer(fileContent, 'utf8');
  88. }
  89. }
  90. if (!sourceMap) {
  91. // Make an empty source map
  92. sourceMap = {
  93. version : 3,
  94. file: unixStylePath(file.relative),
  95. names: [],
  96. mappings: '',
  97. sources: [unixStylePath(file.relative)],
  98. sourcesContent: [fileContent]
  99. };
  100. }
  101. sourceMap.file = file.relative;
  102. file.sourceMap = sourceMap;
  103. this.push(file);
  104. callback();
  105. }
  106. return through.obj(sourceMapInit);
  107. };
  108. /**
  109. * Write the source map
  110. *
  111. * @param options options to change the way the source map is written
  112. *
  113. */
  114. module.exports.write = function write(destPath, options) {
  115. if (options === undefined && Object.prototype.toString.call(destPath) === '[object Object]') {
  116. options = destPath;
  117. destPath = undefined;
  118. }
  119. options = options || {};
  120. // set defaults for options if unset
  121. if (options.includeContent === undefined)
  122. options.includeContent = true;
  123. if (options.addComment === undefined)
  124. options.addComment = true;
  125. function sourceMapWrite(file, encoding, callback) {
  126. /*jshint validthis:true */
  127. if (file.isNull() || !file.sourceMap) {
  128. this.push(file);
  129. return callback();
  130. }
  131. if (file.isStream()) {
  132. return callback(new Error(PLUGIN_NAME + '-write: Streaming not supported'));
  133. }
  134. var sourceMap = file.sourceMap;
  135. // fix paths if Windows style paths
  136. sourceMap.file = unixStylePath(file.relative);
  137. sourceMap.sources = sourceMap.sources.map(function(filePath) {
  138. return unixStylePath(filePath);
  139. });
  140. if (options.sourceRoot) {
  141. if (typeof options.sourceRoot === 'function') {
  142. sourceMap.sourceRoot = options.sourceRoot(file);
  143. } else {
  144. sourceMap.sourceRoot = options.sourceRoot;
  145. }
  146. }
  147. if (options.includeContent) {
  148. sourceMap.sourceRoot = sourceMap.sourceRoot || '/source/';
  149. sourceMap.sourcesContent = sourceMap.sourcesContent || [];
  150. // load missing source content
  151. for (var i = 0; i < file.sourceMap.sources.length; i++) {
  152. if (!sourceMap.sourcesContent[i]) {
  153. var sourcePath = path.resolve(file.base, sourceMap.sources[i]);
  154. try {
  155. if (options.debug)
  156. console.log(PLUGIN_NAME + '-write: No source content for "' + sourceMap.sources[i] + '". Loading from file.');
  157. sourceMap.sourcesContent[i] = fs.readFileSync(sourcePath).toString();
  158. } catch (e) {
  159. if (options.debug)
  160. console.warn(PLUGIN_NAME + '-write: source file not found: ' + sourcePath);
  161. }
  162. }
  163. }
  164. } else {
  165. delete sourceMap.sourcesContent;
  166. }
  167. var extension = file.relative.split('.').pop();
  168. var commentFormatter;
  169. switch (extension) {
  170. case 'css':
  171. commentFormatter = function(url) { return "\n/*# sourceMappingURL=" + url + " */"; };
  172. break;
  173. case 'js':
  174. commentFormatter = function(url) { return "\n//# sourceMappingURL=" + url; };
  175. break;
  176. default:
  177. commentFormatter = function(url) { return ""; };
  178. }
  179. var comment;
  180. if (!destPath) {
  181. // encode source map into comment
  182. var base64Map = new Buffer(JSON.stringify(sourceMap)).toString('base64');
  183. comment = commentFormatter('data:application/json;base64,' + base64Map);
  184. } else {
  185. // add new source map file to stream
  186. var sourceMapFile = new File({
  187. cwd: file.cwd,
  188. base: file.base,
  189. path: path.join(file.base, destPath, file.relative) + '.map',
  190. contents: new Buffer(JSON.stringify(sourceMap))
  191. });
  192. this.push(sourceMapFile);
  193. comment = commentFormatter(unixStylePath(path.join(path.relative(path.dirname(file.path), file.base), destPath, file.relative) + '.map'));
  194. if (options.sourceMappingURLPrefix) {
  195. if (typeof options.sourceMappingURLPrefix === 'function') {
  196. options.sourceMappingURLPrefix = options.sourceMappingURLPrefix(file);
  197. }
  198. comment = comment.replace(/sourceMappingURL=\.*/, 'sourceMappingURL=' + options.sourceMappingURLPrefix);
  199. }
  200. }
  201. // append source map comment
  202. if (options.addComment)
  203. file.contents = Buffer.concat([file.contents, new Buffer(comment)]);
  204. this.push(file);
  205. callback();
  206. }
  207. return through.obj(sourceMapWrite);
  208. };
  209. function unixStylePath(filePath) {
  210. return filePath.split(path.sep).join('/');
  211. }