plugin.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. var entries = require('object.entries');
  2. var path = require('path');
  3. var fse = require('fs-extra');
  4. var _ = require('lodash');
  5. const emitCountMap = new Map();
  6. const compilerHookMap = new WeakMap();
  7. const standardizeFilePaths = (file) => {
  8. file.name = file.name.replace(/\\/g, '/');
  9. file.path = file.path.replace(/\\/g, '/');
  10. return file;
  11. };
  12. function ManifestPlugin(opts) {
  13. this.opts = _.assign({
  14. publicPath: null,
  15. basePath: '',
  16. fileName: 'manifest.json',
  17. transformExtensions: /^(gz|map)$/i,
  18. writeToFileEmit: false,
  19. seed: null,
  20. filter: null,
  21. map: null,
  22. generate: null,
  23. sort: null,
  24. serialize: function(manifest) {
  25. return JSON.stringify(manifest, null, 2);
  26. },
  27. }, opts || {});
  28. }
  29. ManifestPlugin.getCompilerHooks = (compiler) => {
  30. var hooks = compilerHookMap.get(compiler);
  31. if (hooks === undefined) {
  32. const SyncWaterfallHook = require('tapable').SyncWaterfallHook;
  33. hooks = {
  34. afterEmit: new SyncWaterfallHook(['manifest'])
  35. };
  36. compilerHookMap.set(compiler, hooks);
  37. }
  38. return hooks;
  39. }
  40. ManifestPlugin.prototype.getFileType = function(str) {
  41. str = str.replace(/\?.*/, '');
  42. var split = str.split('.');
  43. var ext = split.pop();
  44. if (this.opts.transformExtensions.test(ext)) {
  45. ext = split.pop() + '.' + ext;
  46. }
  47. return ext;
  48. };
  49. ManifestPlugin.prototype.apply = function(compiler) {
  50. var moduleAssets = {};
  51. var outputFolder = compiler.options.output.path;
  52. var outputFile = path.resolve(outputFolder, this.opts.fileName);
  53. var outputName = path.relative(outputFolder, outputFile);
  54. var moduleAsset = function (module, file) {
  55. if (module.userRequest) {
  56. moduleAssets[file] = path.join(
  57. path.dirname(file),
  58. path.basename(module.userRequest)
  59. );
  60. }
  61. };
  62. var emit = function(compilation, compileCallback) {
  63. const emitCount = emitCountMap.get(outputFile) - 1
  64. emitCountMap.set(outputFile, emitCount);
  65. var seed = this.opts.seed || {};
  66. var publicPath = this.opts.publicPath != null ? this.opts.publicPath : compilation.options.output.publicPath;
  67. var stats = compilation.getStats().toJson({
  68. // Disable data generation of everything we don't use
  69. all: false,
  70. // Add asset Information
  71. assets: true,
  72. // Show cached assets (setting this to `false` only shows emitted files)
  73. cachedAssets: true,
  74. });
  75. var files = compilation.chunks.reduce(function(files, chunk) {
  76. return chunk.files.reduce(function (files, path) {
  77. var name = chunk.name ? chunk.name : null;
  78. if (name) {
  79. name = name + '.' + this.getFileType(path);
  80. } else {
  81. // For nameless chunks, just map the files directly.
  82. name = path;
  83. }
  84. // Webpack 4: .isOnlyInitial()
  85. // Webpack 3: .isInitial()
  86. // Webpack 1/2: .initial
  87. return files.concat({
  88. path: path,
  89. chunk: chunk,
  90. name: name,
  91. isInitial: chunk.isOnlyInitial ? chunk.isOnlyInitial() : (chunk.isInitial ? chunk.isInitial() : chunk.initial),
  92. isChunk: true,
  93. isAsset: false,
  94. isModuleAsset: false
  95. });
  96. }.bind(this), files);
  97. }.bind(this), []);
  98. // module assets don't show up in assetsByChunkName.
  99. // we're getting them this way;
  100. files = stats.assets.reduce(function (files, asset) {
  101. var name = moduleAssets[asset.name];
  102. if (name) {
  103. return files.concat({
  104. path: asset.name,
  105. name: name,
  106. isInitial: false,
  107. isChunk: false,
  108. isAsset: true,
  109. isModuleAsset: true
  110. });
  111. }
  112. var isEntryAsset = asset.chunks.length > 0;
  113. if (isEntryAsset) {
  114. return files;
  115. }
  116. return files.concat({
  117. path: asset.name,
  118. name: asset.name,
  119. isInitial: false,
  120. isChunk: false,
  121. isAsset: true,
  122. isModuleAsset: false
  123. });
  124. }, files);
  125. files = files.filter(function (file) {
  126. // Don't add hot updates to manifest
  127. var isUpdateChunk = file.path.indexOf('hot-update') >= 0;
  128. // Don't add manifest from another instance
  129. var isManifest = emitCountMap.get(path.join(outputFolder, file.name)) !== undefined;
  130. return !isUpdateChunk && !isManifest;
  131. });
  132. // Append optional basepath onto all references.
  133. // This allows output path to be reflected in the manifest.
  134. if (this.opts.basePath) {
  135. files = files.map(function(file) {
  136. file.name = this.opts.basePath + file.name;
  137. return file;
  138. }.bind(this));
  139. }
  140. if (publicPath) {
  141. // Similar to basePath but only affects the value (similar to how
  142. // output.publicPath turns require('foo/bar') into '/public/foo/bar', see
  143. // https://github.com/webpack/docs/wiki/configuration#outputpublicpath
  144. files = files.map(function(file) {
  145. file.path = publicPath + file.path;
  146. return file;
  147. }.bind(this));
  148. }
  149. files = files.map(standardizeFilePaths);
  150. if (this.opts.filter) {
  151. files = files.filter(this.opts.filter);
  152. }
  153. if (this.opts.map) {
  154. files = files.map(this.opts.map).map(standardizeFilePaths);
  155. }
  156. if (this.opts.sort) {
  157. files = files.sort(this.opts.sort);
  158. }
  159. var manifest;
  160. if (this.opts.generate) {
  161. const entrypointsArray = Array.from(
  162. compilation.entrypoints instanceof Map ?
  163. // Webpack 4+
  164. compilation.entrypoints.entries() :
  165. // Webpack 3
  166. entries(compilation.entrypoints)
  167. );
  168. const entrypoints = entrypointsArray.reduce(
  169. (e, [name, entrypoint]) => Object.assign(e, { [name]: entrypoint.getFiles() }),
  170. {}
  171. );
  172. manifest = this.opts.generate(seed, files, entrypoints);
  173. } else {
  174. manifest = files.reduce(function (manifest, file) {
  175. manifest[file.name] = file.path;
  176. return manifest;
  177. }, seed);
  178. }
  179. const isLastEmit = emitCount === 0
  180. if (isLastEmit) {
  181. var output = this.opts.serialize(manifest);
  182. compilation.assets[outputName] = {
  183. source: function() {
  184. return output;
  185. },
  186. size: function() {
  187. return output.length;
  188. }
  189. };
  190. if (this.opts.writeToFileEmit) {
  191. fse.outputFileSync(outputFile, output);
  192. }
  193. }
  194. if (compiler.hooks) {
  195. ManifestPlugin.getCompilerHooks(compiler).afterEmit.call(manifest);
  196. } else {
  197. compilation.applyPluginsAsync('webpack-manifest-plugin-after-emit', manifest, compileCallback);
  198. }
  199. }.bind(this);
  200. function beforeRun (compiler, callback) {
  201. let emitCount = emitCountMap.get(outputFile) || 0;
  202. emitCountMap.set(outputFile, emitCount + 1);
  203. if (callback) {
  204. callback();
  205. }
  206. }
  207. if (compiler.hooks) {
  208. const pluginOptions = {
  209. name: 'ManifestPlugin',
  210. stage: Infinity
  211. };
  212. // Preserve exposure of custom hook in Webpack 4 for back compatability.
  213. // Going forward, plugins should call `ManifestPlugin.getCompilerHooks(compiler)` directy.
  214. if (!Object.isFrozen(compiler.hooks)) {
  215. compiler.hooks.webpackManifestPluginAfterEmit = ManifestPlugin.getCompilerHooks(compiler).afterEmit;
  216. }
  217. compiler.hooks.compilation.tap(pluginOptions, function (compilation) {
  218. compilation.hooks.moduleAsset.tap(pluginOptions, moduleAsset);
  219. });
  220. compiler.hooks.emit.tap(pluginOptions, emit);
  221. compiler.hooks.run.tap(pluginOptions, beforeRun);
  222. compiler.hooks.watchRun.tap(pluginOptions, beforeRun);
  223. } else {
  224. compiler.plugin('compilation', function (compilation) {
  225. compilation.plugin('module-asset', moduleAsset);
  226. });
  227. compiler.plugin('emit', emit);
  228. compiler.plugin('before-run', beforeRun);
  229. compiler.plugin('watch-run', beforeRun);
  230. }
  231. };
  232. module.exports = ManifestPlugin;