index.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. 'use strict';
  2. var path = require('path');
  3. var convert = require('convert-source-map');
  4. var memoize = require('lodash.memoize');
  5. var createGenerator = require('inline-source-map');
  6. var pathIsAbsolute = require('./lib/path-is-absolute');
  7. var mappingsFromMap = require('./lib/mappings-from-map');
  8. var protocolRx = /^[a-z]+:\/\//;
  9. /**
  10. * Rebases a relative path in 'sourceFile' to be relative
  11. * to the path where 'sourceFile' is located.
  12. *
  13. * This is necessary before adding relative paths to the
  14. * new combined map to ensure all paths are relative to their
  15. * original source.
  16. *
  17. * The 'sourceRoot' from the original source map is joined
  18. * as well to ensure the complete path.
  19. *
  20. * Resulting paths that are absolute are passed along directly.
  21. *
  22. * @param sourceFile {String} path to the original source file that references a map
  23. * @param relativeRoot {String} sourceRoot in sourceFile's map to combine with relativePath
  24. * @param relativePath {String} source path from sourceFile's map
  25. */
  26. var rebaseRelativePath = memoize(function(sourceFile, relativeRoot, relativePath) {
  27. if (!relativePath) {
  28. return relativePath;
  29. }
  30. // join relative path to root (e.g. 'src/' + 'file.js')
  31. var relativeRootedPath = relativeRoot ? path.join(relativeRoot, relativePath) : relativePath;
  32. relativeRootedPath = relativeRootedPath.replace(/\\/g, '/');
  33. sourceFile = sourceFile.replace(/\\/g, '/');
  34. if (sourceFile === relativeRootedPath || // same path,
  35. pathIsAbsolute(relativeRootedPath) || // absolute path, nor
  36. protocolRx.test(relativeRootedPath)) { // absolute protocol need rebasing
  37. return relativeRootedPath;
  38. }
  39. // make relative to source file
  40. return path.join(path.dirname(sourceFile), relativeRootedPath).replace(/\\/g, '/');
  41. }, function(a, b, c) {
  42. return a + '::' + b + '::' + c;
  43. });
  44. function resolveMap(source) {
  45. var gen = convert.fromSource(source);
  46. return gen ? gen.toObject() : null;
  47. }
  48. function hasInlinedSource(existingMap) {
  49. return existingMap.sourcesContent && !!existingMap.sourcesContent[0];
  50. }
  51. function Combiner(file, sourceRoot) {
  52. // since we include the original code in the map sourceRoot actually not needed
  53. this.generator = createGenerator({ file: file || 'generated.js', sourceRoot: sourceRoot });
  54. }
  55. Combiner.prototype._addGeneratedMap = function (sourceFile, source, offset) {
  56. this.generator.addGeneratedMappings(sourceFile, source, offset);
  57. this.generator.addSourceContent(sourceFile, source);
  58. return this;
  59. };
  60. Combiner.prototype._addExistingMap = function (sourceFile, source, existingMap, offset) {
  61. var mappings = mappingsFromMap(existingMap);
  62. // add all of the sources from the map
  63. for (var i = 0, len = existingMap.sources.length; i < len; i++) {
  64. if (!existingMap.sourcesContent) continue;
  65. this.generator.addSourceContent(
  66. rebaseRelativePath(sourceFile, existingMap.sourceRoot, existingMap.sources[i]),
  67. existingMap.sourcesContent[i]);
  68. }
  69. // add the mappings, preserving the original mapping 'source'
  70. mappings.forEach(function(mapping) {
  71. // Add the mappings one at a time because 'inline-source-map' doesn't handle
  72. // mapping source filenames. The mapping.source already takes sourceRoot into account
  73. // per the SMConsumer.eachMapping function, so pass null for the root here.
  74. this.generator.addMappings(
  75. rebaseRelativePath(sourceFile, null, mapping.source), [mapping], offset);
  76. }, this);
  77. return this;
  78. };
  79. /**
  80. * Adds map to underlying source map.
  81. * If source contains a source map comment that has the source of the original file inlined it will offset these
  82. * mappings and include them.
  83. * If no source map comment is found or it has no source inlined, mappings for the file will be generated and included
  84. *
  85. * @name addMap
  86. * @function
  87. * @param opts {Object} { sourceFile: {String}, source: {String} }
  88. * @param offset {Object} { line: {Number}, column: {Number} }
  89. */
  90. Combiner.prototype.addFile = function (opts, offset) {
  91. offset = offset || {};
  92. if (!offset.hasOwnProperty('line')) offset.line = 0;
  93. if (!offset.hasOwnProperty('column')) offset.column = 0;
  94. var existingMap = resolveMap(opts.source);
  95. return existingMap && hasInlinedSource(existingMap)
  96. ? this._addExistingMap(opts.sourceFile, opts.source, existingMap, offset)
  97. : this._addGeneratedMap(opts.sourceFile, opts.source, offset);
  98. };
  99. /**
  100. * @name base64
  101. * @function
  102. * @return {String} base64 encoded combined source map
  103. */
  104. Combiner.prototype.base64 = function () {
  105. return this.generator.base64Encode();
  106. };
  107. /**
  108. * @name comment
  109. * @function
  110. * @return {String} base64 encoded sourceMappingUrl comment of the combined source map
  111. */
  112. Combiner.prototype.comment = function () {
  113. return this.generator.inlineMappingUrl();
  114. };
  115. /**
  116. * @name create
  117. * @function
  118. * @param file {String} optional name of the generated file
  119. * @param sourceRoot {String} optional sourceRoot of the map to be generated
  120. * @return {Object} Combiner instance to which source maps can be added and later combined
  121. */
  122. exports.create = function (file, sourceRoot) { return new Combiner(file, sourceRoot); };
  123. /**
  124. * @name removeComments
  125. * @function
  126. * @param src
  127. * @return {String} src with all sourceMappingUrl comments removed
  128. */
  129. exports.removeComments = function (src) {
  130. if (!src.replace) return src;
  131. return src.replace(convert.commentRegex, '').replace(convert.mapFileCommentRegex, '');
  132. };