SourceMapDevToolPlugin.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const crypto = require("crypto");
  8. const RequestShortener = require("./RequestShortener");
  9. const ConcatSource = require("webpack-sources").ConcatSource;
  10. const RawSource = require("webpack-sources").RawSource;
  11. const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
  12. const SourceMapDevToolModuleOptionsPlugin = require("./SourceMapDevToolModuleOptionsPlugin");
  13. const basename = (name) => {
  14. if(name.indexOf("/") < 0) return name;
  15. return name.substr(name.lastIndexOf("/") + 1);
  16. };
  17. class SourceMapDevToolPlugin {
  18. constructor(options) {
  19. if(arguments.length > 1)
  20. throw new Error("SourceMapDevToolPlugin only takes one argument (pass an options object)");
  21. // TODO: remove in webpack 3
  22. if(typeof options === "string") {
  23. options = {
  24. sourceMapFilename: options
  25. };
  26. }
  27. if(!options) options = {};
  28. this.sourceMapFilename = options.filename;
  29. this.sourceMappingURLComment = options.append === false ? false : options.append || "\n//# sourceMappingURL=[url]";
  30. this.moduleFilenameTemplate = options.moduleFilenameTemplate || "webpack:///[resourcePath]";
  31. this.fallbackModuleFilenameTemplate = options.fallbackModuleFilenameTemplate || "webpack:///[resourcePath]?[hash]";
  32. this.options = options;
  33. }
  34. apply(compiler) {
  35. const sourceMapFilename = this.sourceMapFilename;
  36. const sourceMappingURLComment = this.sourceMappingURLComment;
  37. const moduleFilenameTemplate = this.moduleFilenameTemplate;
  38. const fallbackModuleFilenameTemplate = this.fallbackModuleFilenameTemplate;
  39. const requestShortener = new RequestShortener(compiler.context);
  40. const options = this.options;
  41. options.test = options.test || /\.(js|css)($|\?)/i;
  42. compiler.plugin("compilation", compilation => {
  43. new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation);
  44. compilation.plugin("after-optimize-chunk-assets", function(chunks) {
  45. let allModules = [];
  46. let allModuleFilenames = [];
  47. const tasks = [];
  48. chunks.forEach(function(chunk) {
  49. chunk.files.filter(ModuleFilenameHelpers.matchObject.bind(undefined, options)).map(function(file) {
  50. const asset = compilation.assets[file];
  51. if(asset.__SourceMapDevToolFile === file && asset.__SourceMapDevToolData) {
  52. const data = asset.__SourceMapDevToolData;
  53. for(const cachedFile in data) {
  54. compilation.assets[cachedFile] = data[cachedFile];
  55. if(cachedFile !== file)
  56. chunk.files.push(cachedFile);
  57. }
  58. return;
  59. }
  60. let source, sourceMap;
  61. if(asset.sourceAndMap) {
  62. const sourceAndMap = asset.sourceAndMap(options);
  63. sourceMap = sourceAndMap.map;
  64. source = sourceAndMap.source;
  65. } else {
  66. sourceMap = asset.map(options);
  67. source = asset.source();
  68. }
  69. if(sourceMap) {
  70. return {
  71. chunk,
  72. file,
  73. asset,
  74. source,
  75. sourceMap
  76. };
  77. }
  78. }).filter(Boolean).map(task => {
  79. const modules = task.sourceMap.sources.map(source => {
  80. const module = compilation.findModule(source);
  81. return module || source;
  82. });
  83. const moduleFilenames = modules.map(module => ModuleFilenameHelpers.createFilename(module, moduleFilenameTemplate, requestShortener));
  84. task.modules = modules;
  85. task.moduleFilenames = moduleFilenames;
  86. return task;
  87. }).forEach(task => {
  88. allModules = allModules.concat(task.modules);
  89. allModuleFilenames = allModuleFilenames.concat(task.moduleFilenames);
  90. tasks.push(task);
  91. });
  92. });
  93. allModuleFilenames = ModuleFilenameHelpers.replaceDuplicates(allModuleFilenames, (filename, i) => ModuleFilenameHelpers.createFilename(allModules[i], fallbackModuleFilenameTemplate, requestShortener), (ai, bi) => {
  94. let a = allModules[ai];
  95. let b = allModules[bi];
  96. a = !a ? "" : typeof a === "string" ? a : a.identifier();
  97. b = !b ? "" : typeof b === "string" ? b : b.identifier();
  98. return a.length - b.length;
  99. });
  100. allModuleFilenames = ModuleFilenameHelpers.replaceDuplicates(allModuleFilenames, (filename, i, n) => {
  101. for(let j = 0; j < n; j++)
  102. filename += "*";
  103. return filename;
  104. });
  105. tasks.forEach(task => {
  106. task.moduleFilenames = allModuleFilenames.slice(0, task.moduleFilenames.length);
  107. allModuleFilenames = allModuleFilenames.slice(task.moduleFilenames.length);
  108. });
  109. tasks.forEach(function(task) {
  110. const chunk = task.chunk;
  111. const file = task.file;
  112. const asset = task.asset;
  113. const sourceMap = task.sourceMap;
  114. const source = task.source;
  115. const moduleFilenames = task.moduleFilenames;
  116. const modules = task.modules;
  117. sourceMap.sources = moduleFilenames;
  118. if(sourceMap.sourcesContent && !options.noSources) {
  119. sourceMap.sourcesContent = sourceMap.sourcesContent.map((content, i) => `${content}\n\n\n${ModuleFilenameHelpers.createFooter(modules[i], requestShortener)}`);
  120. } else {
  121. sourceMap.sourcesContent = undefined;
  122. }
  123. sourceMap.sourceRoot = options.sourceRoot || "";
  124. sourceMap.file = file;
  125. asset.__SourceMapDevToolFile = file;
  126. asset.__SourceMapDevToolData = {};
  127. let currentSourceMappingURLComment = sourceMappingURLComment;
  128. if(currentSourceMappingURLComment !== false && /\.css($|\?)/i.test(file)) {
  129. currentSourceMappingURLComment = currentSourceMappingURLComment.replace(/^\n\/\/(.*)$/, "\n/*$1*/");
  130. }
  131. const sourceMapString = JSON.stringify(sourceMap);
  132. if(sourceMapFilename) {
  133. let filename = file;
  134. let query = "";
  135. const idx = filename.indexOf("?");
  136. if(idx >= 0) {
  137. query = filename.substr(idx);
  138. filename = filename.substr(0, idx);
  139. }
  140. let sourceMapFile = compilation.getPath(sourceMapFilename, {
  141. chunk,
  142. filename,
  143. query,
  144. basename: basename(filename)
  145. });
  146. if(sourceMapFile.indexOf("[contenthash]") !== -1) {
  147. sourceMapFile = sourceMapFile.replace(/\[contenthash\]/g, crypto.createHash("md5").update(sourceMapString).digest("hex"));
  148. }
  149. const sourceMapUrl = path.relative(path.dirname(file), sourceMapFile).replace(/\\/g, "/");
  150. if(currentSourceMappingURLComment !== false) {
  151. asset.__SourceMapDevToolData[file] = compilation.assets[file] = new ConcatSource(new RawSource(source), currentSourceMappingURLComment.replace(/\[url\]/g, sourceMapUrl));
  152. }
  153. asset.__SourceMapDevToolData[sourceMapFile] = compilation.assets[sourceMapFile] = new RawSource(sourceMapString);
  154. chunk.files.push(sourceMapFile);
  155. } else {
  156. asset.__SourceMapDevToolData[file] = compilation.assets[file] = new ConcatSource(new RawSource(source), currentSourceMappingURLComment
  157. .replace(/\[map\]/g, () => sourceMapString)
  158. .replace(/\[url\]/g, () => `data:application/json;charset=utf-8;base64,${new Buffer(sourceMapString, "utf-8").toString("base64")}`) // eslint-disable-line
  159. );
  160. }
  161. });
  162. });
  163. });
  164. }
  165. }
  166. module.exports = SourceMapDevToolPlugin;