index.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. const assign = require('lodash/assign');
  2. const each = require('lodash/each');
  3. const find = require('lodash/find');
  4. const isArray = require('lodash/isArray');
  5. const isFunction = require('lodash/isFunction');
  6. const isRegExp = require('lodash/isRegExp');
  7. const keys = require('lodash/keys');
  8. const values = require('lodash/values');
  9. const webpackSources = require('webpack-sources');
  10. const PHASES = {
  11. OPTIMIZE_CHUNK_ASSETS: 'compilation.optimize-chunk-assets',
  12. OPTIMIZE_ASSETS: 'compilation.optimize-assets',
  13. EMIT: 'emit'
  14. };
  15. const PHASE_LIST = values(PHASES);
  16. function ensureAssetProcessor(processor, index) {
  17. if (!processor) {
  18. throw new Error('LastCallWebpackPlugin Error: invalid options.assetProcessors[' + String(index) + '] (must be an object).');
  19. }
  20. if (!isRegExp(processor.regExp)) {
  21. throw new Error('LastCallWebpackPlugin Error: invalid options.assetProcessors[' + String(index) + '].regExp (must be an regular expression).');
  22. }
  23. if (!isFunction(processor.processor)) {
  24. throw new Error('LastCallWebpackPlugin Error: invalid options.assetProcessors[' + String(index) + '].processor (must be a function).');
  25. }
  26. if (processor.phase === undefined) {
  27. processor.phase = PHASES.OPTIMIZE_ASSETS;
  28. }
  29. if (!find(PHASE_LIST, function(p) { return p === processor.phase; })) {
  30. throw new Error('LastCallWebpackPlugin Error: invalid options.assetProcessors[' + String(index) + '].phase (must be on of: ' + PHASES.join(', ') + ').');
  31. }
  32. }
  33. class LastCallWebpackPlugin {
  34. constructor(options) {
  35. this.pluginDescriptor = this.buildPluginDescriptor();
  36. this.options = assign(
  37. {
  38. assetProcessors: [],
  39. canPrint: true
  40. },
  41. options || {}
  42. );
  43. this.phaseAssetProcessors = {};
  44. each(PHASE_LIST, (phase) => {
  45. this.phaseAssetProcessors[phase] = [];
  46. });
  47. if (!isArray(this.options.assetProcessors)) {
  48. throw new Error('LastCallWebpackPlugin Error: invalid options.assetProcessors (must be an Array).');
  49. }
  50. each(this.options.assetProcessors, (processor, index) => {
  51. ensureAssetProcessor(processor, index);
  52. this.phaseAssetProcessors[processor.phase].push(processor);
  53. });
  54. this.resetInternalState();
  55. }
  56. buildPluginDescriptor() {
  57. return { name: 'LastCallWebpackPlugin' };
  58. }
  59. resetInternalState() {
  60. this.deleteAssetsMap = {};
  61. }
  62. setAsset(assetName, assetValue, immediate, compilation) {
  63. if (assetName) {
  64. if (assetValue === null) {
  65. this.deleteAssetsMap[assetName] = true;
  66. if (immediate) {
  67. delete compilation.assets[assetName];
  68. }
  69. } else {
  70. if (assetValue !== undefined) {
  71. compilation.assets[assetName] = this.createAsset(assetValue, compilation.assets[assetName]);
  72. }
  73. }
  74. }
  75. }
  76. deleteAssets(compilation) {
  77. if (this.deleteAssetsMap && compilation) {
  78. each(keys(this.deleteAssetsMap), (key) => {
  79. delete compilation.assets[key];
  80. });
  81. }
  82. }
  83. print() {
  84. if (this.options.canPrint) {
  85. console.log.apply(console, arguments);
  86. }
  87. }
  88. createAsset(content, originalAsset) {
  89. return new webpackSources.RawSource(content);
  90. }
  91. getAssetsAndProcessors(assets, phase) {
  92. const assetProcessors = this.phaseAssetProcessors[phase];
  93. const assetNames = keys(assets);
  94. const assetsAndProcessors = [];
  95. each(assetNames, (assetName) => {
  96. each(assetProcessors, (assetProcessor) => {
  97. const regExpResult = assetProcessor.regExp.exec(assetName);
  98. assetProcessor.regExp.lastIndex = 0;
  99. if (regExpResult) {
  100. const assetAndProcessor = {
  101. assetName: assetName,
  102. regExp: assetProcessor.regExp,
  103. processor: assetProcessor.processor,
  104. regExpResult: regExpResult,
  105. };
  106. assetsAndProcessors.push(assetAndProcessor);
  107. }
  108. });
  109. });
  110. return assetsAndProcessors;
  111. }
  112. process(compilation, phase) {
  113. const assetsAndProcessors = this.getAssetsAndProcessors(compilation.assets, phase);
  114. if (assetsAndProcessors.length <= 0) {
  115. return Promise.resolve(undefined);
  116. }
  117. const promises = [];
  118. const assetsManipulationObject = {
  119. setAsset: (assetName, assetValue, immediate) => {
  120. this.setAsset(assetName, assetValue, immediate, compilation);
  121. },
  122. getAsset: (assetName) => {
  123. var asset = assetName && compilation.assets[assetName] && compilation.assets[assetName].source();
  124. return asset || undefined;
  125. }
  126. };
  127. each(assetsAndProcessors, (assetAndProcessor) => {
  128. const asset = compilation.assets[assetAndProcessor.assetName];
  129. const promise = assetAndProcessor
  130. .processor(assetAndProcessor.assetName, asset, assetsManipulationObject)
  131. .then((result) => {
  132. if (result !== undefined) {
  133. this.setAsset(assetAndProcessor.assetName, result, false, compilation);
  134. }
  135. });
  136. promises.push(promise);
  137. });
  138. return Promise.all(promises);
  139. }
  140. apply(compiler) {
  141. const hasOptimizeChunkAssetsProcessors =
  142. this.phaseAssetProcessors[PHASES.OPTIMIZE_CHUNK_ASSETS].length > 0;
  143. const hasOptimizeAssetsProcessors =
  144. this.phaseAssetProcessors[PHASES.OPTIMIZE_ASSETS].length > 0;
  145. const hasEmitProcessors =
  146. this.phaseAssetProcessors[PHASES.EMIT].length > 0;
  147. compiler.hooks.compilation.tap(
  148. this.pluginDescriptor,
  149. (compilation, params) => {
  150. this.resetInternalState();
  151. if (hasOptimizeChunkAssetsProcessors) {
  152. compilation.hooks.optimizeChunkAssets.tapPromise(
  153. this.pluginDescriptor,
  154. chunks => this.process(compilation, PHASES.OPTIMIZE_CHUNK_ASSETS, { chunks: chunks })
  155. );
  156. }
  157. if (hasOptimizeAssetsProcessors) {
  158. compilation.hooks.optimizeAssets.tapPromise(
  159. this.pluginDescriptor,
  160. assets => this.process(compilation, PHASES.OPTIMIZE_ASSETS, { assets: assets })
  161. );
  162. }
  163. }
  164. );
  165. compiler.hooks.emit.tapPromise(
  166. this.pluginDescriptor,
  167. compilation =>
  168. (
  169. hasEmitProcessors ?
  170. this.process(compilation, PHASES.EMIT) :
  171. Promise.resolve(undefined)
  172. )
  173. .then((result) => {
  174. this.deleteAssets(compilation);
  175. return result;
  176. })
  177. );
  178. }
  179. }
  180. LastCallWebpackPlugin.PHASES = PHASES;
  181. module.exports = LastCallWebpackPlugin;