BundleAnalyzerPlugin.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. "use strict";
  2. const fs = require('fs');
  3. const path = require('path');
  4. const {
  5. bold
  6. } = require('chalk');
  7. const Logger = require('./Logger');
  8. const viewer = require('./viewer');
  9. const utils = require('./utils');
  10. const {
  11. writeStats
  12. } = require('./statsUtils');
  13. class BundleAnalyzerPlugin {
  14. constructor(opts = {}) {
  15. this.opts = {
  16. analyzerMode: 'server',
  17. analyzerHost: '127.0.0.1',
  18. reportFilename: null,
  19. reportTitle: utils.defaultTitle,
  20. defaultSizes: 'parsed',
  21. openAnalyzer: true,
  22. generateStatsFile: false,
  23. statsFilename: 'stats.json',
  24. statsOptions: null,
  25. excludeAssets: null,
  26. logLevel: 'info',
  27. // deprecated
  28. startAnalyzer: true,
  29. ...opts,
  30. analyzerPort: 'analyzerPort' in opts ? opts.analyzerPort === 'auto' ? 0 : opts.analyzerPort : 8888
  31. };
  32. this.server = null;
  33. this.logger = new Logger(this.opts.logLevel);
  34. }
  35. apply(compiler) {
  36. this.compiler = compiler;
  37. const done = (stats, callback) => {
  38. callback = callback || (() => {});
  39. const actions = [];
  40. if (this.opts.generateStatsFile) {
  41. actions.push(() => this.generateStatsFile(stats.toJson(this.opts.statsOptions)));
  42. } // Handling deprecated `startAnalyzer` flag
  43. if (this.opts.analyzerMode === 'server' && !this.opts.startAnalyzer) {
  44. this.opts.analyzerMode = 'disabled';
  45. }
  46. if (this.opts.analyzerMode === 'server') {
  47. actions.push(() => this.startAnalyzerServer(stats.toJson()));
  48. } else if (this.opts.analyzerMode === 'static') {
  49. actions.push(() => this.generateStaticReport(stats.toJson()));
  50. } else if (this.opts.analyzerMode === 'json') {
  51. actions.push(() => this.generateJSONReport(stats.toJson()));
  52. }
  53. if (actions.length) {
  54. // Making analyzer logs to be after all webpack logs in the console
  55. setImmediate(async () => {
  56. try {
  57. await Promise.all(actions.map(action => action()));
  58. callback();
  59. } catch (e) {
  60. callback(e);
  61. }
  62. });
  63. } else {
  64. callback();
  65. }
  66. };
  67. if (compiler.hooks) {
  68. compiler.hooks.done.tapAsync('webpack-bundle-analyzer', done);
  69. } else {
  70. compiler.plugin('done', done);
  71. }
  72. }
  73. async generateStatsFile(stats) {
  74. const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
  75. await fs.promises.mkdir(path.dirname(statsFilepath), {
  76. recursive: true
  77. });
  78. try {
  79. await writeStats(stats, statsFilepath);
  80. this.logger.info(`${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`);
  81. } catch (error) {
  82. this.logger.error(`${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`);
  83. }
  84. }
  85. async startAnalyzerServer(stats) {
  86. if (this.server) {
  87. (await this.server).updateChartData(stats);
  88. } else {
  89. this.server = viewer.startServer(stats, {
  90. openBrowser: this.opts.openAnalyzer,
  91. host: this.opts.analyzerHost,
  92. port: this.opts.analyzerPort,
  93. reportTitle: this.opts.reportTitle,
  94. bundleDir: this.getBundleDirFromCompiler(),
  95. logger: this.logger,
  96. defaultSizes: this.opts.defaultSizes,
  97. excludeAssets: this.opts.excludeAssets
  98. });
  99. }
  100. }
  101. async generateJSONReport(stats) {
  102. await viewer.generateJSONReport(stats, {
  103. reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
  104. bundleDir: this.getBundleDirFromCompiler(),
  105. logger: this.logger,
  106. excludeAssets: this.opts.excludeAssets
  107. });
  108. }
  109. async generateStaticReport(stats) {
  110. await viewer.generateReport(stats, {
  111. openBrowser: this.opts.openAnalyzer,
  112. reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
  113. reportTitle: this.opts.reportTitle,
  114. bundleDir: this.getBundleDirFromCompiler(),
  115. logger: this.logger,
  116. defaultSizes: this.opts.defaultSizes,
  117. excludeAssets: this.opts.excludeAssets
  118. });
  119. }
  120. getBundleDirFromCompiler() {
  121. if (typeof this.compiler.outputFileSystem.constructor === 'undefined') {
  122. return this.compiler.outputPath;
  123. }
  124. switch (this.compiler.outputFileSystem.constructor.name) {
  125. case 'MemoryFileSystem':
  126. return null;
  127. // Detect AsyncMFS used by Nuxt 2.5 that replaces webpack's MFS during development
  128. // Related: #274
  129. case 'AsyncMFS':
  130. return null;
  131. default:
  132. return this.compiler.outputPath;
  133. }
  134. }
  135. }
  136. module.exports = BundleAnalyzerPlugin;