BundleAnalyzerPlugin.js 4.7 KB

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